package org.jruby;

public enum CompatVersion {

    RUBY1_8, RUBY1_9, BOTH;

    public static CompatVersion getVersionFromString(String compatString) {
        if (compatString.equalsIgnoreCase("RUBY1_8")) {
            return CompatVersion.RUBY1_8;
        } else if (compatString.equalsIgnoreCase("RUBY1_9")) {
            return CompatVersion.RUBY1_9;
        } else {
            return null;
        }
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2007 Damian Steer <pldms@mac.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

/**
 * An almost entirely useless interface for those objects that we _really_ want
 * to finalise.
 * 
 * @author pldms
 *
 */
public interface Finalizable {
    public void finalize();
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2002 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;
/**
 * Error numbers.
 * @fixme
 * this interface is a big hack defining a bunch of arbitrary valor as system call error numbers
 * this is actually because I need them but will probably need to be changed to something smarter 
 * sooner or later.
 * The purpose of this class it to help implement the Errno module which in turn in needed by rubicon.
 * @author Benoit Cerrina
 **/
public interface IErrno
{
    int EPERM = 1;
    int ENOENT = 2;
    int ESRCH = 3;
    int EINTR = 4;
    int EIO = 5;
    int ENXIO = 6;
    int E2BIG = 7;
    int ENOEXEC = 8;
    int EBADF = 9;
    int ECHILD = 10;
    int EDEADLK = 11;
    int ENOMEM = 12;
    int EACCES = 13;
    int EFAULT = 14;
    int ENOTBLK = 15;
    int EBUSY = 16;
    int EEXIST = 17;
    int EXDEV = 18;
    int ENODEV = 19;
    int ENOTDIR = 20;
    int EISDIR = 21;
    int EINVAL = 22;
    int ENFILE = 23;
    int EMFILE = 24;
    int ENOTTY = 25;
    int ETXTBSY = 26;
    int EFBIG = 27;
    int ENOSPC = 28;
    int ESPIPE = 29;
    int EROFS = 30;
    int EMLINK = 31;
    int EPIPE = 32;
    int EDOM = 33;
    int ERANGE = 34;
    int EWOULDBLOCK = 35;
    int EAGAIN = 35;
    int EINPROGRESS = 36;
    int EALREADY = 37;
    int ENOTSOCK = 38;
    int EDESTADDRREQ = 39;
    int EMSGSIZE = 40;
    int EPROTOTYPE = 41;
    int ENOPROTOOPT = 42;
    int EPROTONOSUPPORT = 43;
    int ESOCKTNOSUPPORT = 44;
    int EOPNOTSUPP = 45;
    int EPFNOSUPPORT = 46;
    int EAFNOSUPPORT = 47;
    int EADDRINUSE = 48;
    int EADDRNOTAVAIL = 49;
    int ENETDOWN = 50;
    int ENETUNREACH = 51;
    int ENETRESET = 52;
    int ECONNABORTED = 53;
    int ECONNRESET = 54;
    int ENOBUFS = 55;
    int EISCONN = 56;
    int ENOTCONN = 57;
    int ESHUTDOWN = 58;
    int ETOOMANYREFS = 59;
    int ETIMEDOUT = 60;
    int ECONNREFUSED = 61;
    int ELOOP = 62;
    int ENAMETOOLONG = 63;
    int EHOSTDOWN = 64;
    int EHOSTUNREACH = 65;
    int ENOTEMPTY = 66;
    int EUSERS = 68;
    int EDQUOT = 69;
    int ESTALE = 70;
    int EREMOTE = 71;
    int ENOLCK = 77;
    int ENOSYS = 78;
    int EOVERFLOW = 84;
    int EIDRM = 90;
    int ENOMSG = 91;
    int EILSEQ = 92;
    int EBADMSG = 94;
    int EMULTIHOP = 95;
    int ENODATA = 96;
    int ENOLINK = 97;
    int ENOSR = 98;
    int ENOSTR = 99;
    int EPROTO = 100;
    int ETIME = 101;
    int EOPNOTSUPP_DARWIN = 102;
}
/*
 ***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2002-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2004-2006 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2005 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2006 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * Copyright (C) 2007 William N Dortch <bill.dortch@gmail.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.util.List;
import java.util.Map;

import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.builtin.Variable;

/**
 * This class is used to provide an intermediate superclass for modules and classes that include
 * other modules. It inserts itself as the immediate superClass of the includer, but defers all
 * module methods to the actual superclass. Multiple of these intermediate superclasses can be
 * added for multiple included modules.
 * 
 * This allows the normal superclass-based searches (searchMethod, getConstant, etc) to traverse
 * the superclass ancestors as normal while the included modules do not actually show up in
 * direct inheritance traversal.
 * 
 * @see org.jruby.RubyModule
 */
public final class IncludedModuleWrapper extends RubyClass {
    private final RubyModule delegate;

    public IncludedModuleWrapper(Ruby runtime, RubyClass superClass, RubyModule delegate) {
        super(runtime, superClass, false);
        this.delegate = delegate;
        this.metaClass = delegate.metaClass;
    }

    /**
     * Overridden newIncludeClass implementation to allow attaching future includes to the correct module
     * (i.e. the one to which this is attached)
     * 
     * @see org.jruby.RubyModule#newIncludeClass(RubyClass)
     */
    @Override
    public IncludedModuleWrapper newIncludeClass(RubyClass superClass) {
        IncludedModuleWrapper includedModule = new IncludedModuleWrapper(getRuntime(), superClass, getNonIncludedClass());
        
        // include its parent (and in turn that module's parents)
        if (getSuperClass() != null) {
            includedModule.includeModule(getSuperClass());
        }
        
        return includedModule;
    }

    @Override
    public boolean isModule() {
        return false;
    }

    @Override
    public boolean isClass() {
        return false;
    }

    @Override
    public boolean isIncluded() {
        return true;
    }
    
    @Override
    public boolean isImmediate() {
        return true;
    }

    @Override
    public void setMetaClass(RubyClass newRubyClass) {
        throw new UnsupportedOperationException("An included class is only a wrapper for a module");
    }

    @Override
    public Map<String, DynamicMethod> getMethods() {
        return delegate.getMethods();
    }

    @Override
    public void addMethod(String name, DynamicMethod method) {
        throw new UnsupportedOperationException("An included class is only a wrapper for a module");
    }

    public void setMethods(Map newMethods) {
        throw new UnsupportedOperationException("An included class is only a wrapper for a module");
    }

    @Override
    public String getName() {
        return delegate.getName();
    }

    @Override
    public RubyModule getNonIncludedClass() {
        return delegate;
    }
    
    @Override
    public RubyClass getRealClass() {
        return getSuperClass().getRealClass();
    }

    @Override
    protected boolean isSame(RubyModule module) {
        return delegate.isSame(module);
    }
    
   /**
    * We don't want to reveal ourselves to Ruby code, so delegate this
    * operation.
    */    
    @Override
    public IRubyObject id() {
        return delegate.id();
    }

    //
    // VARIABLE TABLE METHODS - pass to delegate
    //

    @Override
    protected boolean variableTableContains(String name) {
        return delegate.variableTableContains(name);
    }

    @Override
    protected boolean variableTableFastContains(String internedName) {
        return delegate.variableTableFastContains(internedName);
    }

    @Override
    protected IRubyObject variableTableFetch(String name) {
        return delegate.variableTableFetch(name);
    }

    @Override
    protected IRubyObject variableTableFastFetch(String internedName) {
        return delegate.variableTableFastFetch(internedName);
    }

    @Override
    protected IRubyObject variableTableStore(String name, IRubyObject value) {
        return delegate.variableTableStore(name, value);
    }

    @Override
    protected IRubyObject variableTableFastStore(String internedName, IRubyObject value) {
        return delegate.variableTableFastStore(internedName, value);
    }

    @Override
    protected IRubyObject variableTableRemove(String name) {
        return delegate.variableTableRemove(name);
    }

    @Override
    protected VariableTableEntry[] variableTableGetTable() {
        return delegate.variableTableGetTable();
    }

    @Override
    protected int variableTableGetSize() {
        return delegate.variableTableGetSize();
    }

    @Override
    protected void variableTableSync(List<Variable<IRubyObject>> vars) {
        delegate.variableTableSync(vars);
    }

    @Override
    protected IRubyObject variableTableReadLocked(VariableTableEntry entry) {
        return delegate.variableTableReadLocked(entry);
    }

    /**
     * Method to help ease transition to new variables implementation.
     * Will likely be deprecated in the near future.
     */
    @SuppressWarnings("unchecked")
    @Override
    @Deprecated // born deprecated
    protected Map variableTableGetMap() {
        return delegate.variableTableGetMap();
    }

    /**
     * Method to help ease transition to new variables implementation.
     * Will likely be deprecated in the near future.
     */
    @SuppressWarnings("unchecked")
    @Override
    @Deprecated // born deprecated
    protected Map variableTableGetMap(Map map) {
        return delegate.variableTableGetMap(map);
    }

    //
    // CONSTANT TABLE METHODS - pass to delegate
    //

    @Override
    protected boolean constantTableContains(String name) {
        return delegate.constantTableContains(name);
    }

    @Override
    protected boolean constantTableFastContains(String internedName) {
        return delegate.constantTableFastContains(internedName);
    }

    @Override
    protected IRubyObject constantTableFetch(String name) {
        return delegate.constantTableFetch(name);
    }

    @Override
    protected IRubyObject constantTableFastFetch(String internedName) {
        return delegate.constantTableFastFetch(internedName);
    }

    @Override
    protected IRubyObject constantTableStore(String name, IRubyObject value) {
        // FIXME: legal here? may want UnsupportedOperationException
        return delegate.constantTableStore(name, value);
    }

    @Override
    protected IRubyObject constantTableFastStore(String internedName, IRubyObject value) {
        // FIXME: legal here? may want UnsupportedOperationException
        return delegate.constantTableFastStore(internedName, value);
    }

    @Override
    protected IRubyObject constantTableRemove(String name) {
        // this _is_ legal (when removing an undef)
        return delegate.constantTableRemove(name);
    }

    @Override
    protected ConstantTableEntry[] constantTableGetTable() {
        return delegate.constantTableGetTable();
    }

    @Override
    protected int constantTableGetSize() {
        return delegate.constantTableGetSize();
    }

    @Override
    protected void constantTableSync(List<Variable<IRubyObject>> vars) {
        // FIXME: legal here? may want UnsupportedOperationException
        delegate.constantTableSync(vars);
    }

    /**
     * Method to help ease transition to new variables implementation.
     * Will likely be deprecated in the near future.
     */
    @SuppressWarnings("unchecked")
    @Override
    @Deprecated // born deprecated
    protected Map constantTableGetMap() {
        return delegate.constantTableGetMap();
    }

    /**
     * Method to help ease transition to new variables implementation.
     * Will likely be deprecated in the near future.
     */
    @SuppressWarnings("unchecked")
    @Override
    @Deprecated // born deprecated
    protected Map constantTableGetMap(Map map) {
        return delegate.constantTableGetMap(map);
    }

}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2007 Charles Nutter <charles.o.nutter@sun.com>
 * Copyright (C) 2008 MenTaLguY <mental@rydia.net>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.applet.Applet;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Insets;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.image.VolatileImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.URL;
import java.util.Arrays;

import java.lang.reflect.InvocationTargetException;

import org.jruby.anno.JRubyMethod;
import org.jruby.demo.TextAreaReadline;
import org.jruby.javasupport.JavaUtil;
import org.jruby.runtime.Block;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;

import javax.swing.JScrollPane;
import javax.swing.JTextPane;

/**
 * @author <a href="mailto:mental@rydia.net">MenTaLguY</a>
 *
 * The JRubyApplet class provides a simple way to write Java applets using
 * JRuby without needing to create a custom Java applet class.  At applet
 * initialization time, JRubyApplet starts up a JRuby runtime, then evaluates
 * the scriptlet given as the "eval" applet parameter.
 *
 * The Java applet instance is available to the Ruby script as
 * JRUBY_APPLET; the script can define callbacks for applet start, stop,
 * and destroy by passing blocks to JRUBY_APPLET.on_start,
 * JRUBY_APPLET.on_stop, and JRUBY_APPLET.on_destroy, respectively.
 *
 * Ruby code can install a custom paint callback using JRUBY_APPLET.on_paint
 * (the Graphics2D object is passed as an argument to the callback).  By
 * default, JRubyApplet painting is double-buffered, but you can select
 * single-buffered painting via JRUBY_APPLET.double_buffered = false.
 *
 * The applet's background color can be set via JRUBY_APPLET.background_color=.
 * You may want to set it to nil if you're not using double-buffering, so that
 * no background color will be drawn (your own paint code is then responsible
 * for filling the area).
 *
 * Beyond these things, you should be able to use JRuby's Java integration
 * to do whatever you would do in Java with the applet instance.
 *
 */
public class JRubyApplet extends Applet {
    private Ruby runtime;
    private boolean doubleBuffered = true;
    private Color backgroundColor = Color.WHITE;
    private RubyProc startProc;
    private RubyProc stopProc;
    private RubyProc destroyProc;
    private RubyProc paintProc;
    private Graphics priorGraphics;
    private IRubyObject wrappedGraphics;
    private VolatileImage backBuffer;
    private Graphics backBufferGraphics;
    private Facade facade;

    private interface Facade {
        public InputStream getInputStream();
        public PrintStream getOutputStream();
        public PrintStream getErrorStream();
        public void attach(Ruby runtime, Applet applet);
        public void destroy();
    }

    private static RubyProc blockToProc(Ruby runtime, Block block) {
        if (block.isGiven()) {
            RubyProc proc = block.getProcObject();
            if (proc == null) {
                proc = RubyProc.newProc(runtime, block, block.type);
            }
            return proc;
        } else {
            return null;
        }
    }

    private boolean getBooleanParameter(String name, boolean defaultValue) {
        String value = getParameter(name);
        if ( value != null ) {
            return value.equals("true");
        } else {
            return defaultValue;
        }
    }

    private InputStream getCodeResourceAsStream(String name) {
        if (name == null) {
            return null;
        }
        try {
            final URL directURL = new URL(getCodeBase(), name);
            return directURL.openStream();
        } catch (IOException e) {
        }
        return JRubyApplet.class.getClassLoader().getResourceAsStream(name);
    }

    private static void safeInvokeAndWait(Runnable runnable) throws InvocationTargetException, InterruptedException {
        if (EventQueue.isDispatchThread()) {
            try {
                runnable.run();
            } catch (Exception e) {
                throw new InvocationTargetException(e);
            }
        } else {
            EventQueue.invokeAndWait(runnable);
        }
    }

    public static class RubyMethods {
        @JRubyMethod
        public static IRubyObject on_start(IRubyObject recv, Block block) {
            JRubyApplet applet = (JRubyApplet)recv.dataGetStruct();
            synchronized (applet) {
                applet.startProc = blockToProc(applet.runtime, block);
            }
            return recv;
        }

        @JRubyMethod
        public static IRubyObject on_stop(IRubyObject recv, Block block) {
            JRubyApplet applet = (JRubyApplet)recv.dataGetStruct();
            synchronized (applet) {
                applet.stopProc = blockToProc(applet.runtime, block);
            }
            return recv;
        }

        @JRubyMethod
        public static IRubyObject on_destroy(IRubyObject recv, Block block) {
            JRubyApplet applet = (JRubyApplet)recv.dataGetStruct();
            synchronized (applet) {
                applet.destroyProc = blockToProc(applet.runtime, block);
            }
            return recv;
        }

        @JRubyMethod
        public static IRubyObject on_paint(IRubyObject recv, Block block) {
            JRubyApplet applet = (JRubyApplet)recv.dataGetStruct();
            synchronized (applet) {
                applet.paintProc = blockToProc(applet.runtime, block);
                applet.repaint();
            }
            return recv;
        }
    }

    @Override
    public void init() {
        super.init();

        if (getBooleanParameter("jruby.console", false)) {
            facade = new ConsoleFacade(getParameter("jruby.banner"));
        } else {
            facade = new TrivialFacade();
        }

        synchronized (this) {
            if (runtime != null) {
                return;
            }

            final RubyInstanceConfig config = new RubyInstanceConfig() {{
                setInput(facade.getInputStream());
                setOutput(facade.getOutputStream());
                setError(facade.getErrorStream());
                setObjectSpaceEnabled(getBooleanParameter("jruby.objectspace", false));
            }};
            Ruby.setSecurityRestricted(true);
            runtime = Ruby.newInstance(config);
        }

        final String scriptName = getParameter("jruby.script");
        final InputStream scriptStream = getCodeResourceAsStream(scriptName);
        final String evalString = getParameter("jruby.eval");

        try {
            final JRubyApplet applet = this;
            safeInvokeAndWait(new Runnable() {
                public void run() {
                    applet.setLayout(new BorderLayout());
                    applet.facade.attach(applet.runtime, applet);
                    if (scriptStream != null) {
                        applet.runtime.runFromMain(scriptStream, scriptName);
                    }
                    if (evalString != null) {
                        applet.runtime.evalScriptlet(evalString);
                    }
                }
            });
        } catch (InterruptedException e) {
        } catch (InvocationTargetException e) {
            throw new RuntimeException("Error running script", e.getCause());
        }
    }

    private void invokeCallback(final RubyProc proc, final IRubyObject[] args) {
        if (proc == null) {
            return;
        }
        final Ruby runtime = this.runtime;
        try {
            safeInvokeAndWait(new Runnable() {
                public void run() {
                    ThreadContext context = runtime.getCurrentContext();
                    proc.call(context, args);
                }
            });
        } catch (InterruptedException e) {
        } catch (InvocationTargetException e) {
            throw new RuntimeException("Ruby callback failed", e.getCause());
        }
    }

    public synchronized void setBackgroundColor(Color color) {
        backgroundColor = color;
        repaint();
    }

    public synchronized Color getBackgroundColor() {
        return backgroundColor;
    }

    public synchronized boolean isDoubleBuffered() {
        return doubleBuffered;
    }

    public synchronized void setDoubleBuffered(boolean shouldBuffer) {
        doubleBuffered = shouldBuffer;
        repaint();
    }

    @Override
    public synchronized void start() {
        super.start();
        invokeCallback(startProc, new IRubyObject[] {});
    }

    @Override
    public synchronized void stop() {
        invokeCallback(stopProc, new IRubyObject[] {});
        super.stop();
    }

    @Override
    public synchronized void destroy() {
        try {
            invokeCallback(destroyProc, new IRubyObject[] {});
        } finally {
            facade.destroy();
            final Ruby runtime = this.runtime;
            this.runtime = null;
            startProc = null;
            stopProc = null;
            destroyProc = null;
            paintProc = null;
            priorGraphics = null;
            wrappedGraphics = null;
            runtime.tearDown();
            super.destroy();
        }
    }

    @Override
    public void update(Graphics g) {
        paint(g);
    }

    @Override
    public synchronized void paint(Graphics g) {
        if (doubleBuffered) {
            paintBuffered(g);
        } else {
            paintUnbuffered(g);
        }
    }

    private synchronized void paintBuffered(Graphics g) {
        do {
            GraphicsConfiguration config = getGraphicsConfiguration();
            int width = getWidth();
            int height = getHeight();
            if (backBuffer == null || width != backBuffer.getWidth() || height != backBuffer.getHeight() || backBuffer.validate(config) == VolatileImage.IMAGE_INCOMPATIBLE) {
                if (backBuffer != null) {
                    backBufferGraphics.dispose();
                    backBufferGraphics = null;
                    backBuffer.flush();
                    backBuffer = null;
                }
                backBuffer = config.createCompatibleVolatileImage(width, height);
                backBufferGraphics = backBuffer.createGraphics();
            }
            backBufferGraphics.setClip(g.getClip());
            paintUnbuffered(backBufferGraphics);
            g.drawImage(backBuffer, 0, 0, this);
        } while (backBuffer.contentsLost());
    }

    private synchronized void paintUnbuffered(Graphics g) {
        if (backgroundColor != null) {
            g.setColor(backgroundColor);
            g.fillRect(0, 0, getWidth(), getHeight());
        }
        if (paintProc != null) {
            if (priorGraphics != g) {
                wrappedGraphics = JavaUtil.convertJavaToUsableRubyObject(runtime, g);
                priorGraphics = g;
            }
            ThreadContext context = runtime.getCurrentContext();
            paintProc.call(context, new IRubyObject[] {wrappedGraphics});
        }
        super.paint(g);
    }

    private static class TrivialFacade implements Facade {
        public TrivialFacade() {}
        public InputStream getInputStream() { return System.in; }
        public PrintStream getOutputStream() { return System.out; }
        public PrintStream getErrorStream() { return System.err; }
        public void attach(Ruby runtime, Applet applet) {
            final IRubyObject wrappedApplet = JavaUtil.convertJavaToUsableRubyObject(runtime, applet);
            wrappedApplet.dataWrapStruct(applet);
            runtime.defineGlobalConstant("JRUBY_APPLET", wrappedApplet);
            wrappedApplet.getMetaClass().defineAnnotatedMethods(RubyMethods.class);
        }
        public void destroy() {}
    }

    private static class ConsoleFacade implements Facade {
        private JTextPane textPane;
        private JScrollPane scrollPane;
        private TextAreaReadline adaptor;
        private InputStream inputStream;
        private PrintStream outputStream;
        private PrintStream errorStream;
        
        public ConsoleFacade(String bannerText) {
            textPane = new JTextPane();
	    textPane.setMargin(new Insets(4, 4, 0, 4));
            textPane.setCaretColor(new Color(0xa4, 0x00, 0x00));
            textPane.setBackground(new Color(0xf2, 0xf2, 0xf2));
            textPane.setForeground(new Color(0xa4, 0x00, 0x00));

            Font font = findFont("Monospaced", Font.PLAIN, 14,
                                 new String[] {"Monaco", "Andale Mono"});

            textPane.setFont(font);

            scrollPane = new JScrollPane(textPane);
            scrollPane.setDoubleBuffered(true);
            if ( bannerText != null ) {
                bannerText = "  " + bannerText + "  \n\n";
            }
            adaptor = new TextAreaReadline(textPane, bannerText);
            inputStream = adaptor.getInputStream();
            outputStream = new PrintStream(adaptor.getOutputStream());
            errorStream = new PrintStream(adaptor.getOutputStream());
        }

        public InputStream getInputStream() { return inputStream; }
        public PrintStream getOutputStream() { return outputStream; }
        public PrintStream getErrorStream() { return errorStream; }

        public void attach(Ruby runtime, Applet applet) {
            adaptor.hookIntoRuntime(runtime);
            applet.add(scrollPane);
            applet.validate();
        }

        public void destroy() {
            Container parent = scrollPane.getParent();
            adaptor.shutdown();
            if (parent != null) {
                parent.remove(scrollPane);
            }
        }

        private Font findFont(String otherwise, int style, int size, String[] families) {
            String[] fonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
            Arrays.sort(fonts);
            for (int i = 0; i < families.length; i++) {
                if (Arrays.binarySearch(fonts, families[i]) >= 0) {
                    return new Font(families[i], style, size);
                }
            }
            return new Font(otherwise, style, size);
        }
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2007 Ola Bini <ola@ologix.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.io.BufferedWriter;
import java.io.OutputStreamWriter;

import java.net.InetAddress;
import java.net.Socket;

/**
 * @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
 */
public class JRubyClient extends JRubyService {
    public JRubyClient(String[] args) throws Exception {
        Configuration conf = new Configuration(args[0]);
        if(conf.isDebug()) {
            System.err.println("Starting client with port " + conf.getPort() + ", key " + conf.getKey() + " and command " + conf.getCommand());
        }
        Socket socket = new Socket(InetAddress.getLocalHost(), conf.getPort());
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        if(conf.terminate()) {
            writer.write(CMD_TERM + " " + conf.getKey() + "\n");
        } else if(conf.noMore()) {
            writer.write(CMD_NO_MORE + " " + conf.getKey() + "\n");
        } else {
            writer.write(CMD_START + " " + conf.getKey() + " " + conf.getCommand() + "\n");
        }
        writer.flush();
        writer.close();
        socket.close();
    }

    public static void main(String[] args) throws Exception {
        new JRubyClient(args);
    }
}// JRubyClient
/*
 ***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2007 Ola Bini <ola@ologix.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.io.BufferedReader;
import java.io.InputStreamReader;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

import java.util.List;
import java.util.ArrayList;

/**
 * @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
 */
public class JRubyServer extends JRubyService {
    private Configuration conf;

    private boolean stillStarting = true;

    private JRubyServer(String[] args) throws Exception {
        conf = new Configuration(args[0]);
        if(conf.isDebug()) {
            System.err.println("Starting server with port " + conf.getPort() + " and key " + conf.getKey());
        }
        ServerSocket server = new ServerSocket();
        server.bind(new InetSocketAddress(InetAddress.getLocalHost(),conf.getPort()));
        while(true) {
            Thread t1 = new Thread(new Handler(server.accept()));
            t1.setDaemon(true);
            t1.start();
        }
    }

    private class Handler implements Runnable {
        private Socket socket;

        public Handler(Socket socket) {
            this.socket = socket;
        }

        public void run() {
            try {
                BufferedReader rr = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
                String command = rr.readLine();
                rr.close();
                this.socket.close();
                this.socket = null;
                if(conf.isDebug()) {
                    System.err.println("Got command: " + command);
                }
                String[] cmds = command.split(" ", 3);
                if(cmds[1].equals(conf.getKey())) {
                    if(cmds[0].equals(CMD_TERM)) {
                        if(conf.isDebug()) {
                            System.err.println("Terminating hard");
                        }
                        System.exit(0);
                    } else if(cmds[0].equals(CMD_NO_MORE)) {
                        if(conf.isDebug()) {
                            System.err.println("Accepting no more START");
                        }
                        stillStarting = false;
                    } else if(cmds[0].equals(CMD_START)) {
                        if(stillStarting) {
                            if(conf.isDebug()) {
                                System.err.println("Doing START on command " + cmds[2]);
                            }
                            new Main().run(intoCommandArguments(cmds[2].trim()));
                        } else {
                            if(conf.isDebug()) {
                                System.err.println("Not doing START anymore, invalid command");
                            }
                        }
                    } else {
                        if(conf.isDebug()) {
                            System.err.println("Unrecognized command");
                        }
                    }
                } else {
                    if(conf.isDebug()) {
                        System.err.println("Invalid key");
                    }
                }
            } catch(Exception e) {}
        }
    }

    protected static String[] intoCommandArguments(String str) {
        List<String> args = new ArrayList<String>();
        boolean inSingle = false;
        int contentStart = -1;

        for(int i=0,j=str.length();i<j;i++) {
            if(str.charAt(i) == ' ' && !inSingle && contentStart != -1) {
                args.add(str.substring(contentStart,i));
                contentStart = -1;
                continue;
            }
            if(str.charAt(i) == ' ') {
                continue;
            }
            if(str.charAt(i) == '\'' && !inSingle) {
                inSingle = true;
                contentStart = i+1;
                continue;
            }
            if(str.charAt(i) == '\'') {
                inSingle = false;
                args.add(str.substring(contentStart,i));
                contentStart = -1;
                continue;
            }
            if(contentStart == -1) {
                contentStart = i;
            }
        }
        if(contentStart != -1) {
            args.add(str.substring(contentStart));
        }
        return (String[])args.toArray(new String[0]);
    }

    public static void main(String[] args) throws Exception {
        new JRubyServer(args);        
    }
}// JRubyServer
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2007 Ola Bini <ola@ologix.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

/**
 * @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
 */
public abstract class JRubyService {
    protected static class Configuration {
        private final static int DEFAULT_PORT = 19222;

        private String key;
        private int port = DEFAULT_PORT;
        private boolean terminate;
        private boolean noMore;
        private boolean debug;
        private String command;

        public Configuration(String args) {
            int i=0;
            int stop;
            loop: for(int j=args.length();i<j;i++) {
                if(args.charAt(i) == '-' && i+1 < j) {
                    switch(args.charAt(++i)) {
                    case 'k':
                        stop = args.indexOf(" ", (++i) + 1);
                        if(stop == -1) {
                            stop = args.length();
                        }
                        key = args.substring(i, stop).trim();
                        i = stop;
                        break;
                    case 'p':
                        stop = args.indexOf(" ", (++i) + 1);
                        if(stop == -1) {
                            stop = args.length();
                        }
                        port = Integer.parseInt(args.substring(i, stop).trim());
                        i = stop;
                        break;
                    case 't':
                        terminate = true;
                        i++;
                        break;
                    case 'n':
                        noMore = true;
                        i++;
                        break;
                    case 'd':
                        debug = true;
                        i++;
                        break;
                    case '-': // handle everything after -- as arguments to the jruby process
                        i++;
                        break loop;
                    default:
                        i--;
                        break loop;
                    }                    
                } else if(args.charAt(i) != ' ') {
                    break loop;
                }
            }
            if(i<args.length()) {
                command = args.substring(i).trim();
            }
        }
        
        public String getKey() {
            return key;
        }

        public int getPort() {
            return port;
        }

        public boolean terminate() {
            return terminate;
        }

        public boolean noMore() {
            return noMore;
        }

        public boolean isDebug() {
            return debug;
        }

        public String getCommand() {
            return command;
        }
    }

    public static final String CMD_START = "START";
    public static final String CMD_NO_MORE = "NO_MORE";
    public static final String CMD_TERM = "TERM";
}// JRubyService
/*
 ***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004-2006 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2005 Kiel Hodges <jruby-devel@selfsosoft.com>
 * Copyright (C) 2005 Jason Voegele <jason@jvoegele.com>
 * Copyright (C) 2005 Tim Azzopardi <tim@tigerfive.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.io.InputStream;
import java.io.PrintStream;

import org.jruby.exceptions.MainExitException;
import org.jruby.exceptions.RaiseException;
import org.jruby.exceptions.ThreadKill;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.SafePropertyAccessor;
import org.jruby.util.SimpleSampler;

/**
 * Class used to launch the interpreter.
 * This is the main class as defined in the jruby.mf manifest.
 * It is very basic and does not support yet the same array of switches
 * as the C interpreter.
 *       Usage: java -jar jruby.jar [switches] [rubyfile.rb] [arguments]
 *           -e 'command'    one line of script. Several -e's allowed. Omit [programfile]
 * @author  jpetersen
 */
public class Main {
    private boolean hasPrintedUsage = false;
    private final RubyInstanceConfig config;

    public Main(RubyInstanceConfig config) {
        this.config = config;
    }

    public Main(final InputStream in, final PrintStream out, final PrintStream err) {
        this(new RubyInstanceConfig(){{
            setInput(in);
            setOutput(out);
            setError(err);
        }});
    }

    public Main() {
        this(new RubyInstanceConfig());
    }

    public static void main(String[] args) {
        Main main = new Main();
        
        try {
            int status = main.run(args);
            if (status != 0) {
                System.exit(status);
            }
        } catch (RaiseException re) {
            throw re;
        } catch (Throwable t) {
            // print out as a nice Ruby backtrace
            System.err.println(ThreadContext.createRawBacktraceStringFromThrowable(t));
            System.exit(1);
        }
    }

    public int run(String[] args) {
        try {
            config.processArguments(args);
            return run();
        } catch (MainExitException mee) {
            if (!mee.isAborted()) {
                config.getOutput().println(mee.getMessage());
                if (mee.isUsageError()) {
                    printUsage();
                }
            }
            return mee.getStatus();
        } catch (OutOfMemoryError oome) {
            // produce a nicer error since Rubyists aren't used to seeing this
            System.gc();
            
            String memoryMax = SafePropertyAccessor.getProperty("jruby.memory.max");
            String message = "";
            if (memoryMax != null) {
                message = " of " + memoryMax;
            }
            System.err.println("Error: Your application used more memory than the safety cap" + message + ".");
            System.err.println("Specify -J-Xmx####m to increase it (#### = cap size in MB).");
            
            if (config.getVerbose()) {
                System.err.println("Exception trace follows:");
                oome.printStackTrace();
            } else {
                System.err.println("Specify -w for full OutOfMemoryError stack trace");
            }
            return 1;
        } catch (StackOverflowError soe) {
            // produce a nicer error since Rubyists aren't used to seeing this
            System.gc();
            
            String stackMax = SafePropertyAccessor.getProperty("jruby.stack.max");
            String message = "";
            if (stackMax != null) {
                message = " of " + stackMax;
            }
            System.err.println("Error: Your application used more stack memory than the safety cap" + message + ".");
            System.err.println("Specify -J-Xss####k to increase it (#### = cap size in KB).");
            
            if (config.getVerbose()) {
                System.err.println("Exception trace follows:");
                soe.printStackTrace();
            } else {
                System.err.println("Specify -w for full StackOverflowError stack trace");
            }
            return 1;
        } catch (UnsupportedClassVersionError ucve) {
            System.err.println("Error: Some library (perhaps JRuby) was built with a later JVM version.");
            System.err.println("Please use libraries built with the version you intend to use or an earlier one.");
            
            if (config.getVerbose()) {
                System.err.println("Exception trace follows:");
                ucve.printStackTrace();
            } else {
                System.err.println("Specify -w for full UnsupportedClassVersionError stack trace");
            }
            return 1;
        } catch (ThreadKill kill) {
            return 0;
        }
    }

    public int run() {
        if (config.isShowVersion()) {
            showVersion();
        }
        
        if (config.isShowCopyright()) {
            showCopyright();
        }

        if (!config.shouldRunInterpreter() ) {
            if (config.shouldPrintUsage()) {
                printUsage();
            }
            if (config.shouldPrintProperties()) {
                printProperties();
            }
            return 0;
        }

        InputStream in   = config.getScriptSource();
        String filename  = config.displayedFileName();
        Ruby runtime     = Ruby.newInstance(config);
        
        // set thread context JRuby classloader here, for the main thread
        try {
            Thread.currentThread().setContextClassLoader(runtime.getJRubyClassLoader());
        } catch (SecurityException se) {
            // can't set TC classloader
            if (runtime.getInstanceConfig().isVerbose()) {
                System.err.println("WARNING: Security restrictions disallowed setting context classloader for main thread.");
            }
        }

        if (in == null) {
            // no script to run, return success below
        } else if (config.isShouldCheckSyntax()) {
            runtime.parseFromMain(in, filename);
            config.getOutput().println("Syntax OK");
        } else {
            long now = -1;

            try {
                if (config.isBenchmarking()) {
                    now = System.currentTimeMillis();
                }

                if (config.isSamplingEnabled()) {
                    SimpleSampler.startSampleThread();
                }

                try {
                    runtime.runFromMain(in, filename);
                } finally {
                    runtime.tearDown();

                    if (config.isBenchmarking()) {
                        config.getOutput().println("Runtime: " + (System.currentTimeMillis() - now) + " ms");
                    }

                    if (config.isSamplingEnabled()) {
                        org.jruby.util.SimpleSampler.report();
                    }
                }
            } catch (RaiseException rj) {
                RubyException raisedException = rj.getException();
                if (runtime.getSystemExit().isInstance(raisedException)) {
                    IRubyObject status = raisedException.callMethod(runtime.getCurrentContext(), "status");

                    if (status != null && !status.isNil()) {
                        return RubyNumeric.fix2int(status);
                    }
                } else {
                    runtime.printError(raisedException);
                    return 1;
                }
            }
        }
        return 0;
    }

    private void showVersion() {
        config.getOutput().print(config.getVersionString());
    }

    private void showCopyright() {
        config.getOutput().print(config.getCopyrightString());
    }

    public void printUsage() {
        if (!hasPrintedUsage) {
            config.getOutput().print(config.getBasicUsageHelp());
            hasPrintedUsage = true;
        }
    }
    
    public void printProperties() {
        config.getOutput().print(config.getPropertyHelp());
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2002-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2004-2006 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.lang.ref.SoftReference;

import org.jruby.runtime.builtin.IRubyObject;

public final class MetaClass extends RubyClass {
    
    private SoftReference<IRubyObject> attached = new SoftReference<IRubyObject>(null); 

    /** NEWOBJ (in RubyObject#getSingletonClassClone()) 
     * 
     */
    public MetaClass(Ruby runtime) {
        super(runtime, null, false);
    }
    
    /** rb_class_boot (for MetaClasses) (in makeMetaClass(RubyClass))
     * 
     */
    public MetaClass(Ruby runtime, RubyClass superClass) {
        super(runtime, superClass, false);
        index = superClass.index; // use same ClassIndex as metaclass, since we're technically still of that type
    }
 
    public boolean isSingleton() {
        return true;
    }

    /**
     * If an object uses an anonymous class 'class << obj', then this grabs the original 
     * metaclass and not the one that get injected as a result of 'class << obj'.
     */
    public RubyClass getRealClass() {
        return superClass.getRealClass();
    }
    
    public final IRubyObject allocate(){
        throw getRuntime().newTypeError("can't create instance of virtual class");
    }

    public IRubyObject getAttached() {
        return attached.get();
    }

    public void setAttached(IRubyObject attached) {
        this.attached = new SoftReference<IRubyObject>(attached);
    }

}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2005 David Corbin <dcorbin@users.sourceforge.net>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.io.PrintStream;

import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.javasupport.Java;
import org.jruby.javasupport.JavaObject;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.builtin.IRubyObject;

@JRubyClass(name = "NativeException", parent = "RuntimeError")
public class NativeException extends RubyException {

    private final Throwable cause;
    public static final String CLASS_NAME = "NativeException";
    private final Ruby runtime;

    public NativeException(Ruby runtime, RubyClass rubyClass, Throwable cause) {
        super(runtime, rubyClass, cause.getClass().getName() + ": " + cause.getMessage());
        this.runtime = runtime;
        this.cause = cause;
    }

    public static RubyClass createClass(Ruby runtime, RubyClass baseClass) {
        // FIXME: If NativeException is expected to be used from Ruby code, it should provide
        // a real allocator to be used. Otherwise Class.new will fail, as will marshalling. JRUBY-415
        RubyClass exceptionClass = runtime.defineClass(CLASS_NAME, baseClass, ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);

        exceptionClass.defineAnnotatedMethods(NativeException.class);

        return exceptionClass;
    }

    @JRubyMethod(frame = true)
    public IRubyObject cause(Block unusedBlock) {
        return Java.wrap(getRuntime(), JavaObject.wrap(getRuntime(), cause));
    }

    public IRubyObject backtrace() {
        IRubyObject rubyTrace = super.backtrace();
        if (rubyTrace.isNil()) {
            return rubyTrace;
        }
        RubyArray array = (RubyArray) rubyTrace.dup();
        StackTraceElement[] stackTrace = cause.getStackTrace();
        for (int i = stackTrace.length - 1; i >= 0; i--) {
            StackTraceElement element = stackTrace[i];
            String className = element.getClassName();
            String line = null;
            if (element.getFileName() == null) {
                line = className + ":" + element.getLineNumber() + ":in `" + element.getMethodName() + "'";
            } else {
                int index = className.lastIndexOf(".");
                String packageName = null;
                if (index == -1) {
                    packageName = "";
                } else {
                    packageName = className.substring(0, index) + "/";
                }
                line = packageName.replace(".", "/") + element.getFileName() + ":" + element.getLineNumber() + ":in `" + element.getMethodName() + "'";
            }
            RubyString string = runtime.newString(line);
            array.unshift(string);
        }
        return array;
    }

    public void printBacktrace(PrintStream errorStream) {
        super.printBacktrace(errorStream);
        errorStream.println("Complete Java stackTrace");
        cause.printStackTrace(errorStream);
    }

    public Throwable getCause() {
        return cause;
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2006 Ola Bini <ola@ologix.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

/**
 * @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
 */
public interface Profile {
    Profile ALL = new Profile() {
            public boolean allowBuiltin(String name) { return true; }
            public boolean allowClass(String name) { return true; }
            public boolean allowModule(String name) { return true; }
            public boolean allowLoad(String name) { return true; }
            public boolean allowRequire(String name) { return true; }
        };
    Profile DEBUG_ALLOW = new Profile() {
            public boolean allowBuiltin(String name) { System.err.println("allowBuiltin("+name+")"); return true; }
            public boolean allowClass(String name) { System.err.println("allowClass("+name+")"); return true; }
            public boolean allowModule(String name) { System.err.println("allowModule("+name+")"); return true; }
            public boolean allowLoad(String name) { System.err.println("allowLoad("+name+")"); return true; }
            public boolean allowRequire(String name) { System.err.println("allowRequire("+name+")"); return true; }
        };
    Profile NO_FILE_CLASS = new Profile() {
            public boolean allowBuiltin(String name) { return true; }
            public boolean allowClass(String name) { return !name.equals("File"); }
            public boolean allowModule(String name) { return true; }
            public boolean allowLoad(String name) { return true; }
            public boolean allowRequire(String name) { return true; }
        };
    Profile ANY = ALL;
    Profile DEFAULT = ALL;
    
    boolean allowBuiltin(String name);
    boolean allowClass(String name);
    boolean allowModule(String name);
    boolean allowLoad(String name);
    boolean allowRequire(String name);
}// Profile
/*
 **** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Chad Fowler <chadfowler@chadfowler.com>
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004-2005 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2006 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * Copyright (C) 2006 Michael Studman <codehaus@michaelstudman.com>
 * Copyright (C) 2006 Ola Bini <ola@ologix.com>
 * Copyright (C) 2007 Nick Sieger <nicksieger@gmail.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.Stack;
import java.util.Vector;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.joda.time.DateTimeZone;
import org.jruby.ast.Node;
import org.jruby.ast.executable.RubiniusRunner;
import org.jruby.ast.executable.Script;
import org.jruby.ast.executable.YARVCompiledRunner;
import org.jruby.common.RubyWarnings;
import org.jruby.common.IRubyWarnings.ID;
import org.jruby.compiler.ASTCompiler;
import org.jruby.compiler.ASTInspector;
import org.jruby.compiler.JITCompiler;
import org.jruby.compiler.NotCompilableException;
import org.jruby.compiler.impl.StandardASMCompiler;
import org.jruby.compiler.yarv.StandardYARVCompiler;
import org.jruby.exceptions.JumpException;
import org.jruby.exceptions.RaiseException;
import org.jruby.ext.JRubyPOSIXHandler;
import org.jruby.ext.LateLoadingLibrary;
import org.jruby.ext.posix.POSIX;
import org.jruby.ext.posix.POSIXFactory;
import org.jruby.internal.runtime.GlobalVariables;
import org.jruby.internal.runtime.ThreadService;
import org.jruby.internal.runtime.ValueAccessor;
import org.jruby.javasupport.JavaSupport;
import org.jruby.management.BeanManager;
import org.jruby.management.ClassCache;
import org.jruby.management.Config;
import org.jruby.parser.Parser;
import org.jruby.parser.ParserConfiguration;
import org.jruby.runtime.Binding;
import org.jruby.runtime.Block;
import org.jruby.runtime.CacheMap;
import org.jruby.runtime.CallSite;
import org.jruby.runtime.CallbackFactory;
import org.jruby.runtime.DynamicScope;
import org.jruby.runtime.EventHook;
import org.jruby.runtime.GlobalVariable;
import org.jruby.runtime.IAccessor;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ObjectSpace;
import org.jruby.runtime.RubyEvent;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.load.Library;
import org.jruby.runtime.load.LoadService;
import org.jruby.util.BuiltinScript;
import org.jruby.util.ByteList;
import org.jruby.util.IOInputStream;
import org.jruby.util.IOOutputStream;
import org.jruby.util.JRubyClassLoader;
import org.jruby.util.JavaNameMangler;
import org.jruby.util.KCode;
import org.jruby.util.SafePropertyAccessor;
import org.jruby.util.collections.WeakHashSet;
import org.jruby.util.io.ChannelDescriptor;

/**
 * The Ruby object represents the top-level of a JRuby "instance" in a given VM.
 * JRuby supports spawning multiple instances in the same JVM. Generally, objects
 * created under these instances are tied to a given runtime, for such details
 * as identity and type, because multiple Ruby instances means there are
 * multiple instances of each class. This means that in multi-runtime mode
 * (or really, multi-VM mode, where each JRuby instance is a ruby "VM"), objects
 * generally can't be transported across runtimes without marshaling.
 * 
 * This class roots everything that makes the JRuby runtime function, and
 * provides a number of utility methods for constructing global types and
 * accessing global runtime structures.
 */
public final class Ruby {
    /**
     * Returns a new instance of the JRuby runtime configured with defaults.
     *
     * @return the JRuby runtime
     * @see org.jruby.RubyInstanceConfig
     */
    public static Ruby newInstance() {
        return newInstance(new RubyInstanceConfig());
    }

    /**
     * Returns a new instance of the JRuby runtime configured as specified.
     *
     * @param config The instance configuration
     * @return The JRuby runtime
     * @see org.jruby.RubyInstanceConfig
     */
    public static Ruby newInstance(RubyInstanceConfig config) {
        Ruby ruby = new Ruby(config);
        ruby.init();
        return ruby;
    }

    /**
     * Returns a new instance of the JRuby runtime configured with the given
     * input, output and error streams and otherwise default configuration
     * (except where specified system properties alter defaults).
     *
     * @param in the custom input stream
     * @param out the custom output stream
     * @param err the custom error stream
     * @return the JRuby runtime
     * @see org.jruby.RubyInstanceConfig
     */
    public static Ruby newInstance(InputStream in, PrintStream out, PrintStream err) {
        RubyInstanceConfig config = new RubyInstanceConfig();
        config.setInput(in);
        config.setOutput(out);
        config.setError(err);
        return newInstance(config);
    }
    
    /**
     * Create and initialize a new JRuby runtime. The properties of the
     * specified RubyInstanceConfig will be used to determine various JRuby
     * runtime characteristics.
     * 
     * @param config The configuration to use for the new instance
     * @see org.jruby.RubyInstanceConfig
     */
    private Ruby(RubyInstanceConfig config) {
        this.config             = config;
        this.threadService      = new ThreadService(this);
        if(config.isSamplingEnabled()) {
            org.jruby.util.SimpleSampler.registerThreadContext(threadService.getCurrentContext());
        }

        this.in                 = config.getInput();
        this.out                = config.getOutput();
        this.err                = config.getError();
        this.objectSpaceEnabled = config.isObjectSpaceEnabled();
        this.profile            = config.getProfile();
        this.currentDirectory   = config.getCurrentDirectory();
        this.kcode              = config.getKCode();
        this.beanManager        = new BeanManager(this, config.isManagementEnabled());
        this.jitCompiler        = new JITCompiler(this);
        
        this.beanManager.register(new Config(this));
        this.beanManager.register(new ClassCache(this));
        
        this.cacheMap = new CacheMap(this);
    }
    
    /**
     * Evaluates a script under the current scope (perhaps the top-level
     * scope) and returns the result (generally the last value calculated).
     * This version goes straight into the interpreter, bypassing compilation
     * and runtime preparation typical to normal script runs.
     * 
     * @param script The scriptlet to run
     * @returns The result of the eval
     */
    public IRubyObject evalScriptlet(String script) {
        ThreadContext context = getCurrentContext();
        Node node = parseEval(script, "<script>", context.getCurrentScope(), 0);
        
        try {
            return node.interpret(this, context, context.getFrameSelf(), Block.NULL_BLOCK);
        } catch (JumpException.ReturnJump rj) {
            throw newLocalJumpError("return", (IRubyObject)rj.getValue(), "unexpected return");
        } catch (JumpException.BreakJump bj) {
            throw newLocalJumpError("break", (IRubyObject)bj.getValue(), "unexpected break");
        } catch (JumpException.RedoJump rj) {
            throw newLocalJumpError("redo", (IRubyObject)rj.getValue(), "unexpected redo");
        }
    }
    
    /**
     * Parse and execute the specified script 
     * This differs from the other methods in that it accepts a string-based script and
     * parses and runs it as though it were loaded at a command-line. This is the preferred
     * way to start up a new script when calling directly into the Ruby object (which is
     * generally *dis*couraged.
     * 
     * @param script The contents of the script to run as a normal, root script
     * @return The last value of the script
     */
    public IRubyObject executeScript(String script, String filename) {
        byte[] bytes;
        
        try {
            bytes = script.getBytes(KCode.NONE.getKCode());
        } catch (UnsupportedEncodingException e) {
            bytes = script.getBytes();
        }

        Node node = parseInline(new ByteArrayInputStream(bytes), filename, null);
        ThreadContext context = getCurrentContext();
        
        String oldFile = context.getFile();
        int oldLine = context.getLine();
        try {
            context.setFile(node.getPosition().getFile());
            context.setLine(node.getPosition().getStartLine());
            return runNormally(node, false);
        } finally {
            context.setFile(oldFile);
            context.setLine(oldLine);
        }
    }
    
    /**
     * Run the script contained in the specified input stream, using the
     * specified filename as the name of the script being executed. The stream
     * will be read fully before being parsed and executed. The given filename
     * will be used for the ruby $PROGRAM_NAME and $0 global variables in this
     * runtime.
     * 
     * This method is intended to be called once per runtime, generally from
     * Main or from main-like top-level entry points.
     * 
     * As part of executing the script loaded from the input stream, various
     * RubyInstanceConfig properties will be used to determine whether to
     * compile the script before execution or run with various wrappers (for
     * looping, printing, and so on, see jruby -help).
     * 
     * @param inputStream The InputStream from which to read the script contents
     * @param filename The filename to use when parsing, and for $PROGRAM_NAME
     * and $0 ruby global variables.
     */
    public void runFromMain(InputStream inputStream, String filename) {
        IAccessor d = new ValueAccessor(newString(filename));
        getGlobalVariables().define("$PROGRAM_NAME", d);
        getGlobalVariables().define("$0", d);

        for (Iterator i = config.getOptionGlobals().entrySet().iterator(); i.hasNext();) {
            Map.Entry entry = (Map.Entry) i.next();
            Object value = entry.getValue();
            IRubyObject varvalue;
            if (value != null) {
                varvalue = newString(value.toString());
            } else {
                varvalue = getTrue();
            }
            getGlobalVariables().set("$" + entry.getKey().toString(), varvalue);
        }

        
        if(config.isYARVEnabled()) {
            if (config.isShowBytecode()) System.err.print("error: bytecode printing only works with JVM bytecode");
            new YARVCompiledRunner(this, inputStream, filename).run();
        } else if(config.isRubiniusEnabled()) {
            if (config.isShowBytecode()) System.err.print("error: bytecode printing only works with JVM bytecode");
            new RubiniusRunner(this, inputStream, filename).run();
        } else {
            Node scriptNode = parseFromMain(inputStream, filename);
            ThreadContext context = getCurrentContext();

            String oldFile = context.getFile();
            int oldLine = context.getLine();
            try {
                context.setFile(scriptNode.getPosition().getFile());
                context.setLine(scriptNode.getPosition().getStartLine());

                if (config.isAssumePrinting() || config.isAssumeLoop()) {
                    runWithGetsLoop(scriptNode, config.isAssumePrinting(), config.isProcessLineEnds(),
                            config.isSplit(), config.isYARVCompileEnabled());
                } else {
                    runNormally(scriptNode, config.isYARVCompileEnabled());
                }
            } finally {
                context.setFile(oldFile);
                context.setLine(oldLine);
            }
        }
    }

    /**
     * Parse the script contained in the given input stream, using the given
     * filename as the name of the script, and return the root Node. This
     * is used to verify that the script syntax is valid, for jruby -c. The
     * current scope (generally the top-level scope) is used as the parent
     * scope for parsing.
     * 
     * @param inputStream The input stream from which to read the script
     * @param filename The filename to use for parsing
     * @returns The root node of the parsed script
     */
    public Node parseFromMain(InputStream inputStream, String filename) {
        if (config.isInlineScript()) {
            return parseInline(inputStream, filename, getCurrentContext().getCurrentScope());
        } else {
            return parseFile(inputStream, filename, getCurrentContext().getCurrentScope());
        }
    }
    
    /**
     * Run the given script with a "while gets; end" loop wrapped around it.
     * This is primarily used for the -n command-line flag, to allow writing
     * a short script that processes input lines using the specified code.
     * 
     * @param scriptNode The root node of the script to execute
     * @param printing Whether $_ should be printed after each loop (as in the
     * -p command-line flag)
     * @param processLineEnds Whether line endings should be processed by
     * setting $\ to $/ and <code>chop!</code>ing every line read
     * @param split Whether to split each line read using <code>String#split</code>
     * @param yarvCompile Whether to compile the target script to YARV (Ruby 1.9)
     * bytecode before executing.
     * @return The result of executing the specified script
     */
    public IRubyObject runWithGetsLoop(Node scriptNode, boolean printing, boolean processLineEnds, boolean split, boolean yarvCompile) {
        ThreadContext context = getCurrentContext();
        
        Script script = null;
        YARVCompiledRunner runner = null;
        boolean compile = getInstanceConfig().getCompileMode().shouldPrecompileCLI();
        if (compile || !yarvCompile) {
            script = tryCompile(scriptNode);
            if (compile && script == null) {
                // terminate; tryCompile will have printed out an error and we're done
                return getNil();
            }
        } else if (yarvCompile) {
            runner = tryCompileYarv(scriptNode);
        }
        
        if (processLineEnds) {
            getGlobalVariables().set("$\\", getGlobalVariables().get("$/"));
        }
        
        while (RubyKernel.gets(context, getTopSelf(), IRubyObject.NULL_ARRAY).isTrue()) {
            loop: while (true) { // Used for the 'redo' command
                try {
                    if (processLineEnds) {
                        getGlobalVariables().get("$_").callMethod(context, "chop!");
                    }
                    
                    if (split) {
                        getGlobalVariables().set("$F", getGlobalVariables().get("$_").callMethod(context, "split"));
                    }
                    
                    if (script != null) {
                        runScript(script);
                    } else if (runner != null) {
                        runYarv(runner);
                    } else {
                        runInterpreter(scriptNode);
                    }
                    
                    if (printing) RubyKernel.print(context, getKernel(), new IRubyObject[] {getGlobalVariables().get("$_")});
                    break loop;
                } catch (JumpException.RedoJump rj) {
                    // do nothing, this iteration restarts
                } catch (JumpException.NextJump nj) {
                    // recheck condition
                    break loop;
                } catch (JumpException.BreakJump bj) {
                    // end loop
                    return (IRubyObject) bj.getValue();
                }
            }
        }
        
        return getNil();
    }
    
    /**
     * Run the specified script without any of the loop-processing wrapper
     * code.
     * 
     * @param scriptNode The root node of the script to be executed
     * @param yarvCompile Whether to compile the script to YARV (Ruby 1.9)
     * bytecode before execution
     * @return The result of executing the script
     */
    public IRubyObject runNormally(Node scriptNode, boolean yarvCompile) {
        Script script = null;
        YARVCompiledRunner runner = null;
        boolean compile = getInstanceConfig().getCompileMode().shouldPrecompileCLI();
        boolean forceCompile = getInstanceConfig().getCompileMode().shouldPrecompileAll();
        if (yarvCompile) {
            runner = tryCompileYarv(scriptNode);
        } else if (compile) {
            script = tryCompile(scriptNode);
            if (forceCompile && script == null) {
                System.err.println("Error, could not compile; pass -J-Djruby.jit.logging.verbose=true for more details");
                return getNil();
            }
        }
        
        if (script != null) {
            if (config.isShowBytecode()) {
                return nilObject;
            } else {
                return runScript(script);
            }
        } else if (runner != null) {
            return runYarv(runner);
        } else {
            if (config.isShowBytecode()) System.err.print("error: bytecode printing only works with JVM bytecode");
            return runInterpreter(scriptNode);
        }
    }
    
    private Script tryCompile(Node node) {
        return tryCompile(node, new JRubyClassLoader(getJRubyClassLoader()));
    }
    
    private Script tryCompile(Node node, JRubyClassLoader classLoader) {
        Script script = null;
        try {
            String filename = node.getPosition().getFile();
            String classname = JavaNameMangler.mangledFilenameForStartupClasspath(filename);

            ASTInspector inspector = new ASTInspector();
            inspector.inspect(node);

            StandardASMCompiler asmCompiler = new StandardASMCompiler(classname, filename);
            ASTCompiler compiler = new ASTCompiler();
            if (config.isShowBytecode()) {
                compiler.compileRoot(node, asmCompiler, inspector, false, false);
                asmCompiler.dumpClass(System.out);
            } else {
                compiler.compileRoot(node, asmCompiler, inspector, true, false);
            }
            script = (Script)asmCompiler.loadClass(classLoader).newInstance();

            if (config.isJitLogging()) {
                System.err.println("compiled: " + node.getPosition().getFile());
            }
        } catch (NotCompilableException nce) {
            if (config.isJitLoggingVerbose()) {
                System.err.println("Error -- Not compileable: " + nce.getMessage());
                nce.printStackTrace();
            }
        } catch (ClassNotFoundException e) {
            if (config.isJitLoggingVerbose()) {
                System.err.println("Error -- Not compileable: " + e.getMessage());
                e.printStackTrace();
            }
        } catch (InstantiationException e) {
            if (config.isJitLoggingVerbose()) {
                System.err.println("Error -- Not compileable: " + e.getMessage());
                e.printStackTrace();
            }
        } catch (IllegalAccessException e) {
            if (config.isJitLoggingVerbose()) {
                System.err.println("Error -- Not compileable: " + e.getMessage());
                e.printStackTrace();
            }
        } catch (Throwable t) {
            if (config.isJitLoggingVerbose()) {
                System.err.println("could not compile: " + node.getPosition().getFile() + " because of: \"" + t.getMessage() + "\"");
                t.printStackTrace();
            }
        }
        
        return script;
    }
    
    private YARVCompiledRunner tryCompileYarv(Node node) {
        try {
            StandardYARVCompiler compiler = new StandardYARVCompiler(this);
            ASTCompiler.getYARVCompiler().compile(node, compiler);
            org.jruby.lexer.yacc.ISourcePosition p = node.getPosition();
            if(p == null && node instanceof org.jruby.ast.RootNode) {
                p = ((org.jruby.ast.RootNode)node).getBodyNode().getPosition();
            }
            return new YARVCompiledRunner(this,compiler.getInstructionSequence("<main>",p.getFile(),"toplevel"));
        } catch (NotCompilableException nce) {
            System.err.println("Error -- Not compileable: " + nce.getMessage());
            return null;
        } catch (JumpException.ReturnJump rj) {
            return null;
        }
    }
    
    private IRubyObject runScript(Script script) {
        ThreadContext context = getCurrentContext();
        
        try {
            return script.load(context, context.getFrameSelf(), IRubyObject.NULL_ARRAY, Block.NULL_BLOCK);
        } catch (JumpException.ReturnJump rj) {
            return (IRubyObject) rj.getValue();
        }
    }
    
    private IRubyObject runYarv(YARVCompiledRunner runner) {
        try {
            return runner.run();
        } catch (JumpException.ReturnJump rj) {
            return (IRubyObject) rj.getValue();
        }
    }
    
    private IRubyObject runInterpreter(Node scriptNode) {
        ThreadContext context = getCurrentContext();
        
        assert scriptNode != null : "scriptNode is not null";
        
        try {
            return scriptNode.interpret(this, context, getTopSelf(), Block.NULL_BLOCK);
        } catch (JumpException.ReturnJump rj) {
            return (IRubyObject) rj.getValue();
        }
    }
    
    public BeanManager getBeanManager() {
        return beanManager;
    }
    
    public JITCompiler getJITCompiler() {
        return jitCompiler;
    }

    /**
     * @deprecated use #newInstance()
     */
    public static Ruby getDefaultInstance() {
        return newInstance();
    }
    
    @Deprecated
    public static Ruby getCurrentInstance() {
        return null;
    }
    
    @Deprecated
    public static void setCurrentInstance(Ruby runtime) {
    }
    
    public int allocSymbolId() {
        return symbolLastId.incrementAndGet();
    }
    public int allocModuleId() {
        return moduleLastId.incrementAndGet();
    }

    /**
     * Retrieve the module with the given name from the Object namespace.
     * 
     * @param name The name of the module
     * @return The module or null if not found
     */
    public RubyModule getModule(String name) {
        return (RubyModule) objectClass.getConstantAt(name);
    }

    /**
     * Retrieve the module with the given name from the Object namespace. The
     * module name must be an interned string, but this method will be faster
     * than the non-interned version.
     * 
     * @param internedName The name of the module; <em>must</em> be an interned String
     * @return The module or null if not found
     */
    public RubyModule fastGetModule(String internedName) {
        return (RubyModule) objectClass.fastGetConstantAt(internedName);
    }

    /** 
     * Retrieve the class with the given name from the Object namespace.
     *
     * @param name The name of the class
     * @return The class
     */
    public RubyClass getClass(String name) {
        return objectClass.getClass(name);
    }

    /**
     * Retrieve the class with the given name from the Object namespace. The
     * module name must be an interned string, but this method will be faster
     * than the non-interned version.
     * 
     * @param internedName the name of the class; <em>must</em> be an interned String!
     * @return
     */
    public RubyClass fastGetClass(String internedName) {
        return objectClass.fastGetClass(internedName);
    }

    /** 
     * Define a new class under the Object namespace. Roughly equivalent to
     * rb_define_class in MRI.
     *
     * @param name The name for the new class
     * @param superClass The super class for the new class
     * @param allocator An ObjectAllocator instance that can construct
     * instances of the new class.
     * @return The new class
     */
    public RubyClass defineClass(String name, RubyClass superClass, ObjectAllocator allocator) {
        return defineClassUnder(name, superClass, allocator, objectClass);
    }

    /** 
     * A variation of defineClass that allows passing in an array of subplementary
     * call sites for improving dynamic invocation performance.
     *
     * @param name The name for the new class
     * @param superClass The super class for the new class
     * @param allocator An ObjectAllocator instance that can construct
     * instances of the new class.
     * @return The new class
     */
    public RubyClass defineClass(String name, RubyClass superClass, ObjectAllocator allocator, CallSite[] callSites) {
        return defineClassUnder(name, superClass, allocator, objectClass, callSites);
    }

    /**
     * Define a new class with the given name under the given module or class
     * namespace. Roughly equivalent to rb_define_class_under in MRI.
     * 
     * If the name specified is already bound, its value will be returned if:
     * * It is a class
     * * No new superclass is being defined
     *
     * @param name The name for the new class
     * @param superClass The super class for the new class
     * @param allocator An ObjectAllocator instance that can construct
     * instances of the new class.
     * @param parent The namespace under which to define the new class
     * @return The new class
     */
    public RubyClass defineClassUnder(String name, RubyClass superClass, ObjectAllocator allocator, RubyModule parent) {
        return defineClassUnder(name, superClass, allocator, parent, null);
    }

    /**
     * A variation of defineClassUnder that allows passing in an array of
     * supplementary call sites to improve dynamic invocation.
     *
     * @param name The name for the new class
     * @param superClass The super class for the new class
     * @param allocator An ObjectAllocator instance that can construct
     * instances of the new class.
     * @param parent The namespace under which to define the new class
     * @param callSites The array of call sites to add
     * @return The new class
     */
    public RubyClass defineClassUnder(String name, RubyClass superClass, ObjectAllocator allocator, RubyModule parent, CallSite[] callSites) {
        IRubyObject classObj = parent.getConstantAt(name);

        if (classObj != null) {
            if (!(classObj instanceof RubyClass)) throw newTypeError(name + " is not a class");
            RubyClass klazz = (RubyClass)classObj;
            if (klazz.getSuperClass().getRealClass() != superClass) {
                throw newNameError(name + " is already defined", name);
            }
            // If we define a class in Ruby, but later want to allow it to be defined in Java,
            // the allocator needs to be updated
            if (klazz.getAllocator() != allocator) {
                klazz.setAllocator(allocator);
            }
            return klazz;
        }
        
        boolean parentIsObject = parent == objectClass;

        if (superClass == null) {
            String className = parentIsObject ? name : parent.getName() + "::" + name;  
            warnings.warn(ID.NO_SUPER_CLASS, "no super class for `" + className + "', Object assumed", className);
            
            superClass = objectClass;
        }

        return RubyClass.newClass(this, superClass, name, allocator, parent, !parentIsObject, callSites);
    }

    /** 
     * Define a new module under the Object namespace. Roughly equivalent to
     * rb_define_module in MRI.
     * 
     * @param name The name of the new module
     * @returns The new module
     */
    public RubyModule defineModule(String name) {
        return defineModuleUnder(name, objectClass);
    }

    /**
     * Define a new module with the given name under the given module or
     * class namespace. Roughly equivalent to rb_define_module_under in MRI.
     * 
     * @param name The name of the new module
     * @param parent The class or module namespace under which to define the
     * module
     * @returns The new module
     */
    public RubyModule defineModuleUnder(String name, RubyModule parent) {
        IRubyObject moduleObj = parent.getConstantAt(name);
        
        boolean parentIsObject = parent == objectClass;

        if (moduleObj != null ) {
            if (moduleObj.isModule()) return (RubyModule)moduleObj;
            
            if (parentIsObject) {
                throw newTypeError(moduleObj.getMetaClass().getName() + " is not a module");
            } else {
                throw newTypeError(parent.getName() + "::" + moduleObj.getMetaClass().getName() + " is not a module");
            }
        }

        return RubyModule.newModule(this, name, parent, !parentIsObject);
    }

    /**
     * From Object, retrieve the named module. If it doesn't exist a
     * new module is created.
     * 
     * @param name The name of the module
     * @returns The existing or new module
     */
    public RubyModule getOrCreateModule(String name) {
        IRubyObject module = objectClass.getConstantAt(name);
        if (module == null) {
            module = defineModule(name);
        } else if (getSafeLevel() >= 4) {
            throw newSecurityError("Extending module prohibited.");
        } else if (!module.isModule()) {
            throw newTypeError(name + " is not a Module");
        }

        return (RubyModule) module;
    }


    /** 
     * Retrieve the current safe level.
     * 
     * @see org.jruby.Ruby#setSaveLevel
     */
    public int getSafeLevel() {
        return this.safeLevel;
    }


    /** 
     * Set the current safe level:
     * 
     * 0 - strings from streams/environment/ARGV are tainted (default)
     * 1 - no dangerous operation by tainted value
     * 2 - process/file operations prohibited
     * 3 - all generated objects are tainted
     * 4 - no global (non-tainted) variable modification/no direct output
     * 
     * The safe level is set using $SAFE in Ruby code. It is not particularly
     * well supported in JRuby.
    */
    public void setSafeLevel(int safeLevel) {
        this.safeLevel = safeLevel;
    }

    public KCode getKCode() {
        return kcode;
    }

    public void setKCode(KCode kcode) {
        this.kcode = kcode;
    }

    public void secure(int level) {
        if (level <= safeLevel) {
            throw newSecurityError("Insecure operation '" + getCurrentContext().getFrameName() + "' at level " + safeLevel);
        }
    }

    // FIXME moved this here to get what's obviously a utility method out of IRubyObject.
    // perhaps security methods should find their own centralized home at some point.
    public void checkSafeString(IRubyObject object) {
        if (getSafeLevel() > 0 && object.isTaint()) {
            ThreadContext tc = getCurrentContext();
            if (tc.getFrameName() != null) {
                throw newSecurityError("Insecure operation - " + tc.getFrameName());
            }
            throw newSecurityError("Insecure operation: -r");
        }
        secure(4);
        if (!(object instanceof RubyString)) {
            throw newTypeError(
                "wrong argument type " + object.getMetaClass().getName() + " (expected String)");
        }
    }

    /** rb_define_global_const
     *
     */
    public void defineGlobalConstant(String name, IRubyObject value) {
        objectClass.defineConstant(name, value);
    }

    public boolean isClassDefined(String name) {
        return getModule(name) != null;
    }
    
    /**
     * A ThreadFactory for when we're using pooled threads; we want to create
     * the threads with daemon = true so they don't keep us from shutting down.
     */
    public static class DaemonThreadFactory implements ThreadFactory {
        public Thread newThread(Runnable runnable) {
            Thread thread = new Thread(runnable);
            thread.setDaemon(true);
            
            return thread;
        }
    }

    /** 
     * This method is called immediately after constructing the Ruby instance.
     * The main thread is prepared for execution, all core classes and libraries
     * are initialized, and any libraries required on the command line are
     * loaded.
     */
    private void init() {
        // Get the main threadcontext (gets constructed for us)
        ThreadContext tc = getCurrentContext();

        safeLevel = config.getSafeLevel();
        
        // Construct key services
        loadService = config.createLoadService(this);
        posix = POSIXFactory.getPOSIX(new JRubyPOSIXHandler(this), RubyInstanceConfig.nativeEnabled);
        javaSupport = new JavaSupport(this);
        
        if (RubyInstanceConfig.POOLING_ENABLED) {
            Executors.newCachedThreadPool();
            executor = new ThreadPoolExecutor(
                    RubyInstanceConfig.POOL_MIN,
                    RubyInstanceConfig.POOL_MAX,
                    RubyInstanceConfig.POOL_TTL,
                    TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(),
                    new DaemonThreadFactory());
        }
        
        // initialize the root of the class hierarchy completely
        initRoot(tc);

        // Construct the top-level execution frame and scope for the main thread
        tc.prepareTopLevel(objectClass, topSelf);

        // Initialize all the core classes
        bootstrap();
        
        // Create global constants and variables
        RubyGlobal.createGlobals(tc, this);

        // Prepare LoadService and load path
        getLoadService().init(config.loadPaths());

        // initialize builtin libraries
        initBuiltins();
        
        // Require in all libraries specified on command line
        for (String scriptName : config.requiredLibraries()) {
            RubyKernel.require(getTopSelf(), newString(scriptName), Block.NULL_BLOCK);
        }
    }

    private void bootstrap() {
        initCore();
        initExceptions();
    }

    private void initRoot(ThreadContext context) {
        // Bootstrap the top of the hierarchy
        objectClass = RubyClass.createBootstrapClass(this, "Object", null, RubyObject.OBJECT_ALLOCATOR);
        moduleClass = RubyClass.createBootstrapClass(this, "Module", objectClass, RubyModule.MODULE_ALLOCATOR);
        classClass = RubyClass.createBootstrapClass(this, "Class", moduleClass, RubyClass.CLASS_ALLOCATOR);

        objectClass.setMetaClass(classClass);
        moduleClass.setMetaClass(classClass);
        classClass.setMetaClass(classClass);

        RubyClass metaClass;
        metaClass = objectClass.makeMetaClass(classClass);
        metaClass = moduleClass.makeMetaClass(metaClass);
        metaClass = classClass.makeMetaClass(metaClass);

        RubyObject.createObjectClass(this, objectClass);
        RubyModule.createModuleClass(this, moduleClass);
        RubyClass.createClassClass(this, classClass);
        
        // set constants now that they're initialized
        objectClass.setConstant("Object", objectClass);
        objectClass.setConstant("Class", classClass);
        objectClass.setConstant("Module", moduleClass);

        // Initialize Kernel and include into Object
        RubyKernel.createKernelModule(this);
        objectClass.includeModule(kernelModule);
        
        // Initialize the "dummy" class used as a marker
        dummyClass = new RubyClass(this);
        dummyClass.freeze(context);

        // Object is ready, create top self
        topSelf = TopSelfFactory.createTopSelf(this);
    }

    private void initCore() {
        // Pre-create all the core classes potentially referenced during startup
        RubyNil.createNilClass(this);
        RubyBoolean.createFalseClass(this);
        RubyBoolean.createTrueClass(this);

        nilObject = new RubyNil(this);
        falseObject = new RubyBoolean(this, false);
        trueObject = new RubyBoolean(this, true);

        RubyComparable.createComparable(this);
        RubyEnumerable.createEnumerableModule(this);
        RubyString.createStringClass(this);
        RubySymbol.createSymbolClass(this);

        if (profile.allowClass("ThreadGroup")) {
            RubyThreadGroup.createThreadGroupClass(this);
        }
        if (profile.allowClass("Thread")) {
            RubyThread.createThreadClass(this);
        }
        if (profile.allowClass("Exception")) {
            RubyException.createExceptionClass(this);
        }
        if (profile.allowModule("Precision")) {
            RubyPrecision.createPrecisionModule(this);
        }
        if (profile.allowClass("Numeric")) {
            RubyNumeric.createNumericClass(this);
        }
        if (profile.allowClass("Integer")) {
            RubyInteger.createIntegerClass(this);
        }
        if (profile.allowClass("Fixnum")) {
            RubyFixnum.createFixnumClass(this);
        }

        if (config.getCompatVersion() == CompatVersion.RUBY1_9) {
            if (profile.allowClass("Complex")) {
                RubyComplex.createComplexClass(this);
            }
            if (profile.allowClass("Rational")) {
                RubyRational.createRationalClass(this);
            }
        }

        if (profile.allowClass("Hash")) {
            RubyHash.createHashClass(this);
        }
        if (profile.allowClass("Array")) {
            RubyArray.createArrayClass(this);
        }
        if (profile.allowClass("Float")) {
            RubyFloat.createFloatClass(this);
        }
        if (profile.allowClass("Bignum")) {
            RubyBignum.createBignumClass(this);
        }
        ioClass = RubyIO.createIOClass(this);

        if (profile.allowClass("Struct")) {
            RubyStruct.createStructClass(this);
        }
        if (profile.allowClass("Tms")) {
            tmsStruct = RubyStruct.newInstance(structClass, new IRubyObject[]{newString("Tms"), newSymbol("utime"), newSymbol("stime"), newSymbol("cutime"), newSymbol("cstime")}, Block.NULL_BLOCK);
        }

        if (profile.allowClass("Binding")) {
            RubyBinding.createBindingClass(this);
        }
        // Math depends on all numeric types
        if (profile.allowModule("Math")) {
            RubyMath.createMathModule(this);
        }
        if (profile.allowClass("Regexp")) {
            RubyRegexp.createRegexpClass(this);
        }
        if (profile.allowClass("Range")) {
            RubyRange.createRangeClass(this);
        }
        if (profile.allowModule("ObjectSpace")) {
            RubyObjectSpace.createObjectSpaceModule(this);
        }
        if (profile.allowModule("GC")) {
            RubyGC.createGCModule(this);
        }
        if (profile.allowClass("Proc")) {
            RubyProc.createProcClass(this);
        }
        if (profile.allowClass("Method")) {
            RubyMethod.createMethodClass(this);
        }
        if (profile.allowClass("MatchData")) {
            RubyMatchData.createMatchDataClass(this);
        }
        if (profile.allowModule("Marshal")) {
            RubyMarshal.createMarshalModule(this);
        }
        if (profile.allowClass("Dir")) {
            RubyDir.createDirClass(this);
        }
        if (profile.allowModule("FileTest")) {
            RubyFileTest.createFileTestModule(this);
        }
        // depends on IO, FileTest
        if (profile.allowClass("File")) {
            RubyFile.createFileClass(this);
        }
        if (profile.allowClass("File::Stat")) {
            RubyFileStat.createFileStatClass(this);
        }
        if (profile.allowModule("Process")) {
            RubyProcess.createProcessModule(this);
        }
        if (profile.allowClass("Time")) {
            RubyTime.createTimeClass(this);
        }
        if (profile.allowClass("UnboundMethod")) {
            RubyUnboundMethod.defineUnboundMethodClass(this);
        }
        if (profile.allowClass("Data")) {
            defineClass("Data", objectClass, objectClass.getAllocator());
        }
        if (!isSecurityRestricted()) {
            // Signal uses sun.misc.* classes, this is not allowed
            // in the security-sensitive environments
            if (profile.allowModule("Signal")) {
                RubySignal.createSignal(this);
            }
        }
        if (profile.allowClass("Continuation")) {
            RubyContinuation.createContinuation(this);
        }
    }

    private void initExceptions() {
        standardError = defineClassIfAllowed("StandardError", exceptionClass);
        runtimeError = defineClassIfAllowed("RuntimeError", standardError);
        ioError = defineClassIfAllowed("IOError", standardError);
        scriptError = defineClassIfAllowed("ScriptError", exceptionClass);
        rangeError = defineClassIfAllowed("RangeError", standardError);
        signalException = defineClassIfAllowed("SignalException", exceptionClass);
        
        if (profile.allowClass("NameError")) {
            nameError = RubyNameError.createNameErrorClass(this, standardError);
            nameErrorMessage = RubyNameError.createNameErrorMessageClass(this, nameError);            
        }
        if (profile.allowClass("NoMethodError")) {
            noMethodError = RubyNoMethodError.createNoMethodErrorClass(this, nameError);
        }
        if (profile.allowClass("SystemExit")) {
            systemExit = RubySystemExit.createSystemExitClass(this, exceptionClass);
        }
        if (profile.allowClass("LocalJumpError")) {
            localJumpError = RubyLocalJumpError.createLocalJumpErrorClass(this, standardError);
        }
        if (profile.allowClass("NativeException")) {
            nativeException = NativeException.createClass(this, runtimeError);
        }
        if (profile.allowClass("SystemCallError")) {
            systemCallError = RubySystemCallError.createSystemCallErrorClass(this, standardError);
        }

        fatal = defineClassIfAllowed("Fatal", exceptionClass);
        interrupt = defineClassIfAllowed("Interrupt", signalException);
        typeError = defineClassIfAllowed("TypeError", standardError);
        argumentError = defineClassIfAllowed("ArgumentError", standardError);
        indexError = defineClassIfAllowed("IndexError", standardError);
        syntaxError = defineClassIfAllowed("SyntaxError", scriptError);
        loadError = defineClassIfAllowed("LoadError", scriptError);
        notImplementedError = defineClassIfAllowed("NotImplementedError", scriptError);
        securityError = defineClassIfAllowed("SecurityError", standardError);
        noMemoryError = defineClassIfAllowed("NoMemoryError", exceptionClass);
        regexpError = defineClassIfAllowed("RegexpError", standardError);
        eofError = defineClassIfAllowed("EOFError", ioError);
        threadError = defineClassIfAllowed("ThreadError", standardError);
        concurrencyError = defineClassIfAllowed("ConcurrencyError", threadError);
        systemStackError = defineClassIfAllowed("SystemStackError", standardError);
        zeroDivisionError = defineClassIfAllowed("ZeroDivisionError", standardError);
        floatDomainError  = defineClassIfAllowed("FloatDomainError", rangeError);

        initErrno();
    }
    
    private RubyClass defineClassIfAllowed(String name, RubyClass superClass) {
	// TODO: should probably apply the null object pattern for a
	// non-allowed class, rather than null
        if (superClass != null && profile.allowClass(name)) {
            return defineClass(name, superClass, superClass.getAllocator());
        }
        return null;
    }

    private Map<Integer, RubyClass> errnos = new HashMap<Integer, RubyClass>();

    public RubyClass getErrno(int n) {
        return errnos.get(n);
    }

    /**
     * Create module Errno's Variables.  We have this method since Errno does not have it's
     * own java class.
     */
    private void initErrno() {
        if (profile.allowModule("Errno")) {
            errnoModule = defineModule("Errno");

            Field[] fields = IErrno.class.getFields();

            for (int i = 0; i < fields.length; i++) {
                try {
                    createSysErr(fields[i].getInt(IErrno.class), fields[i].getName());
                } catch (IllegalAccessException e) {
                    throw new RuntimeException("Someone defined a non-public constant in IErrno.java", e);
                }
            }
        }
    }

    /**
     * Creates a system error.
     * @param i the error code (will probably use a java exception instead)
     * @param name of the error to define.
     **/
    private void createSysErr(int i, String name) {
        if(profile.allowClass(name)) {
            RubyClass errno = getErrno().defineClassUnder(name, systemCallError, systemCallError.getAllocator());
            errnos.put(i, errno);
            errno.defineConstant("Errno", newFixnum(i));
        }
    }

    private void initBuiltins() {
        addLazyBuiltin("java.rb", "java", "org.jruby.javasupport.Java");
        addLazyBuiltin("jruby.rb", "jruby", "org.jruby.libraries.JRubyLibrary");
        
        addLazyBuiltin("minijava.rb", "minijava", "org.jruby.java.MiniJava");
        
        addLazyBuiltin("jruby/ext.rb", "jruby/ext", "org.jruby.RubyJRuby$ExtLibrary");
        addLazyBuiltin("jruby/type.rb", "jruby/type", "org.jruby.RubyJRuby$TypeLibrary");
        addLazyBuiltin("iconv.so", "iconv", "org.jruby.libraries.IConvLibrary");
        addLazyBuiltin("nkf.so", "nkf", "org.jruby.libraries.NKFLibrary");
        addLazyBuiltin("stringio.so", "stringio", "org.jruby.libraries.StringIOLibrary");
        addLazyBuiltin("strscan.so", "strscan", "org.jruby.libraries.StringScannerLibrary");
        addLazyBuiltin("zlib.so", "zlib", "org.jruby.libraries.ZlibLibrary");
        addLazyBuiltin("yaml_internal.rb", "yaml_internal", "org.jruby.libraries.YamlLibrary");
        addLazyBuiltin("enumerator.so", "enumerator", "org.jruby.libraries.EnumeratorLibrary");
        addLazyBuiltin("generator_internal.rb", "generator_internal", "org.jruby.ext.Generator$Service");
        addLazyBuiltin("readline.so", "readline", "org.jruby.ext.Readline$Service");
        addLazyBuiltin("thread.so", "thread", "org.jruby.libraries.ThreadLibrary");
        addLazyBuiltin("digest.so", "digest", "org.jruby.libraries.DigestLibrary");
        addLazyBuiltin("digest.rb", "digest", "org.jruby.libraries.DigestLibrary");
        addLazyBuiltin("digest/md5.so", "digest/md5", "org.jruby.libraries.DigestLibrary$MD5");
        addLazyBuiltin("digest/rmd160.so", "digest/rmd160", "org.jruby.libraries.DigestLibrary$RMD160");
        addLazyBuiltin("digest/sha1.so", "digest/sha1", "org.jruby.libraries.DigestLibrary$SHA1");
        addLazyBuiltin("digest/sha2.so", "digest/sha2", "org.jruby.libraries.DigestLibrary$SHA2");
        addLazyBuiltin("bigdecimal.so", "bigdecimal", "org.jruby.libraries.BigDecimalLibrary");
        addLazyBuiltin("io/wait.so", "io/wait", "org.jruby.libraries.IOWaitLibrary");
        addLazyBuiltin("etc.so", "etc", "org.jruby.libraries.EtcLibrary");
        addLazyBuiltin("weakref.rb", "weakref", "org.jruby.ext.WeakRef$WeakRefLibrary");
        addLazyBuiltin("socket.so", "socket", "org.jruby.ext.socket.RubySocket$Service");
        addLazyBuiltin("rbconfig.rb", "rbconfig", "org.jruby.libraries.RbConfigLibrary");
        addLazyBuiltin("jruby/serialization.rb", "serialization", "org.jruby.libraries.JRubySerializationLibrary");
        addLazyBuiltin("ffi.so", "ffi", "org.jruby.ext.ffi.Factory$Service");
        if(RubyInstanceConfig.NATIVE_NET_PROTOCOL) {
            addLazyBuiltin("net/protocol.rb", "net/protocol", "org.jruby.libraries.NetProtocolBufferedIOLibrary");
        }
        
        if (config.getCompatVersion() == CompatVersion.RUBY1_9) {
            addLazyBuiltin("fiber.so", "fiber", "org.jruby.libraries.FiberLibrary");
        }
        
        addBuiltinIfAllowed("openssl.so", new Library() {
            public void load(Ruby runtime, boolean wrap) throws IOException {
                runtime.getLoadService().require("jruby/openssl/stub");
            }
        });
        
        String[] builtins = {"fcntl", "yaml", "yaml/syck", "jsignal" };
        for (String library : builtins) {
            addBuiltinIfAllowed(library + ".rb", new BuiltinScript(library));
        }

        getLoadService().require("builtin/core_ext/symbol");
        
        RubyKernel.autoload(topSelf, newSymbol("Java"), newString("java"));

        getLoadService().require("enumerator");
    }

    private void addLazyBuiltin(String name, String shortName, String className) {
        addBuiltinIfAllowed(name, new LateLoadingLibrary(shortName, className, getJRubyClassLoader()));
    }

    private void addBuiltinIfAllowed(String name, Library lib) {
        if(profile.allowBuiltin(name)) {
            loadService.addBuiltinLibrary(name,lib);
        }
    }

    Object getRespondToMethod() {
        return respondToMethod;
    }

    void setRespondToMethod(Object rtm) {
        this.respondToMethod = rtm;
    }

    public Object getObjectToYamlMethod() {
        return objectToYamlMethod;
    }

    void setObjectToYamlMethod(Object otym) {
        this.objectToYamlMethod = otym;
    }

    /**
     * Retrieve mappings of cached methods to where they have been cached.  When a cached
     * method needs to be invalidated this map can be used to remove all places it has been
     * cached.
     *
     * @return the mappings of where cached methods have been stored
     */
    public CacheMap getCacheMap() {
        return cacheMap;
    }

    /** Getter for property rubyTopSelf.
     * @return Value of property rubyTopSelf.
     */
    public IRubyObject getTopSelf() {
        return topSelf;
    }

    public void setCurrentDirectory(String dir) {
        currentDirectory = dir;
    }

    public String getCurrentDirectory() {
        return currentDirectory;
    }
    
    public RubyModule getEtc() {
        return etcModule;
    }
    
    public void setEtc(RubyModule etcModule) {
        this.etcModule = etcModule;
    }

    public RubyClass getObject() {
        return objectClass;
    }

    public RubyClass getModule() {
        return moduleClass;
    }

    public RubyClass getClassClass() {
        return classClass;
    }
    
    public RubyModule getKernel() {
        return kernelModule;
    }
    void setKernel(RubyModule kernelModule) {
        this.kernelModule = kernelModule;
    }
    
    public RubyClass getDummy() {
        return dummyClass;
    }

    public RubyModule getComparable() {
        return comparableModule;
    }
    void setComparable(RubyModule comparableModule) {
        this.comparableModule = comparableModule;
    }    

    public RubyClass getNumeric() {
        return numericClass;
    }
    void setNumeric(RubyClass numericClass) {
        this.numericClass = numericClass;
    }    

    public RubyClass getFloat() {
        return floatClass;
    }
    void setFloat(RubyClass floatClass) {
        this.floatClass = floatClass;
    }
    
    public RubyClass getInteger() {
        return integerClass;
    }
    void setInteger(RubyClass integerClass) {
        this.integerClass = integerClass;
    }    
    
    public RubyClass getFixnum() {
        return fixnumClass;
    }
    void setFixnum(RubyClass fixnumClass) {
        this.fixnumClass = fixnumClass;
    }

    public RubyClass getComplex() {
        return complexClass;
    }
    void setComplex(RubyClass complexClass) {
        this.complexClass = complexClass;
    }

    public RubyClass getRational() {
        return rationalClass;
    }
    void setRational(RubyClass rationalClass) {
        this.rationalClass = rationalClass;
    }

    public RubyModule getEnumerable() {
        return enumerableModule;
    }
    void setEnumerable(RubyModule enumerableModule) {
        this.enumerableModule = enumerableModule;
    }

    public RubyModule getEnumerator() {
        return enumeratorClass;
    }
    void setEnumerator(RubyClass enumeratorClass) {
        this.enumeratorClass = enumeratorClass;
    }  

    public RubyClass getString() {
        return stringClass;
    }    
    void setString(RubyClass stringClass) {
        this.stringClass = stringClass;
    }    

    public RubyClass getSymbol() {
        return symbolClass;
    }
    void setSymbol(RubyClass symbolClass) {
        this.symbolClass = symbolClass;
    }   

    public RubyClass getArray() {
        return arrayClass;
    }    
    void setArray(RubyClass arrayClass) {
        this.arrayClass = arrayClass;
    }

    public RubyClass getHash() {
        return hashClass;
    }
    void setHash(RubyClass hashClass) {
        this.hashClass = hashClass;
    }

    public RubyClass getRange() {
        return rangeClass;
    }
    void setRange(RubyClass rangeClass) {
        this.rangeClass = rangeClass;
    }

    /** Returns the "true" instance from the instance pool.
     * @return The "true" instance.
     */
    public RubyBoolean getTrue() {
        return trueObject;
    }

    /** Returns the "false" instance from the instance pool.
     * @return The "false" instance.
     */
    public RubyBoolean getFalse() {
        return falseObject;
    }

    /** Returns the "nil" singleton instance.
     * @return "nil"
     */
    public IRubyObject getNil() {
        return nilObject;
    }

    public RubyClass getNilClass() {
        return nilClass;
    }
    void setNilClass(RubyClass nilClass) {
        this.nilClass = nilClass;
    }

    public RubyClass getTrueClass() {
        return trueClass;
    }
    void setTrueClass(RubyClass trueClass) {
        this.trueClass = trueClass;
    }

    public RubyClass getFalseClass() {
        return falseClass;
    }
    void setFalseClass(RubyClass falseClass) {
        this.falseClass = falseClass;
    }

    public RubyClass getProc() {
        return procClass;
    }
    void setProc(RubyClass procClass) {
        this.procClass = procClass;
    }

    public RubyClass getBinding() {
        return bindingClass;
    }
    void setBinding(RubyClass bindingClass) {
        this.bindingClass = bindingClass;
    }

    public RubyClass getMethod() {
        return methodClass;
    }
    void setMethod(RubyClass methodClass) {
        this.methodClass = methodClass;
    }    

    public RubyClass getUnboundMethod() {
        return unboundMethodClass;
    }
    void setUnboundMethod(RubyClass unboundMethodClass) {
        this.unboundMethodClass = unboundMethodClass;
    }    

    public RubyClass getMatchData() {
        return matchDataClass;
    }
    void setMatchData(RubyClass matchDataClass) {
        this.matchDataClass = matchDataClass;
    }    

    public RubyClass getRegexp() {
        return regexpClass;
    }
    void setRegexp(RubyClass regexpClass) {
        this.regexpClass = regexpClass;
    }    

    public RubyClass getTime() {
        return timeClass;
    }
    void setTime(RubyClass timeClass) {
        this.timeClass = timeClass;
    }    

    public RubyModule getMath() {
        return mathModule;
    }
    void setMath(RubyModule mathModule) {
        this.mathModule = mathModule;
    }    

    public RubyModule getMarshal() {
        return marshalModule;
    }
    void setMarshal(RubyModule marshalModule) {
        this.marshalModule = marshalModule;
    }    

    public RubyClass getBignum() {
        return bignumClass;
    }
    void setBignum(RubyClass bignumClass) {
        this.bignumClass = bignumClass;
    }    

    public RubyClass getDir() {
        return dirClass;
    }
    void setDir(RubyClass dirClass) {
        this.dirClass = dirClass;
    }    

    public RubyClass getFile() {
        return fileClass;
    }
    void setFile(RubyClass fileClass) {
        this.fileClass = fileClass;
    }    

    public RubyClass getFileStat() {
        return fileStatClass;
    }
    void setFileStat(RubyClass fileStatClass) {
        this.fileStatClass = fileStatClass;
    }    

    public RubyModule getFileTest() {
        return fileTestModule;
    }
    void setFileTest(RubyModule fileTestModule) {
        this.fileTestModule = fileTestModule;
    }
    
    public RubyClass getIO() {
        return ioClass;
    }
    void setIO(RubyClass ioClass) {
        this.ioClass = ioClass;
    }    

    public RubyClass getThread() {
        return threadClass;
    }
    void setThread(RubyClass threadClass) {
        this.threadClass = threadClass;
    }    

    public RubyClass getThreadGroup() {
        return threadGroupClass;
    }
    void setThreadGroup(RubyClass threadGroupClass) {
        this.threadGroupClass = threadGroupClass;
    }
    
    public RubyThreadGroup getDefaultThreadGroup() {
        return defaultThreadGroup;
    }
    void setDefaultThreadGroup(RubyThreadGroup defaultThreadGroup) {
        this.defaultThreadGroup = defaultThreadGroup;
    }

    public RubyClass getContinuation() {
        return continuationClass;
    }
    void setContinuation(RubyClass continuationClass) {
        this.continuationClass = continuationClass;
    }    

    public RubyClass getStructClass() {
        return structClass;
    }
    void setStructClass(RubyClass structClass) {
        this.structClass = structClass;
    }    

    public IRubyObject getTmsStruct() {
        return tmsStruct;
    }
    void setTmsStruct(RubyClass tmsStruct) {
        this.tmsStruct = tmsStruct;
    }
    
    public IRubyObject getPasswdStruct() {
        return passwdStruct;
    }
    void setPasswdStruct(RubyClass passwdStruct) {
        this.passwdStruct = passwdStruct;
    }

    public IRubyObject getGroupStruct() {
        return groupStruct;
    }
    void setGroupStruct(RubyClass groupStruct) {
        this.groupStruct = groupStruct;
    }

    public RubyModule getGC() {
        return gcModule;
    }
    void setGC(RubyModule gcModule) {
        this.gcModule = gcModule;
    }    

    public RubyModule getObjectSpaceModule() {
        return objectSpaceModule;
    }
    void setObjectSpaceModule(RubyModule objectSpaceModule) {
        this.objectSpaceModule = objectSpaceModule;
    }    

    public RubyModule getProcess() {
        return processModule;
    }
    void setProcess(RubyModule processModule) {
        this.processModule = processModule;
    }    

    public RubyClass getProcStatus() {
        return procStatusClass; 
    }
    void setProcStatus(RubyClass procStatusClass) {
        this.procStatusClass = procStatusClass;
    }
    
    public RubyModule getProcUID() {
        return procUIDModule;
    }
    void setProcUID(RubyModule procUIDModule) {
        this.procUIDModule = procUIDModule;
    }
    
    public RubyModule getProcGID() {
        return procGIDModule;
    }
    void setProcGID(RubyModule procGIDModule) {
        this.procGIDModule = procGIDModule;
    }
    
    public RubyModule getProcSysModule() {
        return procSysModule;
    }
    void setProcSys(RubyModule procSysModule) {
        this.procSysModule = procSysModule;
    }

    public RubyModule getPrecision() {
        return precisionModule;
    }
    void setPrecision(RubyModule precisionModule) {
        this.precisionModule = precisionModule;
    }

    public RubyModule getErrno() {
        return errnoModule;
    }

    public RubyClass getException() {
        return exceptionClass;
    }
    void setException(RubyClass exceptionClass) {
        this.exceptionClass = exceptionClass;
    }

    public RubyClass getNameError() {
        return nameError;
    }

    public RubyClass getNameErrorMessage() {
        return nameErrorMessage;
    }

    public RubyClass getNoMethodError() {
        return noMethodError;
    }

    public RubyClass getSignalException() {
        return signalException;
    }

    public RubyClass getRangeError() {
        return rangeError;
    }

    public RubyClass getSystemExit() {
        return systemExit;
    }

    public RubyClass getLocalJumpError() {
        return localJumpError;
    }

    public RubyClass getNativeException() {
        return nativeException;
    }

    public RubyClass getSystemCallError() {
        return systemCallError;
    }

    public RubyClass getFatal() {
        return fatal;
    }
    
    public RubyClass getInterrupt() {
        return interrupt;
    }
    
    public RubyClass getTypeError() {
        return typeError;
    }

    public RubyClass getArgumentError() {
        return argumentError;
    }

    public RubyClass getIndexError() {
        return indexError;
    }
    
    public RubyClass getSyntaxError() {
        return syntaxError;
    }

    public RubyClass getStandardError() {
        return standardError;
    }
    
    public RubyClass getRuntimeError() {
        return runtimeError;
    }
    
    public RubyClass getIOError() {
        return ioError;
    }

    public RubyClass getLoadError() {
        return loadError;
    }

    public RubyClass getNotImplementedError() {
        return notImplementedError;
    }

    public RubyClass getSecurityError() {
        return securityError;
    }

    public RubyClass getNoMemoryError() {
        return noMemoryError;
    }

    public RubyClass getRegexpError() {
        return regexpError;
    }

    public RubyClass getEOFError() {
        return eofError;
    }

    public RubyClass getThreadError() {
        return threadError;
    }

    public RubyClass getConcurrencyError() {
        return concurrencyError;
    }

    public RubyClass getSystemStackError() {
        return systemStackError;
    }

    public RubyClass getZeroDivisionError() {
        return zeroDivisionError;
    }

    public RubyClass getFloatDomainError() {
        return floatDomainError;
    }

    private RubyHash charsetMap;
    public RubyHash getCharsetMap() {
        if (charsetMap == null) charsetMap = new RubyHash(this);
        return charsetMap;
    }

    /** Getter for property isVerbose.
     * @return Value of property isVerbose.
     */
    public IRubyObject getVerbose() {
        return verbose;
    }

    /** Setter for property isVerbose.
     * @param verbose New value of property isVerbose.
     */
    public void setVerbose(IRubyObject verbose) {
        this.verbose = verbose;
    }

    /** Getter for property isDebug.
     * @return Value of property isDebug.
     */
    public IRubyObject getDebug() {
        return debug;
    }

    /** Setter for property isDebug.
     * @param debug New value of property isDebug.
     */
    public void setDebug(IRubyObject debug) {
        this.debug = debug;
    }

    public JavaSupport getJavaSupport() {
        return javaSupport;
    }
    
    public static ClassLoader getClassLoader() {
        // we try to get the classloader that loaded JRuby, falling back on System
        ClassLoader loader = Ruby.class.getClassLoader();
        if (loader == null) {
            loader = ClassLoader.getSystemClassLoader();
        }
        
        return loader;
    }

    public synchronized JRubyClassLoader getJRubyClassLoader() {
        // FIXME: Get rid of laziness and handle restricted access elsewhere
        if (!Ruby.isSecurityRestricted() && jrubyClassLoader == null) {
            jrubyClassLoader = new JRubyClassLoader(config.getLoader());
        }
        
        return jrubyClassLoader;
    }

    /** Defines a global variable
     */
    public void defineVariable(final GlobalVariable variable) {
        globalVariables.define(variable.name(), new IAccessor() {
            public IRubyObject getValue() {
                return variable.get();
            }

            public IRubyObject setValue(IRubyObject newValue) {
                return variable.set(newValue);
            }
        });
    }

    /** defines a readonly global variable
     *
     */
    public void defineReadonlyVariable(String name, IRubyObject value) {
        globalVariables.defineReadonly(name, new ValueAccessor(value));
    }
    
    public Node parseFile(InputStream in, String file, DynamicScope scope) {
        return parser.parse(file, in, scope, new ParserConfiguration(0, false, false, true));
    }

    public Node parseInline(InputStream in, String file, DynamicScope scope) {
        return parser.parse(file, in, scope, new ParserConfiguration(0, false, true));
    }

    public Node parseEval(String content, String file, DynamicScope scope, int lineNumber) {
        byte[] bytes;
        
        try {
            bytes = content.getBytes(KCode.NONE.getKCode());
        } catch (UnsupportedEncodingException e) {
            bytes = content.getBytes();
        }
        
        return parser.parse(file, new ByteArrayInputStream(bytes), scope, 
                new ParserConfiguration(lineNumber, false));
    }

    public Node parse(String content, String file, DynamicScope scope, int lineNumber, 
            boolean extraPositionInformation) {
        byte[] bytes;
        
        try {
            bytes = content.getBytes(KCode.NONE.getKCode());
        } catch (UnsupportedEncodingException e) {
            bytes = content.getBytes();
        }

        return parser.parse(file, new ByteArrayInputStream(bytes), scope, 
                new ParserConfiguration(lineNumber, extraPositionInformation, false));
    }
    
    public Node parseEval(ByteList content, String file, DynamicScope scope, int lineNumber) {
        return parser.parse(file, content, scope, new ParserConfiguration(lineNumber, false));
    }

    public Node parse(ByteList content, String file, DynamicScope scope, int lineNumber, 
            boolean extraPositionInformation) {
        return parser.parse(file, content, scope, 
                new ParserConfiguration(lineNumber, extraPositionInformation, false));
    }


    public ThreadService getThreadService() {
        return threadService;
    }

    public ThreadContext getCurrentContext() {
        return threadService.getCurrentContext();
    }

    /**
     * Returns the loadService.
     * @return ILoadService
     */
    public LoadService getLoadService() {
        return loadService;
    }

    public RubyWarnings getWarnings() {
        return warnings;
    }

    public PrintStream getErrorStream() {
        // FIXME: We can't guarantee this will always be a RubyIO...so the old code here is not safe
        /*java.io.OutputStream os = ((RubyIO) getGlobalVariables().get("$stderr")).getOutStream();
        if(null != os) {
            return new PrintStream(os);
        } else {
            return new PrintStream(new org.jruby.util.SwallowingOutputStream());
        }*/
        return new PrintStream(new IOOutputStream(getGlobalVariables().get("$stderr")));
    }

    public InputStream getInputStream() {
        return new IOInputStream(getGlobalVariables().get("$stdin"));
    }

    public PrintStream getOutputStream() {
        return new PrintStream(new IOOutputStream(getGlobalVariables().get("$stdout")));
    }

    public RubyModule getClassFromPath(String path) {
        RubyModule c = getObject();
        if (path.length() == 0 || path.charAt(0) == '#') {
            throw newTypeError("can't retrieve anonymous class " + path);
        }
        int pbeg = 0, p = 0;
        for(int l=path.length(); p<l; ) {
            while(p<l && path.charAt(p) != ':') {
                p++;
            }
            String str = path.substring(pbeg, p);

            if(p<l && path.charAt(p) == ':') {
                if(p+1 < l && path.charAt(p+1) != ':') {
                    throw newTypeError("undefined class/module " + path.substring(pbeg,p));
                }
                p += 2;
                pbeg = p;
            }

            IRubyObject cc = c.getConstant(str);
            if(!(cc instanceof RubyModule)) {
                throw newTypeError("" + path + " does not refer to class/module");
            }
            c = (RubyModule)cc;
        }
        return c;
    }

    /** Prints an error with backtrace to the error stream.
     *
     * MRI: eval.c - error_print()
     *
     */
    public void printError(RubyException excp) {
        if (excp == null || excp.isNil()) {
            return;
        }

        ThreadContext context = getCurrentContext();
        IRubyObject backtrace = excp.callMethod(context, "backtrace");

        PrintStream errorStream = getErrorStream();
        if (backtrace.isNil() || !(backtrace instanceof RubyArray)) {
            if (context.getFile() != null) {
                errorStream.print(context.getFile() + ":" + context.getLine());
            } else {
                errorStream.print(context.getLine());
            }
        } else if (((RubyArray) backtrace).getLength() == 0) {
            printErrorPos(context, errorStream);
        } else {
            IRubyObject mesg = ((RubyArray) backtrace).first();

            if (mesg.isNil()) {
                printErrorPos(context, errorStream);
            } else {
                errorStream.print(mesg);
            }
        }

        RubyClass type = excp.getMetaClass();
        String info = excp.toString();

        if (type == getRuntimeError() && (info == null || info.length() == 0)) {
            errorStream.print(": unhandled exception\n");
        } else {
            String path = type.getName();

            if (info.length() == 0) {
                errorStream.print(": " + path + '\n');
            } else {
                if (path.startsWith("#")) {
                    path = null;
                }

                String tail = null;
                if (info.indexOf("\n") != -1) {
                    tail = info.substring(info.indexOf("\n") + 1);
                    info = info.substring(0, info.indexOf("\n"));
                }

                errorStream.print(": " + info);

                if (path != null) {
                    errorStream.print(" (" + path + ")\n");
                }

                if (tail != null) {
                    errorStream.print(tail + '\n');
                }
            }
        }

        excp.printBacktrace(errorStream);
    }

    private void printErrorPos(ThreadContext context, PrintStream errorStream) {
        if (context.getFile() != null) {
            if (context.getFrameName() != null) {
                errorStream.print(context.getFile() + ":" + context.getLine());
                errorStream.print(":in '" + context.getFrameName() + '\'');
            } else if (context.getLine() != 0) {
                errorStream.print(context.getFile() + ":" + context.getLine());
            } else {
                errorStream.print(context.getFile());
            }
        }
    }
    
    public void loadFile(String scriptName, InputStream in, boolean wrap) {
        IRubyObject self = wrap ? TopSelfFactory.createTopSelf(this) : getTopSelf();
        ThreadContext context = getCurrentContext();
        String file = context.getFile();
        
        try {
            secure(4); /* should alter global state */

            context.setFile(scriptName);
            context.preNodeEval(objectClass, self, scriptName);

            parseFile(in, scriptName, null).interpret(this, context, self, Block.NULL_BLOCK);
        } catch (JumpException.ReturnJump rj) {
            return;
        } finally {
            context.postNodeEval();
            context.setFile(file);
        }
    }
    
    public void compileAndLoadFile(String filename, InputStream in, boolean wrap) {
        IRubyObject self = wrap ? TopSelfFactory.createTopSelf(this) : getTopSelf();
        ThreadContext context = getCurrentContext();
        String file = context.getFile();
        
        try {
            secure(4); /* should alter global state */

            context.setFile(filename);
            context.preNodeEval(objectClass, self, filename);
            
            Node scriptNode = parseFile(in, filename, null);
            
            Script script = tryCompile(scriptNode, new JRubyClassLoader(jrubyClassLoader));
            if (script == null) {
                System.err.println("Error, could not compile; pass -J-Djruby.jit.logging.verbose=true for more details");
            }

            runScript(script);
        } catch (JumpException.ReturnJump rj) {
            return;
        } finally {
            context.postNodeEval();
            context.setFile(file);
        }
    }

    public void loadScript(Script script) {
        IRubyObject self = getTopSelf();
        ThreadContext context = getCurrentContext();

        try {
            secure(4); /* should alter global state */

            context.preNodeEval(objectClass, self);
            
            script.load(context, self, IRubyObject.NULL_ARRAY, Block.NULL_BLOCK);
        } catch (JumpException.ReturnJump rj) {
            return;
        } finally {
            context.postNodeEval();
        }
    }

    public class CallTraceFuncHook extends EventHook {
        private RubyProc traceFunc;
        
        public void setTraceFunc(RubyProc traceFunc) {
            this.traceFunc = traceFunc;
        }
        
        public void eventHandler(ThreadContext context, String eventName, String file, int line, String name, IRubyObject type) {
            if (!context.isWithinTrace()) {
                if (file == null) file = "(ruby)";
                if (type == null) type = getFalse();
                
                RubyBinding binding = RubyBinding.newBinding(Ruby.this);

                context.preTrace();
                try {
                    traceFunc.call(context, new IRubyObject[] {
                        newString(eventName), // event name
                        newString(file), // filename
                        newFixnum(line), // line numbers should be 1-based
                        name != null ? newSymbol(name) : getNil(),
                        binding,
                        type
                    });
                } finally {
                    context.postTrace();
                }
            }
        }

        public boolean isInterestedInEvent(RubyEvent event) {
            return true;
        }
    };
    
    private final CallTraceFuncHook callTraceFuncHook = new CallTraceFuncHook();
    
    public void addEventHook(EventHook hook) {
        eventHooks.add(hook);
        hasEventHooks = true;
    }
    
    public void removeEventHook(EventHook hook) {
        eventHooks.remove(hook);
        hasEventHooks = !eventHooks.isEmpty();
    }

    public void setTraceFunction(RubyProc traceFunction) {
        removeEventHook(callTraceFuncHook);
        
        if (traceFunction == null) {
            return;
        }
        
        callTraceFuncHook.setTraceFunc(traceFunction);
        addEventHook(callTraceFuncHook);
    }
    
    public void callEventHooks(ThreadContext context, RubyEvent event, String file, int line, String name, IRubyObject type) {
        for (EventHook eventHook : eventHooks) {
            if (eventHook.isInterestedInEvent(event)) {
                eventHook.event(context, event, file, line, name, type);
            }
        }
    }
    
    public boolean hasEventHooks() {
        return hasEventHooks;
    }
    
    public GlobalVariables getGlobalVariables() {
        return globalVariables;
    }

    // For JSR 223 support: see http://scripting.java.net/
    public void setGlobalVariables(GlobalVariables globalVariables) {
        this.globalVariables = globalVariables;
    }

    public CallbackFactory callbackFactory(Class<?> type) {
        return CallbackFactory.createFactory(this, type);
    }

    /**
     * Push block onto exit stack.  When runtime environment exits
     * these blocks will be evaluated.
     *
     * @return the element that was pushed onto stack
     */
    public IRubyObject pushExitBlock(RubyProc proc) {
        atExitBlocks.push(proc);
        return proc;
    }

    // use this for JRuby-internal finalizers
    public void addInternalFinalizer(Finalizable finalizer) {
        synchronized (internalFinalizersMutex) {
            if (internalFinalizers == null) {
                internalFinalizers = new WeakHashMap<Finalizable, Object>();
            }
            internalFinalizers.put(finalizer, null);
        }
    }

    // this method is for finalizers registered via ObjectSpace
    public void addFinalizer(Finalizable finalizer) {
        synchronized (finalizersMutex) {
            if (finalizers == null) {
                finalizers = new WeakHashMap<Finalizable, Object>();
            }
            finalizers.put(finalizer, null);
        }
    }
    
    public void removeInternalFinalizer(Finalizable finalizer) {
        synchronized (internalFinalizersMutex) {
            if (internalFinalizers != null) {
                internalFinalizers.remove(finalizer);
            }
        }
    }

    public void removeFinalizer(Finalizable finalizer) {
        synchronized (finalizersMutex) {
            if (finalizers != null) {
                finalizers.remove(finalizer);
            }
        }
    }

    /**
     * Make sure Kernel#at_exit procs get invoked on runtime shutdown.
     * This method needs to be explicitly called to work properly.
     * I thought about using finalize(), but that did not work and I
     * am not sure the runtime will be at a state to run procs by the
     * time Ruby is going away.  This method can contain any other
     * things that need to be cleaned up at shutdown.
     */
    public void tearDown() {
        int status = 0;

        while (!atExitBlocks.empty()) {
            RubyProc proc = atExitBlocks.pop();
            try {
                proc.call(getCurrentContext(), IRubyObject.NULL_ARRAY);
            } catch (RaiseException rj) {
                RubyException raisedException = rj.getException();
                if (!getSystemExit().isInstance(raisedException)) {
                    status = 1;
                    printError(raisedException);
                } else {
                    IRubyObject statusObj = raisedException.callMethod(
                            getCurrentContext(), "status");
                    if (statusObj != null && !statusObj.isNil()) {
                        status = RubyNumeric.fix2int(statusObj);
                    }
                }
            }
        }

        if (finalizers != null) {
            synchronized (finalizers) {
                for (Iterator<Finalizable> finalIter = new ArrayList<Finalizable>(finalizers.keySet()).iterator(); finalIter.hasNext();) {
                    finalIter.next().finalize();
                    finalIter.remove();
                }
            }
        }

        synchronized (internalFinalizersMutex) {
            if (internalFinalizers != null) {
                for (Iterator<Finalizable> finalIter = new ArrayList<Finalizable>(
                        internalFinalizers.keySet()).iterator(); finalIter.hasNext();) {
                    finalIter.next().finalize();
                    finalIter.remove();
                }
            }
        }

        getThreadService().disposeCurrentThread();

        getBeanManager().unregisterCompiler();
        getBeanManager().unregisterConfig();
        getBeanManager().unregisterClassCache();
        getBeanManager().unregisterMethodCache();

        if (status != 0) {
            throw newSystemExit(status);
        }
    }

    // new factory methods ------------------------------------------------------------------------

    public RubyArray newEmptyArray() {
        return RubyArray.newEmptyArray(this);
    }

    public RubyArray newArray() {
        return RubyArray.newArray(this);
    }

    public RubyArray newArrayLight() {
        return RubyArray.newArrayLight(this);
    }

    public RubyArray newArray(IRubyObject object) {
        return RubyArray.newArray(this, object);
    }

    public RubyArray newArray(IRubyObject car, IRubyObject cdr) {
        return RubyArray.newArray(this, car, cdr);
    }

    public RubyArray newArray(IRubyObject[] objects) {
        return RubyArray.newArray(this, objects);
    }
    
    public RubyArray newArrayNoCopy(IRubyObject[] objects) {
        return RubyArray.newArrayNoCopy(this, objects);
    }
    
    public RubyArray newArrayNoCopyLight(IRubyObject[] objects) {
        return RubyArray.newArrayNoCopyLight(this, objects);
    }
    
    public RubyArray newArray(List<IRubyObject> list) {
        return RubyArray.newArray(this, list);
    }

    public RubyArray newArray(int size) {
        return RubyArray.newArray(this, size);
    }

    public RubyBoolean newBoolean(boolean value) {
        return RubyBoolean.newBoolean(this, value);
    }

    public RubyFileStat newFileStat(String filename, boolean lstat) {
        return RubyFileStat.newFileStat(this, filename, lstat);
    }
    
    public RubyFileStat newFileStat(FileDescriptor descriptor) {
        return RubyFileStat.newFileStat(this, descriptor);
    }

    public RubyFixnum newFixnum(long value) {
        return RubyFixnum.newFixnum(this, value);
    }

    public RubyFixnum newFixnum(int value) {
        return RubyFixnum.newFixnum(this, value);
    }

    public RubyFloat newFloat(double value) {
        return RubyFloat.newFloat(this, value);
    }

    public RubyNumeric newNumeric() {
        return RubyNumeric.newNumeric(this);
    }

    public RubyProc newProc(Block.Type type, Block block) {
        if (type != Block.Type.LAMBDA && block.getProcObject() != null) return block.getProcObject();

        RubyProc proc =  RubyProc.newProc(this, type);

        proc.callInit(IRubyObject.NULL_ARRAY, block);

        return proc;
    }

    public RubyProc newBlockPassProc(Block.Type type, Block block) {
        if (type != Block.Type.LAMBDA && block.getProcObject() != null) return block.getProcObject();

        RubyProc proc =  RubyProc.newProc(this, type);
        proc.initialize(getCurrentContext(), block);

        return proc;
    }

    public RubyBinding newBinding() {
        return RubyBinding.newBinding(this);
    }

    public RubyBinding newBinding(Binding binding) {
        return RubyBinding.newBinding(this, binding);
    }

    public RubyString newString() {
        return RubyString.newString(this, new ByteList());
    }

    public RubyString newString(String string) {
        return RubyString.newString(this, string);
    }
    
    public RubyString newString(ByteList byteList) {
        return RubyString.newString(this, byteList);
    }

    @Deprecated
    public RubyString newStringShared(ByteList byteList) {
        return RubyString.newStringShared(this, byteList);
    }    

    public RubySymbol newSymbol(String name) {
        return symbolTable.getSymbol(name);
    }

    /**
     * Faster than {@link #newSymbol(String)} if you already have an interned
     * name String. Don't intern your string just to call this version - the
     * overhead of interning will more than wipe out any benefit from the faster
     * lookup.
     *   
     * @param internedName the symbol name, <em>must</em> be interned! if in
     *                     doubt, call {@link #newSymbol(String)} instead.
     * @return the symbol for name
     */
    public RubySymbol fastNewSymbol(String internedName) {
        assert internedName == internedName.intern() : internedName + " is not interned";

        return symbolTable.fastGetSymbol(internedName);
    }

    public RubyTime newTime(long milliseconds) {
        return RubyTime.newTime(this, milliseconds);
    }

    public RaiseException newRuntimeError(String message) {
        return newRaiseException(getRuntimeError(), message);
    }    
    
    public RaiseException newArgumentError(String message) {
        return newRaiseException(getArgumentError(), message);
    }

    public RaiseException newArgumentError(int got, int expected) {
        return newRaiseException(getArgumentError(), "wrong # of arguments(" + got + " for " + expected + ")");
    }

    public RaiseException newErrnoEBADFError() {
        return newRaiseException(getErrno().fastGetClass("EBADF"), "Bad file descriptor");
    }

    public RaiseException newErrnoENOPROTOOPTError() {
        return newRaiseException(getErrno().fastGetClass("ENOPROTOOPT"), "Protocol not available");
    }

    public RaiseException newErrnoEPIPEError() {
        return newRaiseException(getErrno().fastGetClass("EPIPE"), "Broken pipe");
    }

    public RaiseException newErrnoECONNREFUSEDError() {
        return newRaiseException(getErrno().fastGetClass("ECONNREFUSED"), "Connection refused");
    }

    public RaiseException newErrnoECONNRESETError() {
        return newRaiseException(getErrno().fastGetClass("ECONNRESET"), "Connection reset by peer");
    }

    public RaiseException newErrnoEADDRINUSEError() {
        return newRaiseException(getErrno().fastGetClass("EADDRINUSE"), "Address in use");
    }

    public RaiseException newErrnoEINVALError() {
        return newRaiseException(getErrno().fastGetClass("EINVAL"), "Invalid file");
    }

    public RaiseException newErrnoENOENTError() {
        return newRaiseException(getErrno().fastGetClass("ENOENT"), "File not found");
    }

    public RaiseException newErrnoEACCESError(String message) {
        return newRaiseException(getErrno().fastGetClass("EACCES"), message);
    }

    public RaiseException newErrnoEAGAINError(String message) {
        return newRaiseException(getErrno().fastGetClass("EAGAIN"), message);
    }

    public RaiseException newErrnoEISDirError() {
        return newRaiseException(getErrno().fastGetClass("EISDIR"), "Is a directory");
    }

    public RaiseException newErrnoESPIPEError() {
        return newRaiseException(getErrno().fastGetClass("ESPIPE"), "Illegal seek");
    }

    public RaiseException newErrnoEBADFError(String message) {
        return newRaiseException(getErrno().fastGetClass("EBADF"), message);
    }

    public RaiseException newErrnoEINVALError(String message) {
        return newRaiseException(getErrno().fastGetClass("EINVAL"), message);
    }

    public RaiseException newErrnoENOTDIRError(String message) {
        return newRaiseException(getErrno().fastGetClass("ENOTDIR"), message);
    }

    public RaiseException newErrnoENOTSOCKError(String message) {
        return newRaiseException(getErrno().fastGetClass("ENOTSOCK"), message);
    }

    public RaiseException newErrnoENOENTError(String message) {
        return newRaiseException(getErrno().fastGetClass("ENOENT"), message);
    }

    public RaiseException newErrnoESPIPEError(String message) {
        return newRaiseException(getErrno().fastGetClass("ESPIPE"), message);
    }

    public RaiseException newErrnoEEXISTError(String message) {
        return newRaiseException(getErrno().fastGetClass("EEXIST"), message);
    }
    
    public RaiseException newErrnoEDOMError(String message) {
        return newRaiseException(getErrno().fastGetClass("EDOM"), "Domain error - " + message);
    }   
    
    public RaiseException newErrnoECHILDError() {
        return newRaiseException(getErrno().fastGetClass("ECHILD"), "No child processes");
    }    

    public RaiseException newIndexError(String message) {
        return newRaiseException(getIndexError(), message);
    }

    public RaiseException newSecurityError(String message) {
        return newRaiseException(getSecurityError(), message);
    }

    public RaiseException newSystemCallError(String message) {
        return newRaiseException(getSystemCallError(), message);
    }

    public RaiseException newTypeError(String message) {
        return newRaiseException(getTypeError(), message);
    }

    public RaiseException newThreadError(String message) {
        return newRaiseException(getThreadError(), message);
    }

    public RaiseException newConcurrencyError(String message) {
        return newRaiseException(getConcurrencyError(), message);
    }

    public RaiseException newSyntaxError(String message) {
        return newRaiseException(getSyntaxError(), message);
    }

    public RaiseException newRegexpError(String message) {
        return newRaiseException(getRegexpError(), message);
    }

    public RaiseException newRangeError(String message) {
        return newRaiseException(getRangeError(), message);
    }

    public RaiseException newNotImplementedError(String message) {
        return newRaiseException(getNotImplementedError(), message);
    }
    
    public RaiseException newInvalidEncoding(String message) {
        return newRaiseException(fastGetClass("Iconv").fastGetClass("InvalidEncoding"), message);
    }

    public RaiseException newNoMethodError(String message, String name, IRubyObject args) {
        return new RaiseException(new RubyNoMethodError(this, getNoMethodError(), message, name, args), true);
    }

    public RaiseException newNameError(String message, String name) {
        return newNameError(message, name, null);
    }

    public RaiseException newNameError(String message, String name, Throwable origException) {
        return newNameError(message, name, origException, true);
    }

    public RaiseException newNameError(String message, String name, Throwable origException, boolean printWhenVerbose) {
        if (printWhenVerbose && origException != null && this.getVerbose().isTrue()) {
            origException.printStackTrace(getErrorStream());
        }
        return new RaiseException(new RubyNameError(
                this, getNameError(), message, name), true);
    }

    public RaiseException newLocalJumpError(String reason, IRubyObject exitValue, String message) {
        return new RaiseException(new RubyLocalJumpError(this, getLocalJumpError(), message, reason, exitValue), true);
    }

    public RaiseException newRedoLocalJumpError() {
        return new RaiseException(new RubyLocalJumpError(this, getLocalJumpError(), "unexpected redo", "redo", getNil()), true);
    }

    public RaiseException newLoadError(String message) {
        return newRaiseException(getLoadError(), message);
    }

    public RaiseException newFrozenError(String objectType) {
        // TODO: Should frozen error have its own distinct class?  If not should more share?
        return newRaiseException(getTypeError(), "can't modify frozen " + objectType);
    }

    public RaiseException newSystemStackError(String message) {
        return newRaiseException(getSystemStackError(), message);
    }

    public RaiseException newSystemExit(int status) {
        return new RaiseException(RubySystemExit.newInstance(this, status));
    }

    public RaiseException newIOError(String message) {
        return newRaiseException(getIOError(), message);
    }

    public RaiseException newStandardError(String message) {
        return newRaiseException(getStandardError(), message);
    }

    public RaiseException newIOErrorFromException(IOException ioe) {
        // TODO: this is kinda gross
        if(ioe.getMessage() != null) {
            if (ioe.getMessage().equals("Broken pipe")) {
                throw newErrnoEPIPEError();
            } else if (ioe.getMessage().equals("Connection reset by peer")) {
                throw newErrnoECONNRESETError();
            }
            return newRaiseException(getIOError(), ioe.getMessage());
        } else {
            return newRaiseException(getIOError(), "IO Error");
        }
    }

    public RaiseException newTypeError(IRubyObject receivedObject, RubyClass expectedType) {
        return newRaiseException(getTypeError(), "wrong argument type " +
                receivedObject.getMetaClass().getRealClass() + " (expected " + expectedType + ")");
    }

    public RaiseException newEOFError() {
        return newRaiseException(getEOFError(), "End of file reached");
    }

    public RaiseException newEOFError(String message) {
        return newRaiseException(getEOFError(), message);
    }

    public RaiseException newZeroDivisionError() {
        return newRaiseException(getZeroDivisionError(), "divided by 0");
    }

    public RaiseException newFloatDomainError(String message){
        return newRaiseException(getFloatDomainError(), message);
    }

    /**
     * @param exceptionClass
     * @param message
     * @return
     */
    private RaiseException newRaiseException(RubyClass exceptionClass, String message) {
        RaiseException re = new RaiseException(this, exceptionClass, message, true);
        return re;
    }


    public RubySymbol.SymbolTable getSymbolTable() {
        return symbolTable;
    }

    public void setStackTraces(int stackTraces) {
        this.stackTraces = stackTraces;
    }

    public int getStackTraces() {
        return stackTraces;
    }

    public void setRandomSeed(long randomSeed) {
        this.randomSeed = randomSeed;
    }

    public long getRandomSeed() {
        return randomSeed;
    }

    public Random getRandom() {
        return random;
    }

    public ObjectSpace getObjectSpace() {
        return objectSpace;
    }

    public Map<Integer, WeakReference<ChannelDescriptor>> getDescriptors() {
        return descriptors;
    }

    public long incrementRandomSeedSequence() {
        return randomSeedSequence++;
    }

    public InputStream getIn() {
        return in;
    }

    public PrintStream getOut() {
        return out;
    }

    public PrintStream getErr() {
        return err;
    }

    public boolean isGlobalAbortOnExceptionEnabled() {
        return globalAbortOnExceptionEnabled;
    }

    public void setGlobalAbortOnExceptionEnabled(boolean enable) {
        globalAbortOnExceptionEnabled = enable;
    }

    public boolean isDoNotReverseLookupEnabled() {
        return doNotReverseLookupEnabled;
    }

    public void setDoNotReverseLookupEnabled(boolean b) {
        doNotReverseLookupEnabled = b;
    }

    private ThreadLocal<Map<Object, Object>> inspect = new ThreadLocal<Map<Object, Object>>();
    public void registerInspecting(Object obj) {
        Map<Object, Object> val = inspect.get();
        if (val == null) inspect.set(val = new IdentityHashMap<Object, Object>());
        val.put(obj, null);
    }

    public boolean isInspecting(Object obj) {
        Map<Object, Object> val = inspect.get();
        return val == null ? false : val.containsKey(obj);
    }

    public void unregisterInspecting(Object obj) {
        Map<Object, Object> val = inspect.get();
        if (val != null ) val.remove(obj);
    }

    public boolean isObjectSpaceEnabled() {
        return objectSpaceEnabled;
    }

    // The method is intentionally not public, since it typically should
    // not be used outside of the core.
    /* package-private */ void setObjectSpaceEnabled(boolean objectSpaceEnabled) {
        this.objectSpaceEnabled = objectSpaceEnabled;
    }

    public long getStartTime() {
        return startTime;
    }

    public Profile getProfile() {
        return profile;
    }

    public String getJRubyHome() {
        return config.getJRubyHome();
    }

    public void setJRubyHome(String home) {
        config.setJRubyHome(home);
    }

    public RubyInstanceConfig getInstanceConfig() {
        return config;
    }

    /** GET_VM_STATE_VERSION */
    public long getGlobalState() {
        synchronized(this) {
            return globalState;
        }
    }

    /** INC_VM_STATE_VERSION */
    public void incGlobalState() {
        synchronized(this) {
            globalState = (globalState+1) & 0x8fffffff;
        }
    }

    public static boolean isSecurityRestricted() {
        return securityRestricted;
    }
    
    public static void setSecurityRestricted(boolean restricted) {
        securityRestricted = restricted;
    }
    
    public POSIX getPosix() {
        return posix;
    }
    
    public void setRecordSeparatorVar(GlobalVariable recordSeparatorVar) {
        this.recordSeparatorVar = recordSeparatorVar;
    }
    
    public GlobalVariable getRecordSeparatorVar() {
        return recordSeparatorVar;
    }
    
    public Set<Script> getJittedMethods() {
        return jittedMethods;
    }
    
    public ExecutorService getExecutor() {
        return executor;
    }

    public Map<String, DateTimeZone> getLocalTimezoneCache() {
        return localTimeZoneCache;
    }

    private final CacheMap cacheMap;
    private final ThreadService threadService;
    private Hashtable<Object, Object> runtimeInformation;
    
    private POSIX posix;

    private int stackTraces = 0;

    private ObjectSpace objectSpace = new ObjectSpace();

    private final RubySymbol.SymbolTable symbolTable = new RubySymbol.SymbolTable(this);
    private Map<Integer, WeakReference<ChannelDescriptor>> descriptors = new ConcurrentHashMap<Integer, WeakReference<ChannelDescriptor>>();
    private long randomSeed = 0;
    private long randomSeedSequence = 0;
    private Random random = new Random();

    private List<EventHook> eventHooks = new Vector<EventHook>();
    private boolean hasEventHooks;  
    private boolean globalAbortOnExceptionEnabled = false;
    private boolean doNotReverseLookupEnabled = false;
    private volatile boolean objectSpaceEnabled;
    
    private final Set<Script> jittedMethods = Collections.synchronizedSet(new WeakHashSet<Script>());
    
    private static ThreadLocal<Ruby> currentRuntime = new ThreadLocal<Ruby>();
    
    private long globalState = 1;
    
    private int safeLevel = -1;

    // Default objects
    private IRubyObject topSelf;
    private RubyNil nilObject;
    private RubyBoolean trueObject;
    private RubyBoolean falseObject;
    public final RubyFixnum[] fixnumCache = new RubyFixnum[256];

    private IRubyObject verbose;
    private IRubyObject debug;
    
    private RubyThreadGroup defaultThreadGroup;

    /**
     * All the core classes we keep hard references to. These are here largely
     * so that if someone redefines String or Array we won't start blowing up
     * creating strings and arrays internally. They also provide much faster
     * access than going through normal hash lookup on the Object class.
     */
    private RubyClass
            objectClass, moduleClass, classClass, nilClass, trueClass,
            falseClass, numericClass, floatClass, integerClass, fixnumClass,
            complexClass, rationalClass, enumeratorClass,
            arrayClass, hashClass, rangeClass, stringClass, symbolClass,
            procClass, bindingClass, methodClass, unboundMethodClass,
            matchDataClass, regexpClass, timeClass, bignumClass, dirClass,
            fileClass, fileStatClass, ioClass, threadClass, threadGroupClass,
            continuationClass, structClass, tmsStruct, passwdStruct,
            groupStruct, procStatusClass, exceptionClass, runtimeError, ioError,
            scriptError, nameError, nameErrorMessage, noMethodError, signalException,
            rangeError, dummyClass, systemExit, localJumpError, nativeException,
            systemCallError, fatal, interrupt, typeError, argumentError, indexError,
            syntaxError, standardError, loadError, notImplementedError, securityError, noMemoryError,
            regexpError, eofError, threadError, concurrencyError, systemStackError, zeroDivisionError, floatDomainError;

    /**
     * All the core modules we keep direct references to, for quick access and
     * to ensure they remain available.
     */
    private RubyModule
            kernelModule, comparableModule, enumerableModule, mathModule,
            marshalModule, etcModule, fileTestModule, gcModule,
            objectSpaceModule, processModule, procUIDModule, procGIDModule,
            procSysModule, precisionModule, errnoModule;
    
    // record separator var, to speed up io ops that use it
    private GlobalVariable recordSeparatorVar;

    // former java.lang.System concepts now internalized for MVM
    private String currentDirectory;

    private long startTime = System.currentTimeMillis();

    private RubyInstanceConfig config;

    private InputStream in;
    private PrintStream out;
    private PrintStream err;

    // Java support
    private JavaSupport javaSupport;
    private JRubyClassLoader jrubyClassLoader;
    
    // Management/monitoring
    private BeanManager beanManager;
    
    // Compilation
    private final JITCompiler jitCompiler;

    // Note: this field and the following static initializer
    // must be located be in this order!
    private volatile static boolean securityRestricted = false;
    static {
        if (SafePropertyAccessor.isSecurityProtected("jruby.reflection")) {
            // can't read non-standard properties
            securityRestricted = true;
        } else {
            SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                try {
                    sm.checkCreateClassLoader();
                } catch (SecurityException se) {
                    // can't create custom classloaders
                    securityRestricted = true;
                }
            }
        }
    }

    private Parser parser = new Parser(this);

    private LoadService loadService;
    private GlobalVariables globalVariables = new GlobalVariables(this);
    private RubyWarnings warnings = new RubyWarnings(this);

    // Contains a list of all blocks (as Procs) that should be called when
    // the runtime environment exits.
    private Stack<RubyProc> atExitBlocks = new Stack<RubyProc>();

    private Profile profile;

    private KCode kcode = KCode.NONE;

    // Atomic integers for symbol and method IDs
    private AtomicInteger symbolLastId = new AtomicInteger(128);
    private AtomicInteger moduleLastId = new AtomicInteger(0);

    private Object respondToMethod;
    private Object objectToYamlMethod;

    private Map<String, DateTimeZone> localTimeZoneCache = new HashMap<String,DateTimeZone>();
    /**
     * A list of "external" finalizers (the ones, registered via ObjectSpace),
     * weakly referenced, to be executed on tearDown.
     */
    private Map<Finalizable, Object> finalizers;
    
    /**
     * A list of JRuby-internal finalizers,  weakly referenced,
     * to be executed on tearDown.
     */
    private Map<Finalizable, Object> internalFinalizers;

    // mutex that controls modifications of user-defined finalizers
    private final Object finalizersMutex = new Object();

    // mutex that controls modifications of internal finalizers
    private final Object internalFinalizersMutex = new Object();
    
    // A thread pool to use for executing this runtime's Ruby threads
    private ExecutorService executor;
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2002-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2007 Ola Bini <ola@ologix.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import org.jruby.anno.FrameField;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.Block;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;

public class RubyArgsFile {
    private static final class ArgsFileData {
        private final Ruby runtime;
        public ArgsFileData(Ruby runtime) {
            this.runtime = runtime;
        }

        public IRubyObject currentFile;
        public int currentLineNumber;
        public boolean startedProcessing = false; 
        public boolean finishedProcessing = false;

        public boolean nextArgsFile(ThreadContext context) {
            if (finishedProcessing) {
                return false;
            }

            RubyArray args = (RubyArray)runtime.getGlobalVariables().get("$*");
            if (args.getLength() == 0) {
                if (!startedProcessing) { 
                    currentFile = runtime.getGlobalVariables().get("$stdin");
                    ((RubyString) runtime.getGlobalVariables().get("$FILENAME")).setValue(new ByteList(new byte[]{'-'}));
                    currentLineNumber = 0;
                    startedProcessing = true;
                    return true;
                } else {
                    finishedProcessing = true;
                    return false;
                }
            }

            IRubyObject arg = args.shift();
            RubyString filename = (RubyString)((RubyObject)arg).to_s();
            ByteList filenameBytes = filename.getByteList();
            ((RubyString) runtime.getGlobalVariables().get("$FILENAME")).setValue(filenameBytes);

            if (filenameBytes.length() == 1 && filenameBytes.get(0) == '-') {
                currentFile = runtime.getGlobalVariables().get("$stdin");
            } else {
                currentFile = RubyFile.open(context, runtime.getFile(), 
                        new IRubyObject[] {filename.strDup(context.getRuntime())}, Block.NULL_BLOCK); 
            }
            
            startedProcessing = true;
            return true;
        }

        public static ArgsFileData getDataFrom(IRubyObject recv) {
            ArgsFileData data = (ArgsFileData)recv.dataGetStruct();
            if(data == null) {
                data = new ArgsFileData(recv.getRuntime());
                recv.dataWrapStruct(data);
            }
            return data;
        }
    }    
    
    public static void setCurrentLineNumber(IRubyObject recv, int newLineNumber) {
        ArgsFileData.getDataFrom(recv).currentLineNumber = newLineNumber;
    }

    public static void initArgsFile(Ruby runtime) {
        RubyObject argsFile = new RubyObject(runtime, runtime.getObject());

        runtime.getEnumerable().extend_object(argsFile);
        
        runtime.defineReadonlyVariable("$<", argsFile);
        runtime.defineGlobalConstant("ARGF", argsFile);
        
        RubyClass argfClass = argsFile.getMetaClass();
        argfClass.defineAnnotatedMethods(RubyArgsFile.class);
        runtime.defineReadonlyVariable("$FILENAME", runtime.newString("-"));
    }

    @JRubyMethod(name = {"fileno", "to_i"})
    public static IRubyObject fileno(ThreadContext context, IRubyObject recv) {
        ArgsFileData data = ArgsFileData.getDataFrom(recv);

        if (data.currentFile == null && !data.nextArgsFile(context)) {
            throw context.getRuntime().newArgumentError("no stream");
        }
        return ((RubyIO) data.currentFile).fileno(context);
    }

    @JRubyMethod(name = "to_io")
    public static IRubyObject to_io(ThreadContext context, IRubyObject recv) {
        ArgsFileData data = ArgsFileData.getDataFrom(recv);

        if (data.currentFile == null && !data.nextArgsFile(context)) {
            throw context.getRuntime().newArgumentError("no stream");
        }
        return data.currentFile;
    }

    public static IRubyObject internalGets(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        ArgsFileData data = ArgsFileData.getDataFrom(recv);

        if(data.currentFile == null && !data.nextArgsFile(context)) {
            return context.getRuntime().getNil();
        }
        
        IRubyObject line = data.currentFile.callMethod(context, "gets", args);
        
        while (line instanceof RubyNil) {
            data.currentFile.callMethod(context, "close");
            if (!data.nextArgsFile(context)) {
                data.currentFile = null;
                return line;
        	}
            line = data.currentFile.callMethod(context, "gets", args);
        }
        
        data.currentLineNumber++;
        context.getRuntime().getGlobalVariables().set("$.", context.getRuntime().newFixnum(data.currentLineNumber));
        
        return line;
    }
    
    // ARGF methods

    /** Read a line.
     * 
     */
    @JRubyMethod(name = "gets", optional = 1, frame = true, writes = FrameField.LASTLINE)
    public static IRubyObject gets(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        IRubyObject result = internalGets(context, recv, args);

        if (!result.isNil()) {
            context.getCurrentFrame().setLastLine(result);
        }

        return result;
    }
    
    /** Read a line.
     * 
     */
    @JRubyMethod(name = "readline", optional = 1, frame = true, writes = FrameField.LASTLINE)
    public static IRubyObject readline(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        IRubyObject line = gets(context, recv, args);

        if (line.isNil()) {
            throw context.getRuntime().newEOFError();
        }
        
        return line;
    }

    @JRubyMethod(name = "readlines", optional = 1, frame = true)
    public static RubyArray readlines(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        IRubyObject[] separatorArgument;
        if (args.length > 0) {
            if (!context.getRuntime().getNilClass().isInstance(args[0]) &&
                !context.getRuntime().getString().isInstance(args[0])) {
                throw context.getRuntime().newTypeError(args[0], context.getRuntime().getString());
            } 
            separatorArgument = new IRubyObject[] { args[0] };
        } else {
            separatorArgument = IRubyObject.NULL_ARRAY;
        }

        RubyArray result = context.getRuntime().newArray();
        IRubyObject line;
        while (! (line = internalGets(context, recv, separatorArgument)).isNil()) {
            result.append(line);
        }
        return result;
    }
    
    @JRubyMethod(name = "each_byte", frame = true)
    public static IRubyObject each_byte(ThreadContext context, IRubyObject recv, Block block) {
        IRubyObject bt;

        while(!(bt = getc(context, recv)).isNil()) {
            block.yield(context, bt);
        }

        return recv;
    }

    /** Invoke a block for each line.
     *
     */
    @JRubyMethod(name = "each_line", alias = {"each"}, optional = 1, frame = true)
    public static IRubyObject each_line(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        IRubyObject nextLine = internalGets(context, recv, args);
        
        while (!nextLine.isNil()) {
        	block.yield(context, nextLine);
        	nextLine = internalGets(context, recv, args);
        }
        
        return recv;
    }

    @JRubyMethod(name = "file")
    public static IRubyObject file(ThreadContext context, IRubyObject recv) {
        ArgsFileData data = ArgsFileData.getDataFrom(recv);

        if(data.currentFile == null && !data.nextArgsFile(context)) {
            return context.getRuntime().getNil();
        }
        return data.currentFile;
    }

    @JRubyMethod(name = "skip")
    public static IRubyObject skip(IRubyObject recv) {
        ArgsFileData data = ArgsFileData.getDataFrom(recv);
        data.currentFile = null;
        return recv;
    }

    @JRubyMethod(name = "close")
    public static IRubyObject close(ThreadContext context, IRubyObject recv) {
        ArgsFileData data = ArgsFileData.getDataFrom(recv);
        if(data.currentFile == null && !data.nextArgsFile(context)) {
            return recv;
        }
        data.currentFile = null;
        data.currentLineNumber = 0;
        return recv;
    }

    @JRubyMethod(name = "closed?")
    public static IRubyObject closed_p(ThreadContext context, IRubyObject recv) {
        ArgsFileData data = ArgsFileData.getDataFrom(recv);
        if(data.currentFile == null && !data.nextArgsFile(context)) {
            return recv;
        }
        return ((RubyIO)data.currentFile).closed_p(context);
    }

    @JRubyMethod(name = "binmode")
    public static IRubyObject binmode(ThreadContext context, IRubyObject recv) {
        ArgsFileData data = ArgsFileData.getDataFrom(recv);
        if(data.currentFile == null && !data.nextArgsFile(context)) {
            throw context.getRuntime().newArgumentError("no stream");
        }
        
        return ((RubyIO)data.currentFile).binmode();
    }

    @JRubyMethod(name = "lineno")
    public static IRubyObject lineno(ThreadContext context, IRubyObject recv) {
        return context.getRuntime().newFixnum(ArgsFileData.getDataFrom(recv).currentLineNumber);
    }

    @JRubyMethod(name = "tell", alias = {"pos"})
    public static IRubyObject tell(ThreadContext context, IRubyObject recv) {
        ArgsFileData data = ArgsFileData.getDataFrom(recv);
        if(data.currentFile == null && !data.nextArgsFile(context)) {
            throw context.getRuntime().newArgumentError("no stream to tell");
        }
        return ((RubyIO)data.currentFile).pos(context);
    }

    @JRubyMethod(name = "rewind")
    public static IRubyObject rewind(ThreadContext context, IRubyObject recv) {
        ArgsFileData data = ArgsFileData.getDataFrom(recv);
        if(data.currentFile == null && !data.nextArgsFile(context)) {
            throw context.getRuntime().newArgumentError("no stream to rewind");
        }
        return ((RubyIO)data.currentFile).rewind(context);
    }

    @JRubyMethod(name = {"eof", "eof?"})
    public static IRubyObject eof(ThreadContext context, IRubyObject recv) {
        ArgsFileData data = ArgsFileData.getDataFrom(recv);
        if (data.currentFile == null && !data.nextArgsFile(context)) {
            return context.getRuntime().getTrue();
        }

        return ((RubyIO) data.currentFile).eof_p(context);
    }

    @JRubyMethod(name = "pos=", required = 1)
    public static IRubyObject set_pos(ThreadContext context, IRubyObject recv, IRubyObject offset) {
        ArgsFileData data = ArgsFileData.getDataFrom(recv);
        if(data.currentFile == null && !data.nextArgsFile(context)) {
            throw context.getRuntime().newArgumentError("no stream to set position");
        }
        return ((RubyIO)data.currentFile).pos_set(context, offset);
    }

    @JRubyMethod(name = "seek", required = 1, optional = 1)
    public static IRubyObject seek(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        ArgsFileData data = ArgsFileData.getDataFrom(recv);
        if(data.currentFile == null && !data.nextArgsFile(context)) {
            throw context.getRuntime().newArgumentError("no stream to seek");
        }
        return ((RubyIO)data.currentFile).seek(context, args);
    }

    @JRubyMethod(name = "lineno=", required = 1)
    public static IRubyObject set_lineno(ThreadContext context, IRubyObject recv, IRubyObject line) {
        ArgsFileData data = ArgsFileData.getDataFrom(recv);
        data.currentLineNumber = RubyNumeric.fix2int(line);
        return context.getRuntime().getNil();
    }

    @JRubyMethod(name = "readchar")
    public static IRubyObject readchar(ThreadContext context, IRubyObject recv) {
        IRubyObject c = getc(context, recv);
        
        if(c.isNil()) throw context.getRuntime().newEOFError();

        return c;
    }

    @JRubyMethod(name = "getc")
    public static IRubyObject getc(ThreadContext context, IRubyObject recv) {
        ArgsFileData data = ArgsFileData.getDataFrom(recv);
        IRubyObject bt;
        while(true) {
            if(data.currentFile == null && !data.nextArgsFile(context)) {
                return context.getRuntime().getNil();
            }
            if(!(data.currentFile instanceof RubyFile)) {
                bt = data.currentFile.callMethod(context,"getc");
            } else {
                bt = ((RubyIO)data.currentFile).getc();
            }
            if(bt.isNil()) {
                data.currentFile = null;
                continue;
            }
            return bt;
        }
    }

    @JRubyMethod(name = "read", optional = 2)
    public static IRubyObject read(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        Ruby runtime = context.getRuntime();
        ArgsFileData data = ArgsFileData.getDataFrom(recv);
        IRubyObject tmp, str, length;
        long len = 0;
        if(args.length > 0) {
            length = args[0];
            if(args.length > 1) {
                str = args[1];
            } else {
                str = runtime.getNil();
            }
        } else {
            length = str = runtime.getNil();
        }

        if(!length.isNil()) {
            len = RubyNumeric.num2long(length);
        }
        if(!str.isNil()) {
            str = str.convertToString();
            ((RubyString)str).modify();
            ((RubyString)str).getByteList().length(0);
            args[1] = runtime.getNil();
        }
        while(true) {
            if(data.currentFile == null && !data.nextArgsFile(context)) {
                return str;
            }
            if(!(data.currentFile instanceof RubyIO)) {
                tmp = data.currentFile.callMethod(context, "read", args);
            } else {
                tmp = ((RubyIO)data.currentFile).read(args);
            }
            if(str.isNil()) {
                str = tmp;
            } else if(!tmp.isNil()) {
                ((RubyString)str).append(tmp);
            }
            if(tmp.isNil() || length.isNil()) {
                data.currentFile = null;
                continue;
            } else if(args.length >= 1) {
                if(((RubyString)str).getByteList().length() < len) {
                    len -= ((RubyString)str).getByteList().length();
                    args[0] = runtime.newFixnum(len);
                    continue;
                }
            }
            return str;
        }
    }

    @JRubyMethod(name = "filename", alias = {"path"})
    public static RubyString filename(ThreadContext context, IRubyObject recv) {
        return (RubyString) context.getRuntime().getGlobalVariables().get("$FILENAME");
    }

    @JRubyMethod(name = "to_s") 
    public static IRubyObject to_s(IRubyObject recv) {
        return recv.getRuntime().newString("ARGF");
    }
}
/*
 **** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001 Chad Fowler <chadfowler@chadfowler.com>
 * Copyright (C) 2001-2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2002-2005 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004-2005 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2006 Ola Bini <Ola.Bini@ki.se>
 * Copyright (C) 2006 Daniel Steer <damian.steer@hp.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.lang.reflect.Array;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.common.IRubyWarnings.ID;
import org.jruby.javasupport.JavaUtil;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.marshal.MarshalStream;
import org.jruby.runtime.marshal.UnmarshalStream;
import org.jruby.util.ByteList;
import org.jruby.util.Pack;

/**
 * The implementation of the built-in class Array in Ruby.
 *
 * Concurrency: no synchronization is required among readers, but
 * all users must synchronize externally with writers.
 *
 */
@JRubyClass(name="Array")
public class RubyArray extends RubyObject implements List {

    public static RubyClass createArrayClass(Ruby runtime) {
        RubyClass arrayc = runtime.defineClass("Array", runtime.getObject(), ARRAY_ALLOCATOR);
        runtime.setArray(arrayc);
        arrayc.index = ClassIndex.ARRAY;
        arrayc.kindOf = new RubyModule.KindOf() {
            @Override
            public boolean isKindOf(IRubyObject obj, RubyModule type) {
                return obj instanceof RubyArray;
            }
        };

        arrayc.includeModule(runtime.getEnumerable());
        arrayc.defineAnnotatedMethods(RubyArray.class);

        return arrayc;
    }

    private static ObjectAllocator ARRAY_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return new RubyArray(runtime, klass);
        }
    };

    @Override
    public int getNativeTypeIndex() {
        return ClassIndex.ARRAY;
    }

    private final void concurrentModification() {
        throw getRuntime().newConcurrencyError("Detected invalid array contents due to unsynchronized modifications with concurrent users");
    }

    /** rb_ary_s_create
     * 
     */
    @JRubyMethod(name = "[]", rest = true, frame = true, meta = true)
    public static IRubyObject create(IRubyObject klass, IRubyObject[] args, Block block) {
        RubyArray arr = (RubyArray) ((RubyClass) klass).allocate();
        arr.callInit(IRubyObject.NULL_ARRAY, block);
    
        if (args.length > 0) {
            arr.alloc(args.length);
            System.arraycopy(args, 0, arr.values, 0, args.length);
            arr.realLength = args.length;
        }
        return arr;
    }

    /** rb_ary_new2
     *
     */
    public static final RubyArray newArray(final Ruby runtime, final long len) {
        return new RubyArray(runtime, len);
    }
    public static final RubyArray newArrayLight(final Ruby runtime, final long len) {
        return new RubyArray(runtime, len, false);
    }

    /** rb_ary_new
     *
     */
    public static final RubyArray newArray(final Ruby runtime) {
        return new RubyArray(runtime, ARRAY_DEFAULT_SIZE);
    }

    /** rb_ary_new
     *
     */
    public static final RubyArray newArrayLight(final Ruby runtime) {
        /* Ruby arrays default to holding 16 elements, so we create an
         * ArrayList of the same size if we're not told otherwise
         */
        RubyArray arr = new RubyArray(runtime, false);
        arr.alloc(ARRAY_DEFAULT_SIZE);
        return arr;
    }

    public static RubyArray newArray(Ruby runtime, IRubyObject obj) {
        return new RubyArray(runtime, new IRubyObject[] { obj });
    }

    public static RubyArray newArrayLight(Ruby runtime, IRubyObject obj) {
        return new RubyArray(runtime, new IRubyObject[] { obj }, false);
    }

    /** rb_assoc_new
     *
     */
    public static RubyArray newArray(Ruby runtime, IRubyObject car, IRubyObject cdr) {
        return new RubyArray(runtime, new IRubyObject[] { car, cdr });
    }
    
    public static RubyArray newEmptyArray(Ruby runtime) {
        return new RubyArray(runtime, NULL_ARRAY);
    }

    /** rb_ary_new4, rb_ary_new3
     *   
     */
    public static RubyArray newArray(Ruby runtime, IRubyObject[] args) {
        RubyArray arr = new RubyArray(runtime, args.length);
        System.arraycopy(args, 0, arr.values, 0, args.length);
        arr.realLength = args.length;
        return arr;
    }
    
    public static RubyArray newArrayNoCopy(Ruby runtime, IRubyObject[] args) {
        return new RubyArray(runtime, args);
    }
    
    public static RubyArray newArrayNoCopy(Ruby runtime, IRubyObject[] args, int begin) {
        return new RubyArray(runtime, args, begin);
    }
    
    public static RubyArray newArrayNoCopyLight(Ruby runtime, IRubyObject[] args) {
        RubyArray arr = new RubyArray(runtime, false);
        arr.values = args;
        arr.realLength = args.length;
        return arr;
    }

    public static RubyArray newArray(Ruby runtime, Collection collection) {
        RubyArray arr = new RubyArray(runtime, collection.size());
        collection.toArray(arr.values);
        arr.realLength = arr.values.length;
        return arr;
    }

    public static final int ARRAY_DEFAULT_SIZE = 16;    

    // volatile to ensure that initial nil-fill is visible to other threads
    private volatile IRubyObject[] values;

    private static final int TMPLOCK_ARR_F = 1 << 9;
    private static final int TMPLOCK_OR_FROZEN_ARR_F = TMPLOCK_ARR_F | FROZEN_F;

    private volatile boolean isShared = false;
    private int begin = 0;
    private int realLength = 0;

    /* 
     * plain internal array assignment
     */
    private RubyArray(Ruby runtime, IRubyObject[] vals) {
        super(runtime, runtime.getArray());
        values = vals;
        realLength = vals.length;
    }

    /* 
     * plain internal array assignment
     */
    private RubyArray(Ruby runtime, IRubyObject[] vals, boolean objectSpace) {
        super(runtime, runtime.getArray(), objectSpace);
        values = vals;
        realLength = vals.length;
    }

    /* 
     * plain internal array assignment
     */
    private RubyArray(Ruby runtime, IRubyObject[] vals, int begin) {
        super(runtime, runtime.getArray());
        this.values = vals;
        this.begin = begin;
        this.realLength = vals.length - begin;
        this.isShared = true;
    }
    
    /* rb_ary_new2
     * just allocates the internal array
     */
    private RubyArray(Ruby runtime, long length) {
        super(runtime, runtime.getArray());
        checkLength(length);
        alloc((int) length);
    }

    private RubyArray(Ruby runtime, long length, boolean objectspace) {
        super(runtime, runtime.getArray(), objectspace);
        checkLength(length);
        alloc((int)length);
    }     

    /* rb_ary_new3, rb_ary_new4
     * allocates the internal array of size length and copies the 'length' elements
     */
    public RubyArray(Ruby runtime, long length, IRubyObject[] vals) {
        super(runtime, runtime.getArray());
        checkLength(length);
        int ilength = (int) length;
        alloc(ilength);
        if (ilength > 0 && vals.length > 0) System.arraycopy(vals, 0, values, 0, ilength);

        realLength = ilength;
    }

    /* NEWOBJ and OBJSETUP equivalent
     * fastest one, for shared arrays, optional objectspace
     */
    private RubyArray(Ruby runtime, boolean objectSpace) {
        super(runtime, runtime.getArray(), objectSpace);
    }

    private RubyArray(Ruby runtime) {
        super(runtime, runtime.getArray());
        alloc(ARRAY_DEFAULT_SIZE);
    }

    public RubyArray(Ruby runtime, RubyClass klass) {
        super(runtime, klass);
        alloc(ARRAY_DEFAULT_SIZE);
    }
    
    /* Array constructors taking the MetaClass to fulfil MRI Array subclass behaviour
     * 
     */
    private RubyArray(Ruby runtime, RubyClass klass, int length) {
        super(runtime, klass);
        alloc(length);
    }
    
    private RubyArray(Ruby runtime, RubyClass klass, long length) {
        super(runtime, klass);
        checkLength(length);
        alloc((int)length);
    }

    private RubyArray(Ruby runtime, RubyClass klass, long length, boolean objectspace) {
        super(runtime, klass, objectspace);
        checkLength(length);
        alloc((int)length);
    }    

    private RubyArray(Ruby runtime, RubyClass klass, boolean objectSpace) {
        super(runtime, klass, objectSpace);
    }
    
    private RubyArray(Ruby runtime, RubyClass klass, RubyArray original) {
        super(runtime, klass);
        realLength = original.realLength;
        alloc(realLength);
        try {
            System.arraycopy(original.values, original.begin, values, 0, realLength);
        } catch (ArrayIndexOutOfBoundsException e) {
            concurrentModification();
        }
    }
    
    private final IRubyObject[] reserve(int length) {
        final IRubyObject[] arr = new IRubyObject[length];
        Arrays.fill(arr, getRuntime().getNil());
        return arr;
    }

    private final void alloc(int length) {
        final IRubyObject[] newValues = new IRubyObject[length];
        Arrays.fill(newValues, getRuntime().getNil());
        values = newValues;
    }

    private final void realloc(int newLength) {
        IRubyObject[] reallocated = new IRubyObject[newLength];
        Arrays.fill(reallocated, getRuntime().getNil());
        try {
            System.arraycopy(values, 0, reallocated, 0, newLength > realLength ? realLength : newLength);
        } catch (ArrayIndexOutOfBoundsException e) {
            concurrentModification();
        }
        values = reallocated;
    }

    private final void checkLength(long length) {
        if (length < 0) {
            throw getRuntime().newArgumentError("negative array size (or size too big)");
        }

        if (length >= Integer.MAX_VALUE) {
            throw getRuntime().newArgumentError("array size too big");
        }
    }

    /** Getter for property list.
     * @return Value of property list.
     */
    public List getList() {
        return Arrays.asList(toJavaArray()); 
    }

    public int getLength() {
        return realLength;
    }

    public IRubyObject[] toJavaArray() {
        IRubyObject[] copy = reserve(realLength);
        try {
            System.arraycopy(values, begin, copy, 0, realLength);
        } catch (ArrayIndexOutOfBoundsException e) {
            concurrentModification();
        }
        return copy;
    }
    
    public IRubyObject[] toJavaArrayUnsafe() {
        return !isShared ? values : toJavaArray();
    }    

    public IRubyObject[] toJavaArrayMaybeUnsafe() {
        return (!isShared && begin == 0 && values.length == realLength) ? values : toJavaArray();
    }    

    /** rb_ary_make_shared
    *
    */
    private final RubyArray makeShared(int beg, int len, RubyClass klass) {
        return makeShared(beg, len, klass, klass.getRuntime().isObjectSpaceEnabled());
    }    
    
    /** rb_ary_make_shared
     *
     */
    private final RubyArray makeShared(int beg, int len, RubyClass klass, boolean objectSpace) {
        RubyArray sharedArray = new RubyArray(getRuntime(), klass, objectSpace);
        isShared = true;
        sharedArray.values = values;
        sharedArray.isShared = true;
        sharedArray.begin = beg;
        sharedArray.realLength = len;
        return sharedArray;
    }

    /** rb_ary_modify_check
     *
     */
    private final void modifyCheck() {
        if ((flags & TMPLOCK_OR_FROZEN_ARR_F) != 0) {
            if ((flags & FROZEN_F) != 0) throw getRuntime().newFrozenError("array");           
            if ((flags & TMPLOCK_ARR_F) != 0) throw getRuntime().newTypeError("can't modify array during iteration");
        }
        if (!isTaint() && getRuntime().getSafeLevel() >= 4) {
            throw getRuntime().newSecurityError("Insecure: can't modify array");
        }
    }

    /** rb_ary_modify
     *
     */
    private final void modify() {
        modifyCheck();
        if (isShared) {
            IRubyObject[] vals = reserve(realLength);
            isShared = false;
            try {
                System.arraycopy(values, begin, vals, 0, realLength);
            } catch (ArrayIndexOutOfBoundsException e) {
                concurrentModification();
            }
            begin = 0;            
            values = vals;
        }
    }

    /*  ================
     *  Instance Methods
     *  ================ 
     */

    /** rb_ary_initialize
     * 
     */
    @JRubyMethod(name = "initialize", required = 0, optional = 2, frame = true, visibility = Visibility.PRIVATE)
    public IRubyObject initialize(ThreadContext context, IRubyObject[] args, Block block) {
        int argc = args.length;
        Ruby runtime = getRuntime();

        if (argc == 0) {
            modifyCheck();
            realLength = 0;
            if (block.isGiven()) runtime.getWarnings().warn(ID.BLOCK_UNUSED, "given block not used");

    	    return this;
    	}

        if (argc == 1 && !(args[0] instanceof RubyFixnum)) {
            IRubyObject val = args[0].checkArrayType();
            if (!val.isNil()) {
                replace(val);
                return this;
            }
        }

        long len = RubyNumeric.num2long(args[0]);

        if (len < 0) throw runtime.newArgumentError("negative array size");

        if (len >= Integer.MAX_VALUE) throw runtime.newArgumentError("array size too big");

        int ilen = (int) len;

        modify();

        if (ilen > values.length) values = reserve(ilen);

        if (block.isGiven()) {
            if (argc == 2) {
                runtime.getWarnings().warn(ID.BLOCK_BEATS_DEFAULT_VALUE, "block supersedes default value argument");
            }

            for (int i = 0; i < ilen; i++) {
                store(i, block.yield(context, new RubyFixnum(runtime, i)));
                realLength = i + 1;
            }
        } else {
            try {
                Arrays.fill(values, 0, ilen, (argc == 2) ? args[1] : runtime.getNil());
            } catch (ArrayIndexOutOfBoundsException e) {
                concurrentModification();
            }
            realLength = ilen;
        }
    	return this;
    }

    /** rb_ary_initialize_copy
     * 
     */
    @JRubyMethod(name = {"initialize_copy"}, required = 1, visibility=Visibility.PRIVATE)
    @Override
    public IRubyObject initialize_copy(IRubyObject orig) {
        return this.replace(orig);
    }
    
    /** rb_ary_replace
     *
     */
    @JRubyMethod(name = {"replace"}, required = 1)
    public IRubyObject replace(IRubyObject orig) {
        modifyCheck();

        RubyArray origArr = orig.convertToArray();

        if (this == orig) return this;

        origArr.isShared = true;
        isShared = true;
        values = origArr.values;
        realLength = origArr.realLength;
        begin = origArr.begin;


        return this;
    }

    /** rb_ary_to_s
     *
     */
    @JRubyMethod(name = "to_s")
    @Override
    public IRubyObject to_s() {
        if (realLength == 0) return RubyString.newEmptyString(getRuntime());

        return join(getRuntime().getCurrentContext(), getRuntime().getGlobalVariables().get("$,"));
    }

    
    public boolean includes(ThreadContext context, IRubyObject item) {
        int begin = this.begin;
        
        for (int i = begin; i < begin + realLength; i++) {
            final IRubyObject value;
            try {
                value = values[i];
            } catch (ArrayIndexOutOfBoundsException e) {
                concurrentModification();
                continue;
            }
            if (equalInternal(context, value, item)) return true;
    	}
        
        return false;
    }

    /** rb_ary_hash
     * 
     */
    @JRubyMethod(name = "hash")
    public RubyFixnum hash(ThreadContext context) {
        int h = realLength;

        Ruby runtime = getRuntime();
        int begin = this.begin;
        for (int i = begin; i < begin + realLength; i++) {
            h = (h << 1) | (h < 0 ? 1 : 0);
            final IRubyObject value;
            try {
                value = values[i];
            } catch (ArrayIndexOutOfBoundsException e) {
                concurrentModification();
                continue;
            }
            h ^= RubyNumeric.num2long(value.callMethod(context, MethodIndex.HASH, "hash"));
        }

        return runtime.newFixnum(h);
    }

    /** rb_ary_store
     *
     */
    public final IRubyObject store(long index, IRubyObject value) {
        if (index < 0) {
            index += realLength;
            if (index < 0) {
                throw getRuntime().newIndexError("index " + (index - realLength) + " out of array");
            }
        }

        modify();

        if (index >= realLength) {
            if (index >= values.length) {
                long newLength = values.length >> 1;

                if (newLength < ARRAY_DEFAULT_SIZE) newLength = ARRAY_DEFAULT_SIZE;

                newLength += index;
                if (index >= Integer.MAX_VALUE || newLength >= Integer.MAX_VALUE) {
                    throw getRuntime().newArgumentError("index too big");
                }
                realloc((int) newLength);
            }
            
            realLength = (int) index + 1;
        }

        try {
            values[(int) index] = value;
        } catch (ArrayIndexOutOfBoundsException e) {
            concurrentModification();
        }
        return value;
    }

    /** rb_ary_elt
     *
     */
    private final IRubyObject elt(long offset) {
        if (offset < 0 || offset >= realLength) {
            return getRuntime().getNil();
        }
        try {
            return values[begin + (int)offset];
        } catch (ArrayIndexOutOfBoundsException e) {
            concurrentModification();
            return getRuntime().getNil();
        }
    }

    /** rb_ary_entry
     *
     */
    public final IRubyObject entry(long offset) {
        return (offset < 0 ) ? elt(offset + realLength) : elt(offset);
    }


    /** rb_ary_entry
     *
     */
    public final IRubyObject entry(int offset) {
        return (offset < 0 ) ? elt(offset + realLength) : elt(offset);
    }

    public final IRubyObject eltInternal(int offset) {
        return values[begin + offset];
    }
    
    public final IRubyObject eltInternalSet(int offset, IRubyObject item) {
        return values[begin + offset] = item;
    }

    /**
     * Variable arity version for compatibility. Not bound to a Ruby method.
     * @deprecated Use the versions with zero, one, or two args.
     */
    public IRubyObject fetch(ThreadContext context, IRubyObject[] args, Block block) {
        switch (args.length) {
        case 1:
            return fetch(context, args[0], block);
        case 2:
            return fetch(context, args[0], args[1], block);
        default:
            Arity.raiseArgumentError(getRuntime(), args.length, 1, 2);
            return null; // not reached
        }
    }    

    /** rb_ary_fetch
     *
     */
    @JRubyMethod(name = "fetch", frame = true)
    public IRubyObject fetch(ThreadContext context, IRubyObject arg0, Block block) {
        long index = RubyNumeric.num2long(arg0);

        if (index < 0) index += realLength;
        if (index < 0 || index >= realLength) {
            if (block.isGiven()) return block.yield(context, arg0);
            throw getRuntime().newIndexError("index " + index + " out of array");
        }
        
        try {
            return values[begin + (int) index];
        } catch (ArrayIndexOutOfBoundsException e) {
            concurrentModification();
            return getRuntime().getNil();
        }
    }

    /** rb_ary_fetch
    *
    */
   @JRubyMethod(name = "fetch", frame = true)
   public IRubyObject fetch(ThreadContext context, IRubyObject arg0, IRubyObject arg1, Block block) {
       if (block.isGiven()) getRuntime().getWarnings().warn(ID.BLOCK_BEATS_DEFAULT_VALUE, "block supersedes default value argument");

       long index = RubyNumeric.num2long(arg0);

       if (index < 0) index += realLength;
       if (index < 0 || index >= realLength) {
           if (block.isGiven()) return block.yield(context, arg0);
           return arg1;
       }
       
       try {
           return values[begin + (int) index];
       } catch (ArrayIndexOutOfBoundsException e) {
           concurrentModification();
           return getRuntime().getNil();
       }
   }    

    /** rb_ary_to_ary
     * 
     */
    private static RubyArray aryToAry(IRubyObject obj) {
        if (obj instanceof RubyArray) return (RubyArray) obj;

        if (obj.respondsTo("to_ary")) return obj.convertToArray();

        RubyArray arr = new RubyArray(obj.getRuntime(), false); // possibly should not in object space
        arr.alloc(1);
        arr.values[0] = obj;
        arr.realLength = 1;
        return arr;
    }

    /** rb_ary_splice
     * 
     */
    private final void splice(long beg, long len, IRubyObject rpl) {
        if (len < 0) throw getRuntime().newIndexError("negative length (" + len + ")");

        if (beg < 0) {
            beg += realLength;
            if (beg < 0) {
                beg -= realLength;
                throw getRuntime().newIndexError("index " + beg + " out of array");
            }
        }
        
        final RubyArray rplArr;
        final int rlen;

        if (rpl == null || rpl.isNil()) {
            rplArr = null;
            rlen = 0;
        } else {
            rplArr = aryToAry(rpl);
            rlen = rplArr.realLength;
        }

        modify();

        if (beg >= realLength) {
            len = beg + rlen;

            if (len >= values.length) {
                int tryNewLength = values.length + (values.length >> 1);
                realloc(len > tryNewLength ? (int)len : tryNewLength);
            }

            realLength = (int) len;
        } else {
            if (beg + len > realLength) len = realLength - beg;

            long alen = realLength + rlen - len;
            if (alen >= values.length) {
                int tryNewLength = values.length + (values.length >> 1);
                realloc(alen > tryNewLength ? (int)alen : tryNewLength);
            }

            if (len != rlen) {
                try {
                    System.arraycopy(values, (int) (beg + len), values, (int) beg + rlen, realLength - (int) (beg + len));
                } catch (ArrayIndexOutOfBoundsException e) {
                    concurrentModification();
                }
                realLength = (int) alen;
            }
        }

        if (rlen > 0) {
            try {
                System.arraycopy(rplArr.values, rplArr.begin, values, (int) beg, rlen);
            } catch (ArrayIndexOutOfBoundsException e) {
                    concurrentModification();
            }
        }
    }

    /** rb_ary_splice
     * 
     */
    private final void spliceOne(long beg, long len, IRubyObject rpl) {
        if (len < 0) throw getRuntime().newIndexError("negative length (" + len + ")");

        if (beg < 0) {
            beg += realLength;
            if (beg < 0) {
                beg -= realLength;
                throw getRuntime().newIndexError("index " + beg + " out of array");
            }
        }

        modify();

        if (beg >= realLength) {
            len = beg + 1;

            if (len >= values.length) {
                int tryNewLength = values.length + (values.length >> 1);
                realloc(len > tryNewLength ? (int)len : tryNewLength);
            }

            realLength = (int) len;
        } else {
            if (beg + len > realLength) len = realLength - beg;

            int alen = realLength + 1 - (int)len;
            if (alen >= values.length) {
                int tryNewLength = values.length + (values.length >> 1);
                realloc(alen > tryNewLength ? alen : tryNewLength);
            }

            if (len != 1) {
                try {
                    System.arraycopy(values, (int) (beg + len), values, (int) beg + 1, realLength - (int) (beg + len));
                } catch (ArrayIndexOutOfBoundsException e) {
                    concurrentModification();
                }
                realLength = alen;
            }
        }

        try {
            values[(int)beg] = rpl;
        } catch (ArrayIndexOutOfBoundsException e) {
            concurrentModification();
        }
    }

    @JRubyMethod
    public IRubyObject insert() {
        throw getRuntime().newArgumentError(0, 1);
    }

    /** rb_ary_insert
     * 
     */
    @JRubyMethod
    public IRubyObject insert(IRubyObject arg) {
        return this;
    }

    /** rb_ary_insert
     * 
     */
    @JRubyMethod
    public IRubyObject insert(IRubyObject arg1, IRubyObject arg2) {
        long pos = RubyNumeric.num2long(arg1);

        if (pos == -1) pos = realLength;
        if (pos < 0) pos++;
        
        spliceOne(pos, 0, arg2); // rb_ary_new4
        
        return this;
    }

    /** rb_ary_insert
     * 
     */
    @JRubyMethod(name = "insert", required = 1, rest = true)
    public IRubyObject insert(IRubyObject[] args) {
        if (args.length == 1) return this;

        long pos = RubyNumeric.num2long(args[0]);

        if (pos == -1) pos = realLength;
        if (pos < 0) pos++;

        RubyArray inserted = new RubyArray(getRuntime(), false);
        inserted.values = args;
        inserted.begin = 1;
        inserted.realLength = args.length - 1;
        
        splice(pos, 0, inserted); // rb_ary_new4
        
        return this;
    }

    /** rb_ary_dup
     * 
     */
    public final RubyArray aryDup() {
        RubyArray dup = new RubyArray(getRuntime(), getMetaClass(), this);
        dup.flags |= flags & TAINTED_F; // from DUP_SETUP
        // rb_copy_generic_ivar from DUP_SETUP here ...unlikely..
        return dup;
    }

    /** rb_ary_transpose
     * 
     */
    @JRubyMethod(name = "transpose")
    public RubyArray transpose() {
        RubyArray tmp, result = null;

        int alen = realLength;
        if (alen == 0) return aryDup();
    
        Ruby runtime = getRuntime();
        int elen = -1;
        int end = begin + alen;
        for (int i = begin; i < end; i++) {
            tmp = elt(i).convertToArray();
            if (elen < 0) {
                elen = tmp.realLength;
                result = new RubyArray(runtime, elen);
                for (int j = 0; j < elen; j++) {
                    result.store(j, new RubyArray(runtime, alen));
                }
            } else if (elen != tmp.realLength) {
                throw runtime.newIndexError("element size differs (" + tmp.realLength
                        + " should be " + elen + ")");
            }
            for (int j = 0; j < elen; j++) {
                ((RubyArray) result.elt(j)).store(i - begin, tmp.elt(j));
            }
        }
        return result;
    }

    /** rb_values_at (internal)
     * 
     */
    private final IRubyObject values_at(long olen, IRubyObject[] args) {
        RubyArray result = new RubyArray(getRuntime(), args.length);

        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof RubyFixnum) {
                result.append(entry(((RubyFixnum)args[i]).getLongValue()));
                continue;
            }

            long beglen[];
            if (!(args[i] instanceof RubyRange)) {
            } else if ((beglen = ((RubyRange) args[i]).begLen(olen, 0)) == null) {
                continue;
            } else {
                int beg = (int) beglen[0];
                int len = (int) beglen[1];
                int end = begin + len;
                for (int j = begin; j < end; j++) {
                    result.append(entry(j + beg));
                }
                continue;
            }
            result.append(entry(RubyNumeric.num2long(args[i])));
        }

        return result;
    }

    /** rb_values_at
     * 
     */
    @JRubyMethod(name = "values_at", rest = true)
    public IRubyObject values_at(IRubyObject[] args) {
        return values_at(realLength, args);
    }

    /** rb_ary_subseq
     *
     */
    public IRubyObject subseq(long beg, long len) {
        if (beg > realLength || beg < 0 || len < 0) return getRuntime().getNil();

        if (beg + len > realLength) {
            len = realLength - beg;
            
            if (len < 0) len = 0;
        }
        
        if (len == 0) return new RubyArray(getRuntime(), getMetaClass(), 0);

        return makeShared(begin + (int) beg, (int) len, getMetaClass());
    }

    /** rb_ary_subseq
     *
     */
    public IRubyObject subseqLight(long beg, long len) {
        if (beg > realLength || beg < 0 || len < 0) return getRuntime().getNil();

        if (beg + len > realLength) {
            len = realLength - beg;
            
            if (len < 0) len = 0;
        }
        
        if (len == 0) return new RubyArray(getRuntime(), getMetaClass(), 0, false);

        return makeShared(begin + (int) beg, (int) len, getMetaClass(), false);
    }

    /** rb_ary_length
     *
     */
    @JRubyMethod(name = "length", alias = "size")
    public RubyFixnum length() {
        return getRuntime().newFixnum(realLength);
    }

    /** rb_ary_push - specialized rb_ary_store 
     *
     */
    @JRubyMethod(name = "<<", required = 1)
    public RubyArray append(IRubyObject item) {
        modify();
        
        if (realLength == values.length) {
        if (realLength == Integer.MAX_VALUE) throw getRuntime().newArgumentError("index too big");
            
            long newLength = values.length + (values.length >> 1);
            if ( newLength > Integer.MAX_VALUE ) {
                newLength = Integer.MAX_VALUE;
            }else if ( newLength < ARRAY_DEFAULT_SIZE ) {
                newLength = ARRAY_DEFAULT_SIZE;
            }

            realloc((int) newLength);
        }
        
        try {
            values[realLength++] = item;
        } catch (ArrayIndexOutOfBoundsException e) {
            concurrentModification();
        }
        return this;
    }

    /** rb_ary_push_m
     * FIXME: Whis is this named "push_m"?
     */
    @JRubyMethod(name = "push", rest = true)
    public RubyArray push_m(IRubyObject[] items) {
        for (int i = 0; i < items.length; i++) {
            append(items[i]);
        }
        
        return this;
    }

    /** rb_ary_pop
     *
     */
    @JRubyMethod(name = "pop")
    public IRubyObject pop() {
        modifyCheck();
        
        if (realLength == 0) return getRuntime().getNil();

        if (isShared) {
            try {
                return values[begin + --realLength];
            } catch (ArrayIndexOutOfBoundsException e) {
                concurrentModification();
                return getRuntime().getNil();
            }
        } else {
            int index = begin + --realLength;
            try {
                final IRubyObject obj = values[index];
                values[index] = getRuntime().getNil();
                return obj;
            } catch (ArrayIndexOutOfBoundsException e) {
                concurrentModification();
                return getRuntime().getNil();
            }
        }
    }

    /** rb_ary_shift
     *
     */
    @JRubyMethod(name = "shift")
    public IRubyObject shift() {
        modify();

        if (realLength == 0) return getRuntime().getNil();

        final IRubyObject obj;
        try {
            obj = values[begin];
            values[begin] = getRuntime().getNil();
        } catch (ArrayIndexOutOfBoundsException e) {
            concurrentModification();
            return getRuntime().getNil();
        }

        isShared = true;

        begin++;
        realLength--;

        return obj;
    }

    /** rb_ary_unshift
     *
     */
    public RubyArray unshift(IRubyObject item) {
        modify();

        if (realLength == values.length) {
            int newLength = values.length >> 1;
            if (newLength < ARRAY_DEFAULT_SIZE) newLength = ARRAY_DEFAULT_SIZE;

            newLength += values.length;
            realloc(newLength);
        }
        try {
            System.arraycopy(values, 0, values, 1, realLength);
        } catch (ArrayIndexOutOfBoundsException e) {
            concurrentModification();
        }

        realLength++;
        values[0] = item;

        return this;
    }

    /** rb_ary_unshift_m
     *
     */
    @JRubyMethod(name = "unshift", rest = true)
    public RubyArray unshift_m(IRubyObject[] items) {
        long len = realLength;

        if (items.length == 0) return this;

        store(len + items.length - 1, getRuntime().getNil());

        try {
            // it's safe to use zeroes here since modified by store()
            System.arraycopy(values, 0, values, items.length, (int) len);
            System.arraycopy(items, 0, values, 0, items.length);
        } catch (ArrayIndexOutOfBoundsException e) {
            concurrentModification();
        }
        
        return this;
    }

    /** rb_ary_includes
     * 
     */
    @JRubyMethod(name = "include?", required = 1)
    public RubyBoolean include_p(ThreadContext context, IRubyObject item) {
        return context.getRuntime().newBoolean(includes(context, item));
    }

    /** rb_ary_frozen_p
     *
     */
    @JRubyMethod(name = "frozen?")
    @Override
    public RubyBoolean frozen_p(ThreadContext context) {
        return context.getRuntime().newBoolean(isFrozen() || (flags & TMPLOCK_ARR_F) != 0);
    }

    /**
     * Variable arity version for compatibility. Not bound to a Ruby method.
     * @deprecated Use the versions with zero, one, or two args.
     */
    public IRubyObject aref(IRubyObject[] args) {
        switch (args.length) {
        case 1:
            return aref(args[0]);
        case 2:
            return aref(args[0], args[1]);
        default:
            Arity.raiseArgumentError(getRuntime(), args.length, 1, 2);
            return null; // not reached
        }
    }

    /** rb_ary_aref
     */
    @JRubyMethod(name = {"[]", "slice"})
    public IRubyObject aref(IRubyObject arg0) {
        if (arg0 instanceof RubyFixnum) return entry(((RubyFixnum)arg0).getLongValue());
        if (arg0 instanceof RubySymbol) throw getRuntime().newTypeError("Symbol as array index");
            
        long[] beglen;
        if (!(arg0 instanceof RubyRange)) {
        } else if ((beglen = ((RubyRange) arg0).begLen(realLength, 0)) == null) {
            return getRuntime().getNil();
        } else {
            return subseq(beglen[0], beglen[1]);
        }
        return entry(RubyNumeric.num2long(arg0));            
    }        

    /** rb_ary_aref
     */
    @JRubyMethod(name = {"[]", "slice"})
    public IRubyObject aref(IRubyObject arg0, IRubyObject arg1) {
        if (arg0 instanceof RubySymbol) throw getRuntime().newTypeError("Symbol as array index");

        long beg = RubyNumeric.num2long(arg0);
        if (beg < 0) beg += realLength;

        return subseq(beg, RubyNumeric.num2long(arg1));
    }

    /**
     * Variable arity version for compatibility. Not bound to a Ruby method.
     * @deprecated Use the versions with zero, one, or two args.
     */
    public IRubyObject aset(IRubyObject[] args) {
        switch (args.length) {
        case 2:
            return aset(args[0], args[1]);
        case 3:
            return aset(args[0], args[1], args[2]);
        default:
            throw getRuntime().newArgumentError("wrong number of arguments (" + args.length + " for 2)");
        }
    }

    /** rb_ary_aset
     *
     */
    @JRubyMethod(name = "[]=")
    public IRubyObject aset(IRubyObject arg0, IRubyObject arg1) {
        if (arg0 instanceof RubyFixnum) {
            store(((RubyFixnum)arg0).getLongValue(), arg1);
            return arg1;
        }
        if (arg0 instanceof RubyRange) {
            long[] beglen = ((RubyRange) arg0).begLen(realLength, 1);
            splice(beglen[0], beglen[1], arg1);
            return arg1;
        }
        if (arg0 instanceof RubySymbol) throw getRuntime().newTypeError("Symbol as array index");

        store(RubyNumeric.num2long(arg0), arg1);
        return arg1;
    }

    /** rb_ary_aset
    *
    */
    @JRubyMethod(name = "[]=")
    public IRubyObject aset(IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) {
        if (arg0 instanceof RubySymbol) throw getRuntime().newTypeError("Symbol as array index");
        if (arg1 instanceof RubySymbol) throw getRuntime().newTypeError("Symbol as subarray length");
        splice(RubyNumeric.num2long(arg0), RubyNumeric.num2long(arg1), arg2);
        return arg2;
    }

    /** rb_ary_at
     *
     */
    @JRubyMethod(name = "at", required = 1)
    public IRubyObject at(IRubyObject pos) {
        return entry(RubyNumeric.num2long(pos));
    }

	/** rb_ary_concat
     *
     */
    @JRubyMethod(name = "concat", required = 1)
    public RubyArray concat(IRubyObject obj) {
        RubyArray ary = obj.convertToArray();
        
        if (ary.realLength > 0) splice(realLength, 0, ary);

        return this;
    }

    /** inspect_ary
     * 
     */
    private IRubyObject inspectAry(ThreadContext context) {
        ByteList buffer = new ByteList();
        buffer.append('[');
        boolean tainted = isTaint();

        for (int i = 0; i < realLength; i++) {
            if (i > 0) buffer.append(',').append(' ');

            RubyString str = inspect(context, values[begin + i]);
            if (str.isTaint()) tainted = true;
            buffer.append(str.getByteList());
        }
        buffer.append(']');

        RubyString str = getRuntime().newString(buffer);
        if (tainted) str.setTaint(true);

        return str;
    }

    /** rb_ary_inspect
    *
    */
    @JRubyMethod(name = "inspect")
    @Override
    public IRubyObject inspect() {
        if (realLength == 0) return getRuntime().newString("[]");
        if (getRuntime().isInspecting(this)) return  getRuntime().newString("[...]");

        try {
            getRuntime().registerInspecting(this);
            return inspectAry(getRuntime().getCurrentContext());
        } finally {
            getRuntime().unregisterInspecting(this);
        }
    }

    /**
     * Variable arity version for compatibility. Not bound to a Ruby method.
     * @deprecated Use the versions with zero, one, or two args.
     */
    public IRubyObject first(IRubyObject[] args) {
        switch (args.length) {
        case 0:
            return first();
        case 1:
            return first(args[0]);
        default:
            Arity.raiseArgumentError(getRuntime(), args.length, 0, 1);
            return null; // not reached
        }
    }

    /** rb_ary_first
     *
     */
    @JRubyMethod(name = "first")
    public IRubyObject first() {
        if (realLength == 0) return getRuntime().getNil();
        return values[begin];
    }

    /** rb_ary_first
    *
    */
    @JRubyMethod(name = "first")
    public IRubyObject first(IRubyObject arg0) {
        long n = RubyNumeric.num2long(arg0);
        if (n > realLength) {
            n = realLength;
        } else if (n < 0) {
            throw getRuntime().newArgumentError("negative array size (or size too big)");
        }

        return makeShared(begin, (int) n, getRuntime().getArray());
    }

    /**
     * Variable arity version for compatibility. Not bound to a Ruby method.
     * @deprecated Use the versions with zero, one, or two args.
     */
    public IRubyObject last(IRubyObject[] args) {
        switch (args.length) {
        case 0:
            return last();
        case 1:
            return last(args[0]);
        default:
            Arity.raiseArgumentError(getRuntime(), args.length, 0, 1);
            return null; // not reached
        }
    }

    /** rb_ary_last
     *
     */
    @JRubyMethod(name = "last")
    public IRubyObject last() {
        if (realLength == 0) return getRuntime().getNil();
        return values[begin + realLength - 1];
    }

    /** rb_ary_last
    *
    */
    @JRubyMethod(name = "last")
    public IRubyObject last(IRubyObject arg0) {
        long n = RubyNumeric.num2long(arg0);
        if (n > realLength) {
            n = realLength;
        } else if (n < 0) {
            throw getRuntime().newArgumentError("negative array size (or size too big)");
        }

        return makeShared(begin + realLength - (int) n, (int) n, getRuntime().getArray());
    }

    /** rb_ary_each
     *
     */
    @JRubyMethod(name = "each", frame = true)
    public IRubyObject each(ThreadContext context, Block block) {
        for (int i = 0; i < realLength; i++) {
            block.yield(context, values[begin + i]);
        }
        return this;
    }

    /** rb_ary_each_index
     *
     */
    @JRubyMethod(name = "each_index", frame = true)
    public IRubyObject each_index(ThreadContext context, Block block) {
        Ruby runtime = getRuntime();
        for (int i = 0; i < realLength; i++) {
            block.yield(context, runtime.newFixnum(i));
        }
        return this;
    }

    /** rb_ary_reverse_each
     *
     */
    @JRubyMethod(name = "reverse_each", frame = true)
    public IRubyObject reverse_each(ThreadContext context, Block block) {
        int len = realLength;
        
        while(len-- > 0) {
            block.yield(context, values[begin + len]);
            
            if (realLength < len) len = realLength;
        }
        
        return this;
    }

    private IRubyObject inspectJoin(ThreadContext context, RubyArray tmp, IRubyObject sep) {
        Ruby runtime = getRuntime();

        // If already inspecting, there is no need to register/unregister again.
        if (runtime.isInspecting(this)) {
            return tmp.join(context, sep);
        }

        try {
            runtime.registerInspecting(this);
            return tmp.join(context, sep);
        } finally {
            runtime.unregisterInspecting(this);
        }
    }

    /** rb_ary_join
     *
     */
    public RubyString join(ThreadContext context, IRubyObject sep) {
        final Ruby runtime = getRuntime();

        if (realLength == 0) return RubyString.newEmptyString(getRuntime());

        boolean taint = isTaint() || sep.isTaint();

        long len = 1;
        for (int i = begin; i < begin + realLength; i++) {            
            IRubyObject value;
            try {
                value = values[i];
            } catch (ArrayIndexOutOfBoundsException e) {
                concurrentModification();
                return runtime.newString("");
            }
            IRubyObject tmp = value.checkStringType();
            len += tmp.isNil() ? 10 : ((RubyString) tmp).getByteList().length();
        }

        RubyString strSep = null;
        if (!sep.isNil()) {
            sep = strSep = sep.convertToString();
            len += strSep.getByteList().length() * (realLength - 1);
        }

        ByteList buf = new ByteList((int)len);
        for (int i = begin; i < begin + realLength; i++) {
            IRubyObject tmp;
            try {
                tmp = values[i];
            } catch (ArrayIndexOutOfBoundsException e) {
                concurrentModification();
                return runtime.newString("");
            }
            if (tmp instanceof RubyString) {
                // do nothing
            } else if (tmp instanceof RubyArray) {
                if (runtime.isInspecting(tmp)) {
                    tmp = runtime.newString("[...]");
                } else {
                    tmp = inspectJoin(context, (RubyArray)tmp, sep);
                }
            } else {
                tmp = RubyString.objAsString(context, tmp);
            }

            if (i > begin && !sep.isNil()) buf.append(strSep.getByteList());

            buf.append(tmp.asString().getByteList());
            if (tmp.isTaint()) taint = true;
        }

        RubyString result = runtime.newString(buf); 

        if (taint) result.setTaint(true);

        return result;
    }

    /** rb_ary_join_m
     *
     */
    @JRubyMethod(name = "join", optional = 1)
    public RubyString join_m(ThreadContext context, IRubyObject[] args) {
        int argc = args.length;
        IRubyObject sep = (argc == 1) ? args[0] : getRuntime().getGlobalVariables().get("$,");
        
        return join(context, sep);
    }

    /** rb_ary_to_a
     *
     */
    @JRubyMethod(name = "to_a")
    @Override
    public RubyArray to_a() {
        if(getMetaClass() != getRuntime().getArray()) {
            RubyArray dup = new RubyArray(getRuntime(), getRuntime().isObjectSpaceEnabled());

            isShared = true;
            dup.isShared = true;
            dup.values = values;
            dup.realLength = realLength; 
            dup.begin = begin;
            
            return dup;
        }        
        return this;
    }

    @JRubyMethod(name = "to_ary")
    public IRubyObject to_ary() {
    	return this;
    }

    @Override
    public RubyArray convertToArray() {
        return this;
    }
    
    @Override
    public IRubyObject checkArrayType(){
        return this;
    }

    /** rb_ary_equal
     *
     */
    @JRubyMethod(name = "==", required = 1)
    @Override
    public IRubyObject op_equal(ThreadContext context, IRubyObject obj) {
        if (this == obj) return getRuntime().getTrue();

        if (!(obj instanceof RubyArray)) {
            if (!obj.respondsTo("to_ary")) {
                return getRuntime().getFalse();
            } else {
                if (equalInternal(context, obj.callMethod(context, "to_ary"), this)) return getRuntime().getTrue();
                return getRuntime().getFalse();                
            }
        }

        RubyArray ary = (RubyArray) obj;
        if (realLength != ary.realLength) return getRuntime().getFalse();

        Ruby runtime = getRuntime();
        for (long i = 0; i < realLength; i++) {
            if (!equalInternal(context, elt(i), ary.elt(i))) return runtime.getFalse();            
        }
        return runtime.getTrue();
    }

    /** rb_ary_eql
     *
     */
    @JRubyMethod(name = "eql?", required = 1)
    public RubyBoolean eql_p(ThreadContext context, IRubyObject obj) {
        if (this == obj) return getRuntime().getTrue();
        if (!(obj instanceof RubyArray)) return getRuntime().getFalse();

        RubyArray ary = (RubyArray) obj;

        if (realLength != ary.realLength) return getRuntime().getFalse();

        Ruby runtime = getRuntime();
        for (int i = 0; i < realLength; i++) {
            if (!eqlInternal(context, elt(i), ary.elt(i))) return runtime.getFalse();
        }
        return runtime.getTrue();
    }

    /** rb_ary_compact_bang
     *
     */
    @JRubyMethod(name = "compact!")
    public IRubyObject compact_bang() {
        modify();

        int p = 0;
        int t = 0;
        int end = p + realLength;

        while (t < end) {
            if (values[t].isNil()) {
                t++;
            } else {
                values[p++] = values[t++];
            }
        }

        if (realLength == p) return getRuntime().getNil();

        realloc(p);
        realLength = p;
        return this;
    }

    /** rb_ary_compact
     *
     */
    @JRubyMethod(name = "compact")
    public IRubyObject compact() {
        RubyArray ary = aryDup();
        ary.compact_bang();
        return ary;
    }

    /** rb_ary_empty_p
     *
     */
    @JRubyMethod(name = "empty?")
    public IRubyObject empty_p() {
        return realLength == 0 ? getRuntime().getTrue() : getRuntime().getFalse();
    }

    /** rb_ary_clear
     *
     */
    @JRubyMethod(name = "clear")
    public IRubyObject rb_clear() {
        modifyCheck();

        if(isShared) {
            alloc(ARRAY_DEFAULT_SIZE);
            isShared = true;
        } else if (values.length > ARRAY_DEFAULT_SIZE << 1){
            alloc(ARRAY_DEFAULT_SIZE << 1);
        } else {
            final int begin = this.begin;
            try {
                Arrays.fill(values, begin, begin + realLength, getRuntime().getNil());
            } catch (ArrayIndexOutOfBoundsException e) {
                concurrentModification();
            }
        }

        begin = 0;
        realLength = 0;
        return this;
    }

    /** rb_ary_fill
     *
     */
    @JRubyMethod(name = "fill", optional = 3, frame = true)
    public IRubyObject fill(ThreadContext context, IRubyObject[] args, Block block) {
        IRubyObject item = null;
        IRubyObject begObj = null;
        IRubyObject lenObj = null;
        int argc = args.length;

        if (block.isGiven()) {
            Arity.checkArgumentCount(getRuntime(), args, 0, 2);
            item = null;
        	begObj = argc > 0 ? args[0] : null;
        	lenObj = argc > 1 ? args[1] : null;
        	argc++;
        } else {
            Arity.checkArgumentCount(getRuntime(), args, 1, 3);
            item = args[0];
        	begObj = argc > 1 ? args[1] : null;
        	lenObj = argc > 2 ? args[2] : null;
        }

        int beg = 0, end = 0, len = 0;
        switch (argc) {
        case 1:
            beg = 0;
            len = realLength;
            break;
        case 2:
            if (begObj instanceof RubyRange) {
                long[] beglen = ((RubyRange) begObj).begLen(realLength, 1);
                beg = (int) beglen[0];
                len = (int) beglen[1];
                break;
            }
            /* fall through */
        case 3:
            beg = begObj.isNil() ? 0 : RubyNumeric.num2int(begObj);
            if (beg < 0) {
                beg = realLength + beg;
                if (beg < 0) beg = 0;
            }
            len = (lenObj == null || lenObj.isNil()) ? realLength - beg : RubyNumeric.num2int(lenObj);
            // TODO: In MRI 1.9, an explicit check for negative length is
            // added here. IndexError is raised when length is negative.
            // See [ruby-core:12953] for more details.
            //
            // New note: This is actually under re-evaluation,
            // see [ruby-core:17483].
            break;
        }

        modify();

        // See [ruby-core:17483]
        if (len < 0) {
            return this;
        }

        if (len > Integer.MAX_VALUE - beg) {
            throw getRuntime().newArgumentError("argument too big");
        }

        end = beg + len;
        if (end > realLength) {
            if (end >= values.length) realloc(end);

            realLength = end;
        }

        if (block.isGiven()) {
            Ruby runtime = getRuntime();
            for (int i = beg; i < end; i++) {
                IRubyObject v = block.yield(context, runtime.newFixnum(i));
                if (i >= realLength) break;
                try {
                    values[i] = v;
                } catch (ArrayIndexOutOfBoundsException e) {
                    concurrentModification();
                }
            }
        } else {
            if (len > 0) {
                try {
                    Arrays.fill(values, beg, beg + len, item);
                } catch (ArrayIndexOutOfBoundsException e) {
                    concurrentModification();
                }
            }
        }

        return this;
    }

    /** rb_ary_index
     *
     */
    @JRubyMethod(name = "index", required = 1)
    public IRubyObject index(ThreadContext context, IRubyObject obj) {
        Ruby runtime = getRuntime();
        for (int i = begin; i < begin + realLength; i++) {
            if (equalInternal(context, values[i], obj)) return runtime.newFixnum(i - begin);            
        }

        return runtime.getNil();
    }

    /** rb_ary_rindex
     *
     */
    @JRubyMethod(name = "rindex", required = 1)
    public IRubyObject rindex(ThreadContext context, IRubyObject obj) {
        Ruby runtime = getRuntime();
        int i = realLength;

        while (i-- > 0) {
            if (i > realLength) {
                i = realLength;
                continue;
            }
            if (equalInternal(context, values[begin + i], obj)) return getRuntime().newFixnum(i);
        }

        return runtime.getNil();
    }

    /** rb_ary_indexes
     * 
     */
    @JRubyMethod(name = {"indexes", "indices"}, required = 1, rest = true)
    public IRubyObject indexes(IRubyObject[] args) {
        getRuntime().getWarnings().warn(ID.DEPRECATED_METHOD, "Array#indexes is deprecated; use Array#values_at", "Array#indexes", "Array#values_at");

        RubyArray ary = new RubyArray(getRuntime(), args.length);

        IRubyObject[] arefArgs = new IRubyObject[1];
        for (int i = 0; i < args.length; i++) {
            arefArgs[0] = args[i];
            ary.append(aref(arefArgs));
        }

        return ary;
    }

    /** rb_ary_reverse_bang
     *
     */
    @JRubyMethod(name = "reverse!")
    public IRubyObject reverse_bang() {
        modify();

        final int realLength = this.realLength;
        final IRubyObject[] values = this.values;
        try {
            if (realLength > 1) {
                int p1 = 0;
                int p2 = p1 + realLength - 1;

                while (p1 < p2) {
                    final IRubyObject tmp = values[p1];
                    values[p1++] = values[p2];
                    values[p2--] = tmp;
                }
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            concurrentModification();
        }
        return this;
    }

    /** rb_ary_reverse_m
     *
     */
    @JRubyMethod(name = "reverse")
    public IRubyObject reverse() {
        return aryDup().reverse_bang();
    }

    /** rb_ary_collect
     *
     */
    @JRubyMethod(name = {"collect", "map"}, frame = true)
    public RubyArray collect(ThreadContext context, Block block) {
        Ruby runtime = getRuntime();
        
        if (!block.isGiven()) return new RubyArray(getRuntime(), runtime.getArray(), this);
        
        RubyArray collect = new RubyArray(runtime, realLength);
        
        for (int i = begin; i < begin + realLength; i++) {
            collect.append(block.yield(context, values[i]));
        }
        
        return collect;
    }

    /** rb_ary_collect_bang
     *
     */
    @JRubyMethod(name = {"collect!", "map!"}, frame = true)
    public RubyArray collect_bang(ThreadContext context, Block block) {
        modify();
        for (int i = 0, len = realLength; i < len; i++) {
            store(i, block.yield(context, values[begin + i]));
        }
        return this;
    }

    /** rb_ary_select
     *
     */
    @JRubyMethod(name = "select", frame = true)
    public RubyArray select(ThreadContext context, Block block) {
        Ruby runtime = getRuntime();
        RubyArray result = new RubyArray(runtime, realLength);

        if (isShared) {
            for (int i = begin; i < begin + realLength; i++) {
                if (block.yield(context, values[i]).isTrue()) result.append(elt(i - begin));
            }
        } else {
            for (int i = 0; i < realLength; i++) {
                if (block.yield(context, values[i]).isTrue()) result.append(elt(i));
            }
        }
        return result;
    }

    /** rb_ary_delete
     *
     */
    @JRubyMethod(name = "delete", required = 1, frame = true)
    public IRubyObject delete(ThreadContext context, IRubyObject item, Block block) {
        int i2 = 0;

        Ruby runtime = getRuntime();
        for (int i1 = 0; i1 < realLength; i1++) {
            IRubyObject e = values[begin + i1];
            if (equalInternal(context, e, item)) continue;
            if (i1 != i2) store(i2, e);
            i2++;
        }

        if (realLength == i2) {
            if (block.isGiven()) return block.yield(context, item);

            return runtime.getNil();
        }

        modify();

        final int realLength = this.realLength;
        final int begin = this.begin;
        final IRubyObject[] values = this.values;
        if (realLength > i2) {
            try {
                Arrays.fill(values, begin + i2, begin + realLength, getRuntime().getNil());
            } catch (ArrayIndexOutOfBoundsException e) {
                concurrentModification();
            }
            this.realLength = i2;
            if (i2 << 1 < values.length && values.length > ARRAY_DEFAULT_SIZE) realloc(i2 << 1);
        }

        return item;
    }

    /** rb_ary_delete_at
     *
     */
    private final IRubyObject delete_at(int pos) {
        int len = realLength;

        if (pos >= len) return getRuntime().getNil();

        if (pos < 0) pos += len;

        if (pos < 0) return getRuntime().getNil();

        modify();

        IRubyObject obj = values[pos];
        try {
            System.arraycopy(values, pos + 1, values, pos, len - (pos + 1));
            values[realLength-1] = getRuntime().getNil();
        } catch (ArrayIndexOutOfBoundsException e) {
            concurrentModification();
        }
        realLength--;

        return obj;
    }

    /** rb_ary_delete_at_m
     * 
     */
    @JRubyMethod(name = "delete_at", required = 1)
    public IRubyObject delete_at(IRubyObject obj) {
        return delete_at((int) RubyNumeric.num2long(obj));
    }

    /** rb_ary_reject_bang
     * 
     */
    @JRubyMethod(name = "reject", frame = true)
    public IRubyObject reject(ThreadContext context, Block block) {
        RubyArray ary = aryDup();
        ary.reject_bang(context, block);
        return ary;
    }

    /** rb_ary_reject_bang
     *
     */
    @JRubyMethod(name = "reject!", frame = true)
    public IRubyObject reject_bang(ThreadContext context, Block block) {
        int i2 = 0;
        modify();

        for (int i1 = 0; i1 < realLength; i1++) {
            IRubyObject v = values[i1];
            if (block.yield(context, v).isTrue()) continue;

            if (i1 != i2) store(i2, v);
            i2++;
        }
        if (realLength == i2) return getRuntime().getNil();

        if (i2 < realLength) {
            try {
                Arrays.fill(values, i2, realLength, getRuntime().getNil());
            } catch (ArrayIndexOutOfBoundsException e) {
                concurrentModification();
            }
            realLength = i2;
        }

        return this;
    }

    /** rb_ary_delete_if
     *
     */
    @JRubyMethod(name = "delete_if", frame = true)
    public IRubyObject delete_if(ThreadContext context, Block block) {
        reject_bang(context, block);
        return this;
    }

    /** rb_ary_zip
     * 
     */
    @JRubyMethod(name = "zip", optional = 1, rest = true, frame = true)
    public IRubyObject zip(ThreadContext context, IRubyObject[] args, Block block) {
        for (int i = 0; i < args.length; i++) {
            args[i] = args[i].convertToArray();
        }

        Ruby runtime = getRuntime();
        if (block.isGiven()) {
            for (int i = 0; i < realLength; i++) {
                RubyArray tmp = new RubyArray(runtime, args.length + 1);
                tmp.append(elt(i));
                for (int j = 0; j < args.length; j++) {
                    tmp.append(((RubyArray) args[j]).elt(i));
                }
                block.yield(context, tmp);
            }
            return runtime.getNil();
        }
        
        int len = realLength;
        RubyArray result = new RubyArray(runtime, len);
        for (int i = 0; i < len; i++) {
            RubyArray tmp = new RubyArray(runtime, args.length + 1);
            tmp.append(elt(i));
            for (int j = 0; j < args.length; j++) {
                tmp.append(((RubyArray) args[j]).elt(i));
            }
            result.append(tmp);
        }
        return result;
    }

    /** rb_ary_cmp
     *
     */
    @JRubyMethod(name = "<=>", required = 1)
    public IRubyObject op_cmp(ThreadContext context, IRubyObject obj) {
        RubyArray ary2 = obj.convertToArray();

        int len = realLength;

        if (len > ary2.realLength) len = ary2.realLength;

        Ruby runtime = getRuntime();
        for (int i = 0; i < len; i++) {
            IRubyObject v = elt(i).callMethod(context, MethodIndex.OP_SPACESHIP, "<=>", ary2.elt(i));
            if (!(v instanceof RubyFixnum) || ((RubyFixnum) v).getLongValue() != 0) return v;
        }
        len = realLength - ary2.realLength;

        if (len == 0) return RubyFixnum.zero(runtime);
        if (len > 0) return RubyFixnum.one(runtime);

        return RubyFixnum.minus_one(runtime);
    }

    /**
     * Variable arity version for compatibility. Not bound to a Ruby method.
     * @deprecated Use the versions with zero, one, or two args.
     */
    public IRubyObject slice_bang(IRubyObject[] args) {
        switch (args.length) {
        case 1:
            return slice_bang(args[0]);
        case 2:
            return slice_bang(args[0], args[1]);
        default:
            Arity.raiseArgumentError(getRuntime(), args.length, 1, 2);
            return null; // not reached
        }
    }

    /** rb_ary_slice_bang
     *
     */
    @JRubyMethod(name = "slice!")
    public IRubyObject slice_bang(IRubyObject arg0) {
        if (arg0 instanceof RubyRange) {
            long[] beglen = ((RubyRange) arg0).begLen(realLength, 1);
            long pos = beglen[0];
            long len = beglen[1];

            if (pos < 0) pos = realLength + pos;

            arg0 = subseq(pos, len);
            splice(pos, len, null);
            return arg0;
        }
        return delete_at((int) RubyNumeric.num2long(arg0));
    }

    /** rb_ary_slice_bang
    *
    */
    @JRubyMethod(name = "slice!")
    public IRubyObject slice_bang(IRubyObject arg0, IRubyObject arg1) {
        long pos = RubyNumeric.num2long(arg0);
        long len = RubyNumeric.num2long(arg1);

        if (pos < 0) pos = realLength + pos;

        arg1 = subseq(pos, len);
        splice(pos, len, null);

        return arg1;
    }    

    /** rb_ary_assoc
     *
     */
    @JRubyMethod(name = "assoc", required = 1)
    public IRubyObject assoc(ThreadContext context, IRubyObject key) {
        Ruby runtime = getRuntime();

        for (int i = begin; i < begin + realLength; i++) {
            IRubyObject v = values[i];
            if (v instanceof RubyArray) {
                RubyArray arr = (RubyArray)v;
                if (arr.realLength > 0 && equalInternal(context, arr.values[arr.begin], key)) return arr;
            }
        }

        return runtime.getNil();
    }

    /** rb_ary_rassoc
     *
     */
    @JRubyMethod(name = "rassoc", required = 1)
    public IRubyObject rassoc(ThreadContext context, IRubyObject value) {
        Ruby runtime = getRuntime();

        for (int i = begin; i < begin + realLength; i++) {
            IRubyObject v = values[i];
            if (v instanceof RubyArray) {
                RubyArray arr = (RubyArray)v;
                if (arr.realLength > 1 && equalInternal(context, arr.values[arr.begin + 1], value)) return arr;
            }
        }

        return runtime.getNil();
    }

    /** flatten
     * 
     */
    private final int flatten(ThreadContext context, int index, RubyArray ary2, RubyArray memo) {
        int i = index;
        int n;
        int lim = index + ary2.realLength;

        IRubyObject id = ary2.id();

        if (memo.includes(context, id)) throw getRuntime().newArgumentError("tried to flatten recursive array");

        memo.append(id);
        splice(index, 1, ary2);
        while (i < lim) {
            IRubyObject tmp = elt(i).checkArrayType();
            if (!tmp.isNil()) {
                n = flatten(context, i, (RubyArray) tmp, memo);
                i += n;
                lim += n;
            }
            i++;
        }
        memo.pop();
        return lim - index - 1; /* returns number of increased items */
    }

    /** rb_ary_flatten_bang
     *
     */
    @JRubyMethod(name = "flatten!")
    public IRubyObject flatten_bang(ThreadContext context) {
        int i = 0;
        RubyArray memo = null;

        while (i < realLength) {
            IRubyObject ary2 = values[begin + i];
            IRubyObject tmp = ary2.checkArrayType();
            if (!tmp.isNil()) {
                if (memo == null) {
                    memo = new RubyArray(getRuntime(), false);
                    memo.values = reserve(ARRAY_DEFAULT_SIZE);
                }

                i += flatten(context, i, (RubyArray) tmp, memo);
            }
            i++;
        }
        if (memo == null) return getRuntime().getNil();

        return this;
    }

    /** rb_ary_flatten
     *
     */
    @JRubyMethod(name = "flatten")
    public IRubyObject flatten(ThreadContext context) {
        RubyArray ary = aryDup();
        ary.flatten_bang(context);
        return ary;
    }

    /** rb_ary_nitems
     *
     */
    @JRubyMethod(name = "nitems")
    public IRubyObject nitems() {
        int n = 0;

        for (int i = begin; i < begin + realLength; i++) {
            if (!values[i].isNil()) n++;
        }
        
        return getRuntime().newFixnum(n);
    }

    /** rb_ary_plus
     *
     */
    @JRubyMethod(name = "+", required = 1)
    public IRubyObject op_plus(IRubyObject obj) {
        RubyArray y = obj.convertToArray();
        int len = realLength + y.realLength;
        RubyArray z = new RubyArray(getRuntime(), len);
        try {
            System.arraycopy(values, begin, z.values, 0, realLength);
            System.arraycopy(y.values, y.begin, z.values, realLength, y.realLength);
        } catch (ArrayIndexOutOfBoundsException e) {
            concurrentModification();
        }
        z.realLength = len;
        return z;
    }

    /** rb_ary_times
     *
     */
    @JRubyMethod(name = "*", required = 1)
    public IRubyObject op_times(ThreadContext context, IRubyObject times) {
        IRubyObject tmp = times.checkStringType();

        if (!tmp.isNil()) return join(context, tmp);

        long len = RubyNumeric.num2long(times);
        if (len == 0) return new RubyArray(getRuntime(), getMetaClass(), 0);
        if (len < 0) throw getRuntime().newArgumentError("negative argument");

        if (Long.MAX_VALUE / len < realLength) {
            throw getRuntime().newArgumentError("argument too big");
        }

        len *= realLength;

        RubyArray ary2 = new RubyArray(getRuntime(), getMetaClass(), len);
        ary2.realLength = (int) len;

        try {
            for (int i = 0; i < len; i += realLength) {
                System.arraycopy(values, begin, ary2.values, i, realLength);
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            concurrentModification();
        }

        ary2.infectBy(this);

        return ary2;
    }

    /** ary_make_hash
     * 
     */
    private final RubyHash makeHash(RubyArray ary2) {
        RubyHash hash = new RubyHash(getRuntime(), false);
        int begin = this.begin;
        for (int i = begin; i < begin + realLength; i++) {
            hash.fastASet(values[i], NEVER);
        }

        if (ary2 != null) {
            begin = ary2.begin;            
            for (int i = begin; i < begin + ary2.realLength; i++) {
                hash.fastASet(ary2.values[i], NEVER);
            }
        }
        return hash;
    }

    /** rb_ary_uniq_bang
     *
     */
    @JRubyMethod(name = "uniq!")
    public IRubyObject uniq_bang() {
        RubyHash hash = makeHash(null);

        if (realLength == hash.size()) return getRuntime().getNil();

        int j = 0;
        for (int i = 0; i < realLength; i++) {
            IRubyObject v = elt(i);
            if (hash.fastDelete(v)) store(j++, v);
        }
        realLength = j;
        return this;
    }

    /** rb_ary_uniq
     *
     */
    @JRubyMethod(name = "uniq")
    public IRubyObject uniq() {
        RubyArray ary = aryDup();
        ary.uniq_bang();
        return ary;
    }

    /** rb_ary_diff
     *
     */
    @JRubyMethod(name = "-", required = 1)
    public IRubyObject op_diff(IRubyObject other) {
        RubyHash hash = other.convertToArray().makeHash(null);
        RubyArray ary3 = new RubyArray(getRuntime());

        int begin = this.begin;
        for (int i = begin; i < begin + realLength; i++) {
            if (hash.fastARef(values[i]) != null) continue;
            ary3.append(elt(i - begin));
        }

        return ary3;
    }

    /** rb_ary_and
     *
     */
    @JRubyMethod(name = "&", required = 1)
    public IRubyObject op_and(IRubyObject other) {
        RubyArray ary2 = other.convertToArray();
        RubyHash hash = ary2.makeHash(null);
        RubyArray ary3 = new RubyArray(getRuntime(), 
                realLength < ary2.realLength ? realLength : ary2.realLength);

        for (int i = 0; i < realLength; i++) {
            IRubyObject v = elt(i);
            if (hash.fastDelete(v)) ary3.append(v);
        }

        return ary3;
    }

    /** rb_ary_or
     *
     */
    @JRubyMethod(name = "|", required = 1)
    public IRubyObject op_or(IRubyObject other) {
        RubyArray ary2 = other.convertToArray();
        RubyHash set = makeHash(ary2);

        RubyArray ary3 = new RubyArray(getRuntime(), realLength + ary2.realLength);

        for (int i = 0; i < realLength; i++) {
            IRubyObject v = elt(i);
            if (set.fastDelete(v)) ary3.append(v);
        }
        for (int i = 0; i < ary2.realLength; i++) {
            IRubyObject v = ary2.elt(i);
            if (set.fastDelete(v)) ary3.append(v);
        }
        return ary3;
    }

    /** rb_ary_sort
     *
     */
    @JRubyMethod(name = "sort", frame = true)
    public RubyArray sort(Block block) {
        RubyArray ary = aryDup();
        ary.sort_bang(block);
        return ary;
    }

    /** rb_ary_sort_bang
     *
     */
    @JRubyMethod(name = "sort!", frame = true)
    public RubyArray sort_bang(Block block) {
        modify();
        if (realLength > 1) {
            flags |= TMPLOCK_ARR_F;
            try {
                if (block.isGiven()) {
                    Arrays.sort(values, 0, realLength, new BlockComparator(block));
                } else {
                    Arrays.sort(values, 0, realLength, new DefaultComparator());
                }
            } finally {
                flags &= ~TMPLOCK_ARR_F;
            }
        }
        return this;
    }

    final class BlockComparator implements Comparator {
        private Block block;

        public BlockComparator(Block block) {
            this.block = block;
        }

        public int compare(Object o1, Object o2) {
            ThreadContext context = getRuntime().getCurrentContext();
            IRubyObject obj1 = (IRubyObject) o1;
            IRubyObject obj2 = (IRubyObject) o2;
            IRubyObject ret = block.yield(context, getRuntime().newArray(obj1, obj2), null, null, true);
            int n = RubyComparable.cmpint(context, ret, obj1, obj2);
            //TODO: ary_sort_check should be done here
            return n;
        }
    }

    static final class DefaultComparator implements Comparator {
        public int compare(Object o1, Object o2) {
            if (o1 instanceof RubyFixnum && o2 instanceof RubyFixnum) {
                return compareFixnums(o1, o2);
            }
            if (o1 instanceof RubyString && o2 instanceof RubyString) {
                return ((RubyString) o1).op_cmp((RubyString) o2);
            }
            //TODO: ary_sort_check should be done here
            return compareOthers((IRubyObject)o1, (IRubyObject)o2);
        }

        private int compareFixnums(Object o1, Object o2) {
            long a = ((RubyFixnum) o1).getLongValue();
            long b = ((RubyFixnum) o2).getLongValue();
            if (a > b) {
                return 1;
            }
            if (a < b) {
                return -1;
            }
            return 0;
        }

        private int compareOthers(IRubyObject o1, IRubyObject o2) {
            ThreadContext context = o1.getRuntime().getCurrentContext();
            IRubyObject ret = o1.callMethod(context, MethodIndex.OP_SPACESHIP, "<=>", o2);
            int n = RubyComparable.cmpint(context, ret, o1, o2);
            //TODO: ary_sort_check should be done here
            return n;
        }
    }

    public static void marshalTo(RubyArray array, MarshalStream output) throws IOException {
        output.registerLinkTarget(array);
        output.writeInt(array.getList().size());
        for (Iterator iter = array.getList().iterator(); iter.hasNext();) {
            output.dumpObject((IRubyObject) iter.next());
        }
    }

    public static RubyArray unmarshalFrom(UnmarshalStream input) throws IOException {
        RubyArray result = input.getRuntime().newArray();
        input.registerLinkTarget(result);
        int size = input.unmarshalInt();
        for (int i = 0; i < size; i++) {
            result.append(input.unmarshalObject());
        }
        return result;
    }

    /**
     * @see org.jruby.util.Pack#pack
     */
    @JRubyMethod(name = "pack", required = 1)
    public RubyString pack(ThreadContext context, IRubyObject obj) {
        RubyString iFmt = RubyString.objAsString(context, obj);
        return Pack.pack(getRuntime(), this, iFmt.getByteList());
    }

    @Override
    public Class getJavaClass() {
        return List.class;
    }

    // Satisfy java.util.List interface (for Java integration)
    public int size() {
        return realLength;
    }

    public boolean isEmpty() {
        return realLength == 0;
    }

    public boolean contains(Object element) {
        return indexOf(element) != -1;
    }

    public Object[] toArray() {
        Object[] array = new Object[realLength];
        for (int i = begin; i < realLength; i++) {
            array[i - begin] = JavaUtil.convertRubyToJava(values[i]);
        }
        return array;
    }

    public Object[] toArray(final Object[] arg) {
        Object[] array = arg;
        if (array.length < realLength) {
            Class type = array.getClass().getComponentType();
            array = (Object[]) Array.newInstance(type, realLength);
        }
        int length = realLength - begin;

        for (int i = 0; i < length; i++) {
            array[i] = JavaUtil.convertRubyToJava(values[i + begin]);
        }
        return array;
    }

    public boolean add(Object element) {
        append(JavaUtil.convertJavaToRuby(getRuntime(), element));
        return true;
    }

    public boolean remove(Object element) {
        IRubyObject deleted = delete(getRuntime().getCurrentContext(), JavaUtil.convertJavaToRuby(getRuntime(), element), Block.NULL_BLOCK);
        return deleted.isNil() ? false : true; // TODO: is this correct ?
    }

    public boolean containsAll(Collection c) {
        for (Iterator iter = c.iterator(); iter.hasNext();) {
            if (indexOf(iter.next()) == -1) {
                return false;
            }
        }

        return true;
    }

    public boolean addAll(Collection c) {
        for (Iterator iter = c.iterator(); iter.hasNext();) {
            add(iter.next());
        }
        return !c.isEmpty();
    }

    public boolean addAll(int index, Collection c) {
        Iterator iter = c.iterator();
        for (int i = index; iter.hasNext(); i++) {
            add(i, iter.next());
        }
        return !c.isEmpty();
    }

    public boolean removeAll(Collection c) {
        boolean listChanged = false;
        for (Iterator iter = c.iterator(); iter.hasNext();) {
            if (remove(iter.next())) {
                listChanged = true;
            }
        }
        return listChanged;
    }

    public boolean retainAll(Collection c) {
        boolean listChanged = false;

        for (Iterator iter = iterator(); iter.hasNext();) {
            Object element = iter.next();
            if (!c.contains(element)) {
                remove(element);
                listChanged = true;
            }
        }
        return listChanged;
    }

    public Object get(int index) {
        return JavaUtil.convertRubyToJava((IRubyObject) elt(index), Object.class);
    }

    public Object set(int index, Object element) {
        return store(index, JavaUtil.convertJavaToRuby(getRuntime(), element));
    }

    // TODO: make more efficient by not creating IRubyArray[]
    public void add(int index, Object element) {
        insert(new IRubyObject[]{RubyFixnum.newFixnum(getRuntime(), index), JavaUtil.convertJavaToRuby(getRuntime(), element)});
    }

    public Object remove(int index) {
        return JavaUtil.convertRubyToJava(delete_at(index), Object.class);
    }

    public int indexOf(Object element) {
        int begin = this.begin;

        if (element != null) {
            IRubyObject convertedElement = JavaUtil.convertJavaToRuby(getRuntime(), element);

            for (int i = begin; i < begin + realLength; i++) {
                if (convertedElement.equals(values[i])) {
                    return i;
                }
            }
        }
        return -1;
    }

    public int lastIndexOf(Object element) {
        int begin = this.begin;

        if (element != null) {
            IRubyObject convertedElement = JavaUtil.convertJavaToRuby(getRuntime(), element);

            for (int i = begin + realLength - 1; i >= begin; i--) {
                if (convertedElement.equals(values[i])) {
                    return i;
                }
            }
        }

        return -1;
    }

    public class RubyArrayConversionIterator implements Iterator {
        protected int index = 0;
        protected int last = -1;

        public boolean hasNext() {
            return index < realLength;
        }

        public Object next() {
            IRubyObject element = elt(index);
            last = index++;
            return JavaUtil.convertRubyToJava(element, Object.class);
        }

        public void remove() {
            if (last == -1) throw new IllegalStateException();

            delete_at(last);
            if (last < index) index--;

            last = -1;
	
        }
    }

    public Iterator iterator() {
        return new RubyArrayConversionIterator();
    }

    final class RubyArrayConversionListIterator extends RubyArrayConversionIterator implements ListIterator {
        public RubyArrayConversionListIterator() {
        }

        public RubyArrayConversionListIterator(int index) {
            this.index = index;
		}

		public boolean hasPrevious() {
            return index >= 0;
		}

		public Object previous() {
            return JavaUtil.convertRubyToJava((IRubyObject) elt(last = --index), Object.class);
		}

		public int nextIndex() {
            return index;
		}

		public int previousIndex() {
            return index - 1;
		}

        public void set(Object obj) {
            if (last == -1) throw new IllegalStateException();

            store(last, JavaUtil.convertJavaToRuby(getRuntime(), obj));
        }

        public void add(Object obj) {
            insert(new IRubyObject[] { RubyFixnum.newFixnum(getRuntime(), index++), JavaUtil.convertJavaToRuby(getRuntime(), obj) });
            last = -1;
        }
    }

    public ListIterator listIterator() {
        return new RubyArrayConversionListIterator();
    }

    public ListIterator listIterator(int index) {
        return new RubyArrayConversionListIterator(index);
	}

    // TODO: list.subList(from, to).clear() is supposed to clear the sublist from the list.
    // How can we support this operation?
    public List subList(int fromIndex, int toIndex) {
        if (fromIndex < 0 || toIndex > size() || fromIndex > toIndex) {
            throw new IndexOutOfBoundsException();
        }
        
        IRubyObject subList = subseq(fromIndex, toIndex - fromIndex + 1);

        return subList.isNil() ? null : (List) subList;
    }

    public void clear() {
        rb_clear();
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2006 Ola Bini <ola@ologix.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyConstant;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.CallbackFactory;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;

/**
 * @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
 */
@JRubyClass(name="BigDecimal", parent="Numeric")
public class RubyBigDecimal extends RubyNumeric {
    private static final ObjectAllocator BIGDECIMAL_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return new RubyBigDecimal(runtime, klass);
        }
    };

    @JRubyConstant
    public final static int ROUND_DOWN = BigDecimal.ROUND_DOWN;
    @JRubyConstant
    public final static int ROUND_CEILING = BigDecimal.ROUND_CEILING;
    @JRubyConstant
    public final static int ROUND_UP = BigDecimal.ROUND_UP;
    @JRubyConstant
    public final static int ROUND_HALF_DOWN = BigDecimal.ROUND_HALF_DOWN;
    @JRubyConstant
    public final static int ROUND_HALF_EVEN = BigDecimal.ROUND_HALF_EVEN;
    @JRubyConstant
    public final static int ROUND_HALF_UP = BigDecimal.ROUND_HALF_UP;
    @JRubyConstant
    public final static int ROUND_FLOOR = BigDecimal.ROUND_FLOOR;

    @JRubyConstant
    public final static int SIGN_POSITIVE_INFINITE=3;
    @JRubyConstant
    public final static int EXCEPTION_OVERFLOW=1;
    @JRubyConstant
    public final static int SIGN_POSITIVE_ZERO=1;
    @JRubyConstant
    public final static int EXCEPTION_ALL=255;
    @JRubyConstant
    public final static int SIGN_NEGATIVE_FINITE=-2;
    @JRubyConstant
    public final static int EXCEPTION_UNDERFLOW=4;
    @JRubyConstant
    public final static int SIGN_NaN=0;
    @JRubyConstant
    public final static int BASE=10000;
    @JRubyConstant
    public final static int ROUND_MODE=256;
    @JRubyConstant
    public final static int SIGN_POSITIVE_FINITE=2;
    @JRubyConstant
    public final static int EXCEPTION_INFINITY=1;
    @JRubyConstant
    public final static int SIGN_NEGATIVE_INFINITE=-3;
    @JRubyConstant
    public final static int EXCEPTION_ZERODIVIDE=1;
    @JRubyConstant
    public final static int SIGN_NEGATIVE_ZERO=-1;
    @JRubyConstant
    public final static int EXCEPTION_NaN=2;
    
    // Static constants
    private static final BigDecimal TWO = new BigDecimal(2);
    private static final double SQRT_10 = 3.162277660168379332;
    
    public static RubyClass createBigDecimal(Ruby runtime) {
        RubyClass result = runtime.defineClass("BigDecimal",runtime.getNumeric(), BIGDECIMAL_ALLOCATOR);

        CallbackFactory callbackFactory = runtime.callbackFactory(RubyBigDecimal.class);

        runtime.getKernel().defineAnnotatedMethods(BigDecimalKernelMethods.class);

        result.setInternalModuleVariable("vpPrecLimit", RubyFixnum.zero(runtime));
        result.setInternalModuleVariable("vpExceptionMode", RubyFixnum.zero(runtime));
        result.setInternalModuleVariable("vpRoundingMode", runtime.newFixnum(ROUND_HALF_UP));
        
        result.defineAnnotatedMethods(RubyBigDecimal.class);
        result.defineAnnotatedConstants(RubyBigDecimal.class);

        return result;
    }

    private boolean isNaN = false;
    private int infinitySign = 0;
    private int zeroSign = 0;
    private BigDecimal value;

    public BigDecimal getValue() {
        return value;
    }

    public RubyBigDecimal(Ruby runtime, RubyClass klass) {
        super(runtime, klass);
    }

    public RubyBigDecimal(Ruby runtime, BigDecimal value) {
        super(runtime, runtime.fastGetClass("BigDecimal"));
        this.value = value;
    }
    
    public static class BigDecimalKernelMethods {
        @JRubyMethod(name = "BigDecimal", rest = true, module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject newBigDecimal(IRubyObject recv, IRubyObject[] args) {
            return RubyBigDecimal.newBigDecimal(recv, args, Block.NULL_BLOCK);
        }
    }

    public static RubyBigDecimal newBigDecimal(IRubyObject recv, IRubyObject[] args, Block unusedBlock) {
        return newInstance(recv.getRuntime().fastGetClass("BigDecimal"), args);
    }

    @JRubyMethod(name = "ver", meta = true)
    public static IRubyObject ver(IRubyObject recv) {
        return recv.getRuntime().newString("1.0.1");
    }

    @JRubyMethod(name = "_dump", optional = 1, frame = true)
    public IRubyObject dump(IRubyObject[] args, Block unusedBlock) {
        RubyString precision = RubyString.newUnicodeString(args[0].getRuntime(), "0:");
        RubyString str = this.asString();
        return precision.append(str);
    }
        
    @JRubyMethod(name = "_load", required = 1, frame = true, meta = true)
    public static RubyBigDecimal load(IRubyObject recv, IRubyObject from, Block block) {
        RubyBigDecimal rubyBigDecimal = (RubyBigDecimal) (((RubyClass)recv).allocate());
        String precisionAndValue = from.convertToString().asJavaString();
        String value = precisionAndValue.substring(precisionAndValue.indexOf(":")+1);
        rubyBigDecimal.value = new BigDecimal(value);
        return rubyBigDecimal;
    }

    @JRubyMethod(name = "double_fig", meta = true)
    public static IRubyObject double_fig(IRubyObject recv) {
        return recv.getRuntime().newFixnum(20);
    }
    
    @JRubyMethod(name = "limit", optional = 1, meta = true)
    public static IRubyObject limit(IRubyObject recv, IRubyObject[] args) {
        Ruby runtime = recv.getRuntime();
        RubyModule c = (RubyModule)recv;
        IRubyObject nCur = c.searchInternalModuleVariable("vpPrecLimit");

        if (args.length > 0) {
            IRubyObject arg = args[0];
            if (!arg.isNil()) {
                if (!(arg instanceof RubyFixnum)) {
                    throw runtime.newTypeError(arg, runtime.getFixnum());
                }
                if (0 > ((RubyFixnum)arg).getLongValue()) {
                    throw runtime.newArgumentError("argument must be positive");
                }
                c.setInternalModuleVariable("vpPrecLimit", arg);
            }
        }

        return nCur;
    }

    @JRubyMethod(name = "mode", required = 1, optional = 1, meta = true)
    public static IRubyObject mode(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        // FIXME: I doubt any of the constants referenced in this method
        // are ever redefined -- should compare to the known values, rather
        // than do an expensive constant lookup.
        Ruby runtime = recv.getRuntime();
        RubyClass clazz = runtime.fastGetClass("BigDecimal");
        RubyModule c = (RubyModule)recv;
        
        args = Arity.scanArgs(runtime, args, 1, 1);
        
        IRubyObject mode = args[0];
        IRubyObject value = args[1];
        
        if (!(mode instanceof RubyFixnum)) {
            throw runtime.newTypeError("wrong argument type " + mode.getMetaClass() + " (expected Fixnum)");
        }
        
        long longMode = ((RubyFixnum)mode).getLongValue();
        long EXCEPTION_ALL = ((RubyFixnum)clazz.fastGetConstant("EXCEPTION_ALL")).getLongValue();
        if ((longMode & EXCEPTION_ALL) != 0) {     
            if (value.isNil()) {
                return c.searchInternalModuleVariable("vpExceptionMode");
            }
            if (!(value.isNil()) && !(value instanceof RubyBoolean)) {
                throw runtime.newTypeError("second argument must be true or false");
            }

            RubyFixnum currentExceptionMode = (RubyFixnum)c.searchInternalModuleVariable("vpExceptionMode");
            RubyFixnum newExceptionMode = new RubyFixnum(runtime, currentExceptionMode.getLongValue());
            
            RubyFixnum EXCEPTION_INFINITY = (RubyFixnum)clazz.fastGetConstant("EXCEPTION_INFINITY");
            if ((longMode & EXCEPTION_INFINITY.getLongValue()) != 0) {
                newExceptionMode = (value.isTrue()) ? (RubyFixnum)currentExceptionMode.callCoerced(context, "|", EXCEPTION_INFINITY)
                        : (RubyFixnum)currentExceptionMode.callCoerced(context, "&", new RubyFixnum(runtime, ~(EXCEPTION_INFINITY).getLongValue()));
            }
            
            RubyFixnum EXCEPTION_NaN = (RubyFixnum)clazz.fastGetConstant("EXCEPTION_NaN");
            if ((longMode & EXCEPTION_NaN.getLongValue()) != 0) {
                newExceptionMode = (value.isTrue()) ? (RubyFixnum)currentExceptionMode.callCoerced(context, "|", EXCEPTION_NaN)
                        : (RubyFixnum)currentExceptionMode.callCoerced(context, "&", new RubyFixnum(runtime, ~(EXCEPTION_NaN).getLongValue()));
            }
            c.setInternalModuleVariable("vpExceptionMode", newExceptionMode);
            return newExceptionMode;
        }
        
        long ROUND_MODE = ((RubyFixnum)clazz.fastGetConstant("ROUND_MODE")).getLongValue();
        if (longMode == ROUND_MODE) {
            if (value.isNil()) {
                return c.searchInternalModuleVariable("vpRoundingMode");
            }
            if (!(value instanceof RubyFixnum)) {
                throw runtime.newTypeError("wrong argument type " + mode.getMetaClass() + " (expected Fixnum)");
            }
            
            RubyFixnum roundingMode = (RubyFixnum)value;
            if (roundingMode == clazz.fastGetConstant("ROUND_UP") ||
                    roundingMode == clazz.fastGetConstant("ROUND_DOWN") ||
                    roundingMode == clazz.fastGetConstant("ROUND_FLOOR") ||
                    roundingMode == clazz.fastGetConstant("ROUND_CEILING") ||
                    roundingMode == clazz.fastGetConstant("ROUND_HALF_UP") ||
                    roundingMode == clazz.fastGetConstant("ROUND_HALF_DOWN") ||
                    roundingMode == clazz.fastGetConstant("ROUND_HALF_EVEN")) {
                c.setInternalModuleVariable("vpRoundingMode", roundingMode);
            } else {
                throw runtime.newTypeError("invalid rounding mode");
            }
            return c.searchInternalModuleVariable("vpRoundingMode");
        }
        throw runtime.newTypeError("first argument for BigDecimal#mode invalid");
    }

    private RoundingMode getRoundingMode(Ruby runtime) {
        RubyFixnum roundingMode = (RubyFixnum)runtime.fastGetClass("BigDecimal")
                .searchInternalModuleVariable("vpRoundingMode");
        return RoundingMode.valueOf((int)roundingMode.getLongValue());
    }

    private RubyBigDecimal getVpValue(IRubyObject v, boolean must) {
        if(v instanceof RubyBigDecimal) {
            return (RubyBigDecimal)v;
        } else if(v instanceof RubyFixnum || v instanceof RubyBignum) {
            String s = v.toString();
            return newInstance(getRuntime().fastGetClass("BigDecimal"),new IRubyObject[]{getRuntime().newString(s)});
        }
        if(must) {
            String err;
            if (isImmediate()) {
                ThreadContext context = getRuntime().getCurrentContext();
                err = inspect(context, this).toString();
            } else {
                err = getMetaClass().getBaseName();
            }
            throw getRuntime().newTypeError(err + " can't be coerced into BigDecimal");
        }
        return null;
    }

    private final static Pattern INFINITY_PATTERN = Pattern.compile("^([+-])?Infinity$");
    private final static Pattern NUMBER_PATTERN
            = Pattern.compile("^([+-]?\\d*\\.?\\d*([eE][+-]?)?\\d*).*");
    
    @JRubyMethod(name = "new", required = 1, optional = 1, meta = true)
    public static RubyBigDecimal newInstance(IRubyObject recv, IRubyObject[] args) {
        BigDecimal decimal;
        if (args.length == 0) { 
            decimal = new BigDecimal(0);
        } else {
            String strValue = args[0].convertToString().toString();
            strValue = strValue.trim();
            if ("NaN".equals(strValue)) {
                return newNaN(recv.getRuntime());
            }

            Matcher m = INFINITY_PATTERN.matcher(strValue);
            if (m.matches()) {
                int sign = 1;
                String signGroup = m.group(1);
                if ("-".equals(signGroup)) {
                    sign = -1;
                }
                return newInfinity(recv.getRuntime(), sign);
            }

            // Clean-up string representation so that it could be understood
            // by Java's BigDecimal. Not terribly efficient for now.
            // 1. MRI allows d and D as exponent separators
            strValue = strValue.replaceFirst("[dD]", "E");
            // 2. MRI allows underscores anywhere
            strValue = strValue.replaceAll("_", "");
            // 3. MRI ignores the trailing junk
            strValue = NUMBER_PATTERN.matcher(strValue).replaceFirst("$1");

            try {
                decimal = new BigDecimal(strValue);
            } catch(NumberFormatException e) {
                decimal = new BigDecimal(0);
            }
            if (decimal.signum() == 0) {
                // MRI behavior: -0 and +0 are two different things
                if (strValue.matches("^\\s*-.*")) {
                    return newZero(recv.getRuntime(), -1);
                } else {
                    return newZero(recv.getRuntime(), 1);
                }
            }
        }
        return new RubyBigDecimal(recv.getRuntime(), decimal);
    }

    private static RubyBigDecimal newZero(Ruby runtime, int sign) {
        RubyBigDecimal rbd =  new RubyBigDecimal(runtime, BigDecimal.ZERO);
        if (sign < 0) {
            rbd.zeroSign = -1;
        } else {
            rbd.zeroSign = 1;
        }
        return rbd;
    }

    private static RubyBigDecimal newNaN(Ruby runtime) {
        RubyBigDecimal rbd =  new RubyBigDecimal(runtime, BigDecimal.ZERO);
        rbd.isNaN = true;
        return rbd;
    }
    
    private static RubyBigDecimal newInfinity(Ruby runtime, int sign) {
        RubyBigDecimal rbd =  new RubyBigDecimal(runtime, BigDecimal.ZERO);
        if (sign < 0) {
            rbd.infinitySign = -1;
        } else {
            rbd.infinitySign = 1;
        }
        return rbd;
    }

    private RubyBigDecimal setResult() {
        return setResult(0);
    }

    private RubyBigDecimal setResult(int scale) {
        int prec = RubyFixnum.fix2int(getRuntime().fastGetClass("BigDecimal").searchInternalModuleVariable("vpPrecLimit"));
        int prec2 = Math.max(scale,prec);
        if(prec2 > 0 && this.value.scale() > (prec2-getExponent())) {
            this.value = this.value.setScale(prec2-getExponent(),BigDecimal.ROUND_HALF_UP);
        }
        return this;
    }
    
    @JRubyMethod(name = "hash")
    public RubyFixnum hash() {
        return getRuntime().newFixnum(value.hashCode());
    }

    @JRubyMethod(name = {"%", "modulo"}, required = 1)
    public IRubyObject op_mod(ThreadContext context, IRubyObject arg) {
        // TODO: full-precision remainder is 1000x slower than MRI!
        Ruby runtime = context.getRuntime();
        if (isInfinity() || isNaN()) {
            return newNaN(runtime);
        }
        RubyBigDecimal val = getVpValue(arg, false);
        if (val == null) {
            return callCoerced(context, "%", arg, true);
        }
        if (val.isInfinity() || val.isNaN() || val.isZero()) {
            return newNaN(runtime);
        }

        // Java and MRI definitions of modulo are different.
        BigDecimal modulo = value.remainder(val.value);
        if (modulo.signum() * val.value.signum() < 0) {
            modulo = modulo.add(val.value);
        }

        return new RubyBigDecimal(runtime, modulo).setResult();
    }

    @JRubyMethod(name = "remainder", required = 1)
    public IRubyObject remainder(ThreadContext context, IRubyObject arg) {
        // TODO: full-precision remainder is 1000x slower than MRI!
        Ruby runtime = context.getRuntime();
        if (isInfinity() || isNaN()) {
            return newNaN(runtime);
        }
        RubyBigDecimal val = getVpValue(arg,false);
        if (val == null) {
            return callCoerced(context, "remainder", arg, true);
        }
        if (val.isInfinity() || val.isNaN() || val.isZero()) {
            return newNaN(runtime);
        }

        // Java and MRI definitions of remainder are the same.
        return new RubyBigDecimal(runtime, value.remainder(val.value)).setResult();
    }

    @JRubyMethod(name = "*", required = 1)
    public IRubyObject op_mul(ThreadContext context, IRubyObject arg) {
        return mult2(context, arg, RubyFixnum.zero(context.getRuntime()));
    }

    @JRubyMethod(name = "mult", required = 2)
    public IRubyObject mult2(ThreadContext context, IRubyObject b, IRubyObject n) {
        Ruby runtime = context.getRuntime();

        RubyBigDecimal val = getVpValue(b,false);
        if(val == null) {
            // TODO: what about n arg?
            return callCoerced(context, "*", b);
        }

        int digits = RubyNumeric.fix2int(n);

        if (isNaN() || val.isNaN()) {
            return newNaN(runtime);
        }

        if  ((isInfinity() && val.isZero()) || (isZero() && val.isInfinity())) {
            return newNaN(runtime);
        }

        if (isZero() || val.isZero()) {
            int sign1 = isZero()? zeroSign : value.signum();
            int sign2 = val.isZero() ?  val.zeroSign : val.value.signum();
            return newZero(runtime, sign1 * sign2);
        }

        if (isInfinity() || val.isInfinity()) {
            int sign1 = isInfinity() ? infinitySign : value.signum();
            int sign2 = val.isInfinity() ? val.infinitySign : val.value.signum();
            return newInfinity(runtime, sign1 * sign2);
        }

        BigDecimal res = value.multiply(val.value);
        if (res.precision() > digits) {
            // TODO: rounding mode should not be hard-coded. See #mode.
            res = res.round(new MathContext(digits,  RoundingMode.HALF_UP));
        }
        return new RubyBigDecimal(runtime, res).setResult();
    }
    
    @JRubyMethod(name = {"**", "power"}, required = 1)
    public IRubyObject op_pow(IRubyObject arg) {
        if (!(arg instanceof RubyFixnum)) {
            throw getRuntime().newTypeError("wrong argument type " + arg.getMetaClass() + " (expected Fixnum)");
        }

        if (isNaN() || isInfinity()) {
            return newNaN(getRuntime());
        }

        int times = RubyNumeric.fix2int(arg.convertToInteger());

        if (times < 0) {
            if (isZero()) {
                return newInfinity(getRuntime(), value.signum());
            }

            // Note: MRI has a very non-trivial way of calculating the precision,
            // so we use very simple approximation here:
            int precision = (-times + 4) * (getAllDigits().length() + 4);

            return new RubyBigDecimal(getRuntime(),
                    value.pow(times, new MathContext(precision, RoundingMode.HALF_UP)));
        } else {
            return new RubyBigDecimal(getRuntime(), value.pow(times));
        }
    }

    @JRubyMethod(name = "+", required = 1, frame=true)
    public IRubyObject op_plus(ThreadContext context, IRubyObject b) {
        return addInternal(context, b, "add", RubyFixnum.zero(context.getRuntime()));
    }

    @JRubyMethod(name = "add", required = 2, frame=true)
    public IRubyObject add2(ThreadContext context, IRubyObject b, IRubyObject digits) {
        return addInternal(context, b, "add", digits);
    }

    private IRubyObject addInternal(ThreadContext context, IRubyObject b, String op, IRubyObject digits) {
        Ruby runtime = context.getRuntime();
        int prec = getPositiveInt(context, digits);

        RubyBigDecimal val = getVpValue(b, false);
        if (val == null) {
            // TODO:
            // MRI behavior: Call "+" or "add", depending on the call.
            // But this leads to exceptions when Floats are added. See:
            // http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/17374
            // return callCoerced(context, op, b, true); -- this is MRI behavior.
            // We'll use ours for now, thus providing an ability to add Floats.
            return callCoerced(context, "+", b, true);
        }

        IRubyObject res = handleAddSpecialValues(val);
        if (res != null) {
            return res;
        }
        RoundingMode roundMode = getRoundingMode(runtime);
        return new RubyBigDecimal(runtime, value.add(
                val.value, new MathContext(prec, roundMode))); // TODO: why this: .setResult();
    }

    private int getPositiveInt(ThreadContext context, IRubyObject arg) {
        Ruby runtime = context.getRuntime();

        if (arg instanceof RubyFixnum) {
            int value = RubyNumeric.fix2int(arg);
            if (value < 0) {
                throw runtime.newArgumentError("argument must be positive");
            }
            return value;
        } else {
            throw runtime.newTypeError(arg, runtime.getFixnum());
        }
    }

    private IRubyObject handleAddSpecialValues(RubyBigDecimal val) {
        if (isNaN() || val.isNaN) {
            return newNaN(getRuntime());
        }
        // TODO: don't calculate the same value 3 times
        if (infinitySign * val.infinitySign > 0) {
            return isInfinity() ? this : val;
        }
        if (infinitySign * val.infinitySign < 0) {
            return newNaN(getRuntime());
        }
        if (infinitySign * val.infinitySign == 0) {
            int sign = infinitySign + val.infinitySign;
            if (sign != 0) {
                return newInfinity(getRuntime(), sign);
            }
        }
        return null;
    }

    @JRubyMethod(name = "+@")
    public IRubyObject op_uplus() {
        return this;
    }
    
    @JRubyMethod(name = "-", required = 1)
    public IRubyObject op_minus(ThreadContext context, IRubyObject arg) {
        RubyBigDecimal val = getVpValue(arg, false);
        if(val == null) {
            return callCoerced(context, "-", arg);
        }
        RubyBigDecimal res = handleMinusSpecialValues(val);
        if (res != null) {
            return res;
        }
        return new RubyBigDecimal(getRuntime(),value.subtract(val.value)).setResult();
    }

    @JRubyMethod(name = "sub", required = 2)
    public IRubyObject sub2(ThreadContext context, IRubyObject b, IRubyObject n) {
        RubyBigDecimal val = getVpValue(b, false);
        if(val == null) {
            return callCoerced(context, "-", b);
        }
        RubyBigDecimal res = handleMinusSpecialValues(val);
        if (res != null) {
            return res;
        }

        return new RubyBigDecimal(getRuntime(),value.subtract(val.value)).setResult();
    }

    private RubyBigDecimal handleMinusSpecialValues(RubyBigDecimal val) {
        if (isNaN() || val.isNaN()) {
            return newNaN(getRuntime());
        }
        
        // TODO: 3 times calculate the same value below
        if (infinitySign * val.infinitySign > 0) {
            return newNaN(getRuntime());
        }
        if (infinitySign * val.infinitySign < 0) {
                return this;
        }
        if (infinitySign * val.infinitySign == 0) {
            if (isInfinity()) {
                return this;
            }
            if (val.isInfinity()) {
                return newInfinity(getRuntime(), val.infinitySign * -1);
            }
            int sign = infinitySign + val.infinitySign;
            if (sign != 0) {
                return newInfinity(getRuntime(), sign);
            }
        }
        return null;
    }

    @JRubyMethod(name = "-@")
    public IRubyObject op_uminus() {
        Ruby runtime = getRuntime();
        if (isNaN()) {
            return newNaN(runtime);
        }
        if (isInfinity()) {
            return newInfinity(runtime, -infinitySign);
        }
        if (isZero()) {
            return newZero(runtime, -zeroSign);
        }
        return new RubyBigDecimal(getRuntime(), value.negate());
    }

    @JRubyMethod(name = {"/", "quo"})
    public IRubyObject op_quo(ThreadContext context, IRubyObject other) {
        // regular division with some default precision
        // TODO: proper algorithm to set the precision
        return op_div(context, other, getRuntime().newFixnum(200));
    }

    @JRubyMethod(name = "div")
    public IRubyObject op_div(ThreadContext context, IRubyObject other) {
        // integer division
        RubyBigDecimal val = getVpValue(other, false);
        if (val == null) {
            return callCoerced(context, "div", other);
        }

        if (isNaN() || val.isZero() || val.isNaN()) {
            return newNaN(getRuntime());
        }

        if (isInfinity() || val.isInfinity()) {
            return newNaN(getRuntime());
        }

        return new RubyBigDecimal(getRuntime(),
                this.value.divideToIntegralValue(val.value)).setResult();
    }

    @JRubyMethod(name = "div")
    public IRubyObject op_div(ThreadContext context, IRubyObject other, IRubyObject digits) {
        // TODO: take BigDecimal.mode into account.

        int scale = RubyNumeric.fix2int(digits);

        RubyBigDecimal val = getVpValue(other, false);
        if (val == null) {
            return callCoerced(context, "/", other);
        }

        if (isNaN() || (isZero() && val.isZero()) || val.isNaN()) {
            return newNaN(getRuntime());
        }

        if (val.isZero()) {
            int sign1 = isInfinity() ? infinitySign : value.signum();
            return newInfinity(getRuntime(), sign1 * val.zeroSign);
        }

        if (isInfinity() && !val.isInfinity()) {
            return newInfinity(getRuntime(), infinitySign * val.value.signum());
        }

        if (!isInfinity() && val.isInfinity()) {
            return newZero(getRuntime(), value.signum() * val.infinitySign);
        }

        if (isInfinity() && val.isInfinity()) {
            return newNaN(getRuntime());
        }

        if (scale == 0) {
            // MRI behavior: "If digits is 0, the result is the same as the / operator."
            return op_quo(context, other);
        } else {
            // TODO: better algorithm to set precision needed
            int prec = Math.max(200, scale);
            return new RubyBigDecimal(getRuntime(),
                    value.divide(val.value, new MathContext(prec, RoundingMode.HALF_UP))).setResult(scale);
        }
    }

    private IRubyObject cmp(ThreadContext context, IRubyObject r, char op) {
        int e = 0;
        RubyBigDecimal rb = getVpValue(r,false);
        if(rb == null) {
            IRubyObject ee = callCoerced(context, "<=>",r);
            if(ee.isNil()) {
                return getRuntime().getNil();
            }
            e = RubyNumeric.fix2int(ee);
        } else {
            if (isNaN() | rb.isNaN()) {
                return getRuntime().getNil();
            }
            if (infinitySign != 0 || rb.infinitySign != 0) {
                e = infinitySign - rb.infinitySign;
            } else {
                e = value.compareTo(rb.value);
            }
        }
        switch(op) {
        case '*': return getRuntime().newFixnum(e);
        case '=': return (e==0)?getRuntime().getTrue():getRuntime().getFalse();
        case '!': return (e!=0)?getRuntime().getTrue():getRuntime().getFalse();
        case 'G': return (e>=0)?getRuntime().getTrue():getRuntime().getFalse();
        case '>': return (e> 0)?getRuntime().getTrue():getRuntime().getFalse();
        case 'L': return (e<=0)?getRuntime().getTrue():getRuntime().getFalse();
        case '<': return (e< 0)?getRuntime().getTrue():getRuntime().getFalse();
        }
        return getRuntime().getNil();
    }

    @JRubyMethod(name = "<=>", required = 1)
    public IRubyObject op_cmp(ThreadContext context, IRubyObject arg) {
        return cmp(context, arg,'*');
    }

    @JRubyMethod(name = {"eql?", "==", "==="}, required = 1)
    public IRubyObject eql_p(ThreadContext context, IRubyObject arg) {
        return cmp(context, arg,'=');
    }

    @JRubyMethod(name = "<", required = 1)
    public IRubyObject op_lt(ThreadContext context, IRubyObject arg) {
        return cmp(context, arg,'<');
    }

    @JRubyMethod(name = "<=", required = 1)
    public IRubyObject op_le(ThreadContext context, IRubyObject arg) {
        return cmp(context, arg,'L');
    }

    @JRubyMethod(name = ">", required = 1)
    public IRubyObject op_gt(ThreadContext context, IRubyObject arg) {
        return cmp(context, arg,'>');
    }

    @JRubyMethod(name = ">=", required = 1)
    public IRubyObject op_ge(ThreadContext context, IRubyObject arg) {
        return cmp(context, arg,'G');
    }

    @JRubyMethod(name = "abs")
    public IRubyObject abs() {
        Ruby runtime = getRuntime();
        if (isNaN) {
            return newNaN(runtime);
        }
        if (isInfinity()) {
            return newInfinity(runtime, 1);
        }
        return new RubyBigDecimal(getRuntime(), value.abs()).setResult();
    }

    @JRubyMethod(name = "ceil", optional = 1)
    public IRubyObject ceil(IRubyObject[] args) {
        if (isNaN) {
            return newNaN(getRuntime());
        }
        if (isInfinity()) {
            return newInfinity(getRuntime(), infinitySign);
        }

        int n = 0;
        if (args.length > 0) {
            n = RubyNumeric.fix2int(args[0]);
        }
        
        if (value.scale() > n) { // rounding neccessary
            return new RubyBigDecimal(getRuntime(),
                    value.setScale(n, RoundingMode.CEILING));
        } else {
            return this;
        }
    }

    @JRubyMethod(name = "coerce", required = 1)
    public IRubyObject coerce(IRubyObject other) {
        IRubyObject obj;
        if(other instanceof RubyFloat) {
            obj = getRuntime().newArray(other,to_f());
        } else {
            obj = getRuntime().newArray(getVpValue(other, true),this);
        }
        return obj;
    }

    public double getDoubleValue() { return value.doubleValue(); }
    public long getLongValue() { return value.longValue(); }

    public RubyNumeric multiplyWith(ThreadContext context, RubyInteger value) { 
        return (RubyNumeric)op_mul(context, value);
    }

    public RubyNumeric multiplyWith(ThreadContext context, RubyFloat value) { 
        return (RubyNumeric)op_mul(context, value);
    }

    public RubyNumeric multiplyWith(ThreadContext context, RubyBignum value) { 
        return (RubyNumeric)op_mul(context, value);
    }

    @JRubyMethod(name = "divmod", required = 1)
    public IRubyObject divmod(ThreadContext context, IRubyObject other) {
        // TODO: full-precision divmod is 1000x slower than MRI!
        Ruby runtime = context.getRuntime();
        if (isInfinity() || isNaN()) {
            return RubyArray.newArray(runtime, newNaN(runtime), newNaN(runtime));
        }
        RubyBigDecimal val = getVpValue(other, false);
        if (val == null) {
            return callCoerced(context, "divmod", other, true);
        }
        if (val.isInfinity() || val.isNaN() || val.isZero()) {
            return RubyArray.newArray(runtime, newNaN(runtime), newNaN(runtime));
        }

        // Java and MRI definitions of divmod are different.        
        BigDecimal[] divmod = value.divideAndRemainder(val.value);

        BigDecimal div = divmod[0];
        BigDecimal mod = divmod[1];

        if (mod.signum() * val.value.signum() < 0) {
            div = div.subtract(BigDecimal.ONE);
            mod = mod.add(val.value);
        }

        return RubyArray.newArray(runtime,
                new RubyBigDecimal(runtime, div),
                new RubyBigDecimal(runtime, mod));
    }

    @JRubyMethod(name = "exponent")
    public IRubyObject exponent() {
        return getRuntime().newFixnum(getExponent());
    }

    @JRubyMethod(name = "finite?")
    public IRubyObject finite_p() {
        if (isNaN()) {
            return getRuntime().getFalse();
        }
        return getRuntime().newBoolean(!isInfinity());
    }

    @JRubyMethod(name = "floor", optional = 1)
    public IRubyObject floor(IRubyObject[]args) {
        if (isNaN) {
            return newNaN(getRuntime());
        }
        if (isInfinity()) {
            return newInfinity(getRuntime(), infinitySign);
        }

        int n = 0;
        if (args.length > 0) {
            n = RubyNumeric.fix2int(args[0]);
        }

        if (value.scale() > n) { // rounding neccessary
            return new RubyBigDecimal(getRuntime(),
                    value.setScale(n, RoundingMode.FLOOR));
        } else {
            return this;
        }
    }
 
    @JRubyMethod(name = "frac")
    public IRubyObject frac() {
        if (isNaN) {
            return newNaN(getRuntime());
        }
        if (isInfinity()) {
            return newInfinity(getRuntime(), infinitySign);
        }
        if (value.scale() > 0 && value.precision() < value.scale()) {
            return new RubyBigDecimal(getRuntime(), value);
        }

        BigDecimal val = value.subtract(((RubyBigDecimal)fix()).value);
        return new RubyBigDecimal(getRuntime(), val);
    }

    @JRubyMethod(name = "infinite?")
    public IRubyObject infinite_p() {
        if (infinitySign == 0) {
            return getRuntime().getNil();
        }
        return getRuntime().newFixnum(infinitySign);
    }

    @JRubyMethod(name = "inspect")
    public IRubyObject inspect(ThreadContext context) {
        StringBuilder val = new StringBuilder("#<BigDecimal:").append(Integer.toHexString(System.identityHashCode(this))).append(",");
        val.append("'").append(this.callMethod(context, MethodIndex.TO_S, "to_s")).append("'").append(",");

        val.append(getSignificantDigits().length()).append("(");

        int len = getAllDigits().length();
        int pow = len / 4;
        val.append((pow + 1) * 4).append(")").append(">");

        return getRuntime().newString(val.toString());
    }

    @JRubyMethod(name = "nan?")
    public IRubyObject nan_p() {
        return getRuntime().newBoolean(isNaN);
    }

    @JRubyMethod(name = "nonzero?")
    public IRubyObject nonzero_p() {
        return isZero() ? getRuntime().getNil() : this;
    }
 
    @JRubyMethod(name = "precs")
    public IRubyObject precs() {
        final Ruby runtime = getRuntime();
        final IRubyObject[] array = new IRubyObject[2];

        array[0] = runtime.newFixnum(getSignificantDigits().length());

        int len = getAllDigits().length();
        int pow = len / 4;
        array[1] = runtime.newFixnum((pow + 1) * 4);

        return RubyArray.newArray(runtime, array);
    }

    @JRubyMethod(name = "round", optional = 2)
    public IRubyObject round(IRubyObject[] args) {
        int scale = args.length > 0 ? num2int(args[0]) : 0;
        int mode = (args.length > 1) ? javaRoundingModeFromRubyRoundingMode(args[1]) : BigDecimal.ROUND_HALF_UP;
        // JRUBY-914: Java 1.4 BigDecimal does not allow a negative scale, so we have to simulate it
        if (scale < 0) {
          // shift the decimal point just to the right of the digit to be rounded to (divide by 10**(abs(scale)))
          // -1 -> 10's digit, -2 -> 100's digit, etc.
          BigDecimal normalized = value.movePointRight(scale);
          // ...round to that digit
          BigDecimal rounded = normalized.setScale(0,mode);
          // ...and shift the result back to the left (multiply by 10**(abs(scale)))
          return new RubyBigDecimal(getRuntime(), rounded.movePointLeft(scale));
        } else {
          return new RubyBigDecimal(getRuntime(), value.setScale(scale, mode));
        }
    }

    //this relies on the Ruby rounding enumerations == Java ones, which they (currently) all are
    private int javaRoundingModeFromRubyRoundingMode(IRubyObject arg) {
      return num2int(arg);
    }
    
    @JRubyMethod(name = "sign")
    public IRubyObject sign() {
        if (isNaN()) {
            return getMetaClass().fastGetConstant("SIGN_NaN");
        }

        if (isInfinity()) {
            if (infinitySign < 0) {
                return getMetaClass().fastGetConstant("SIGN_NEGATIVE_INFINITE");
            } else {
                return getMetaClass().fastGetConstant("SIGN_POSITIVE_INFINITE");
            }
        }

        if (isZero()) {
            if (zeroSign < 0) {
                return getMetaClass().fastGetConstant("SIGN_NEGATIVE_ZERO");
            } else {
                return getMetaClass().fastGetConstant("SIGN_POSITIVE_ZERO");
            }
        }
        
        if (value.signum() < 0) {
            return getMetaClass().fastGetConstant("SIGN_NEGATIVE_FINITE");
        } else {
            return getMetaClass().fastGetConstant("SIGN_POSITIVE_FINITE");
        }
    }

    @JRubyMethod(name = "split")
    public RubyArray split() {
        final Ruby runtime = getRuntime();
        final IRubyObject[] array = new IRubyObject[4];

        // sign
        final RubyFixnum sign;
        if (isNaN) {
            sign = RubyFixnum.zero(runtime);
        } else if (isInfinity()) {
            sign = runtime.newFixnum(infinitySign);
        } else if (isZero()){
            sign = runtime.newFixnum(zeroSign);
        } else {
            sign = runtime.newFixnum(value.signum());
        }
        array[0] = sign;

        // significant digits and exponent
        final RubyString digits;
        final RubyFixnum exp;
        if (isNaN()) {
            digits = runtime.newString("NaN");
            exp = RubyFixnum.zero(runtime);
        } else if (isInfinity()) {
            digits = runtime.newString("Infinity");
            exp = RubyFixnum.zero(runtime);
        } else if (isZero()){
            digits = runtime.newString("0");
            exp = RubyFixnum.zero(runtime);
        } else {
            // normalize the value
            digits = runtime.newString(getSignificantDigits());
            exp = runtime.newFixnum(getExponent());
        }
        array[1] = digits;
        array[3] = exp;

        // base
        array[2] = runtime.newFixnum(10);

        return RubyArray.newArray(runtime, array);
    }

    // it doesn't handle special cases
    private String getSignificantDigits() {
        // TODO: no need to calculate every time.
        BigDecimal val = value.abs().stripTrailingZeros();
        return val.unscaledValue().toString();
    }

    private String getAllDigits() {
        // TODO: no need to calculate every time.
        BigDecimal val = value.abs();
        return val.unscaledValue().toString();
    }    

    // it doesn't handle special cases
    private int getExponent() {
        // TODO: no need to calculate every time.
        if (isZero()) {
            return 0;
        }
        BigDecimal val = value.abs().stripTrailingZeros();
        return val.precision() - val.scale();
    }

    @JRubyMethod(name = "sqrt", required = 1)
    public IRubyObject sqrt(IRubyObject arg) {
        Ruby runtime = getRuntime();
        if (isNaN()) {
            throw runtime.newFloatDomainError("(VpSqrt) SQRT(NaN value)");
        }
        if ((isInfinity() && infinitySign < 0) || value.signum() < 0) {
            throw runtime.newFloatDomainError("(VpSqrt) SQRT(negative value)");
        }
        if (isInfinity() && infinitySign > 0) {
            return newInfinity(runtime, 1);
        }

        // NOTE: MRI's sqrt precision is limited by 100,
        // but we allow values more than 100.
        int n = RubyNumeric.fix2int(arg);
        if (n < 0) {
            throw runtime.newArgumentError("argument must be positive");
        }

        n += 4; // just in case, add a bit of extra precision

        return new RubyBigDecimal(getRuntime(),
                bigSqrt(this.value, new MathContext(n, RoundingMode.HALF_UP))).setResult();
    }

    @JRubyMethod(name = "to_f")
    public IRubyObject to_f() {
        if (isNaN()) {
            return RubyFloat.newFloat(getRuntime(), Double.NaN);
        }
        if (isInfinity()) {
            return RubyFloat.newFloat(getRuntime(),
                    infinitySign < 0 ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY);
        }
        if (isZero()) {
            return RubyFloat.newFloat(getRuntime(),
                    zeroSign < 0 ? -0.0 : 0.0);
        }
        return RubyFloat.newFloat(getRuntime(), value.doubleValue());
    }

    @JRubyMethod(name = {"to_i", "to_int"})
    public IRubyObject to_int() {
        if (isNaN() || infinitySign != 0) {
            return getRuntime().getNil();
        }
        try {
            return RubyNumeric.int2fix(getRuntime(), value.longValueExact());
        } catch (ArithmeticException ae) {
            return RubyBignum.bignorm(getRuntime(), value.toBigInteger());            
        }
    }

    private String removeTrailingZeroes(String in) {
        while(in.length() > 0 && in.charAt(in.length()-1)=='0') {
            in = in.substring(0,in.length()-1);
        }
        return in;
    }
  
    public static boolean formatHasLeadingPlus(String format) {
        return format.startsWith("+");
    }

    public static boolean formatHasLeadingSpace(String format) {
        return format.startsWith(" ");
    }

    public static boolean formatHasFloatingPointNotation(String format) {
        return format.endsWith("F");
    }

    public static int formatFractionalDigitGroups(String format) {
        int groups = 0;
        Pattern p = Pattern.compile("(\\+| )?(\\d+)(E|F)?");
        Matcher m = p.matcher(format);
        if (m.matches()) {
            groups = Integer.parseInt(m.group(2));
        }
        return groups;
    }
    
    private boolean hasArg(IRubyObject[] args) {
        return args.length != 0 && !args[0].isNil();
    }

    private String format(IRubyObject[] args) {
        return args[0].toString();
    }

    private String firstArgument(IRubyObject[] args) {
        if (hasArg(args)) {
            return format(args);
        }
        return null;
    }

    private boolean posSpace(String arg) {
        if (null != arg) {
            return formatHasLeadingSpace(arg);
        }
        return false;
    }

    private boolean posSign(String arg) {
        if (null != arg) {
            return formatHasLeadingPlus(arg) || posSpace(arg);
        }
        return false;
    }

    private boolean asEngineering(String arg) {
        if (null != arg) {
            return !formatHasFloatingPointNotation(arg);
        }
        return true;
    }

    private int groups(String arg) {
        if (null != arg) {
            return formatFractionalDigitGroups(arg);
        }
        return 0;
    }

    private boolean isZero() {
        return !isNaN() && !isInfinity() && (value.signum() == 0);
    }

    private boolean isNaN() {
        return isNaN;
    }

    private boolean isInfinity() {
        return infinitySign != 0;
    }

    private String unscaledValue() {
        return value.abs().unscaledValue().toString();
    }

    private IRubyObject engineeringValue(String arg) {
        int exponent = getExponent();
        int signum = value.signum();
        StringBuilder build = new StringBuilder();
        build.append(signum == -1 ? "-" : (signum == 1 ? (posSign(arg) ? (posSpace(arg) ? " " : "+") : "") : ""));
        build.append("0.");
        if (0 == groups(arg)) {
            String s = removeTrailingZeroes(unscaledValue());
            if ("".equals(s)) {
                build.append("0");
            } else {
                build.append(s);
            }
        } else {
            int index = 0;
            String sep = "";
            while (index < unscaledValue().length()) {
                int next = index + groups(arg);
                if (next > unscaledValue().length()) {
                    next = unscaledValue().length();
                }
                build.append(sep).append(unscaledValue().substring(index, next));
                sep = " ";
                index += groups(arg);
            }
        }
        build.append("E").append(exponent);
        return getRuntime().newString(build.toString());
    }

    private IRubyObject floatingPointValue(String arg) {
        String values[] = value.abs().stripTrailingZeros().toPlainString().split("\\.");
        String whole = "0";
        if (values.length > 0) {
            whole = values[0];
        }
        String after = "0";
        if (values.length > 1) {
            after = values[1];
        }
        int signum = value.signum();
        StringBuilder build = new StringBuilder();
        build.append(signum == -1 ? "-" : (signum == 1 ? (posSign(arg) ? (posSpace(arg) ? " " : "+") : "") : ""));
        if (groups(arg) == 0) {
            build.append(whole);
            if (null != after) {
                build.append(".").append(after);
            }
        } else {
            int index = 0;
            String sep = "";
            while (index < whole.length()) {
                int next = index + groups(arg);
                if (next > whole.length()) {
                    next = whole.length();
                }
                build.append(sep).append(whole.substring(index, next));
                sep = " ";
                index += groups(arg);
            }
            if (null != after) {
                build.append(".");
                index = 0;
                sep = "";
                while (index < after.length()) {
                    int next = index + groups(arg);
                    if (next > after.length()) {
                        next = after.length();
                    }
                    build.append(sep).append(after.substring(index, next));
                    sep = " ";
                    index += groups(arg);
                }
            }
        }
        return getRuntime().newString(build.toString());
    }
            
    @JRubyMethod(name = "to_s", optional = 1)
    public IRubyObject to_s(IRubyObject[] args) {
        String arg = firstArgument(args);
        if (isNaN()) {
            return getRuntime().newString("NaN");
        }
        if (infinitySign != 0) {
            if (infinitySign == -1) {
                return getRuntime().newString("-Infinity");
            } else {
                return getRuntime().newString("Infinity");
            }
        }
        if (isZero()) {
            String zero = "0.0";
            if (zeroSign < 0) {
                zero = "-" + zero;
            }
            return getRuntime().newString(zero);
        }
        if(asEngineering(arg)) {
            return engineeringValue(arg);
        } else {
            return floatingPointValue(arg);
        }
    }

    // Note: #fix has only no-arg form, but truncate allows optional parameter.

    @JRubyMethod
    public IRubyObject fix() {
        return truncate(RubyFixnum.zero(getRuntime()));
    }

    @JRubyMethod
    public IRubyObject truncate() {
        return truncate(RubyFixnum.zero(getRuntime()));
    }

    @JRubyMethod
    public IRubyObject truncate(IRubyObject arg) {
        if (isNaN) {
            return newNaN(getRuntime());
        }
        if (isInfinity()) {
            return newInfinity(getRuntime(), infinitySign);
        }

        int n = RubyNumeric.fix2int(arg);
        
        int precision = value.precision() - value.scale() + n;
        
        if (precision > 0) {
            return new RubyBigDecimal(getRuntime(),
                    value.round(new MathContext(precision, RoundingMode.DOWN)));
        } else {
            // TODO: proper sign
            return new RubyBigDecimal(getRuntime(), BigDecimal.ZERO);
        }
    }

    @JRubyMethod(name = "zero?")
    public IRubyObject zero_p() {
         return getRuntime().newBoolean(isZero());
    }

    /**
     * Returns the correctly rounded square root of a positive
     * BigDecimal. This method performs the fast <i>Square Root by
     * Coupled Newton Iteration</i> algorithm by Timm Ahrendt, from
     * the book "Pi, unleashed" by Jörg Arndt in a neat loop.
     * <p>
     * The code is based on Frans Lelieveld's code , used here with
     * permission.
     *
     * @param squarD The number to get the root from.
     * @param rootMC Precision and rounding mode.
     * @return the root of the argument number
     * @throws ArithmeticException
     *                 if the argument number is negative
     * @throws IllegalArgumentException
     *                 if rootMC has precision 0
     */
    public static BigDecimal bigSqrt(BigDecimal squarD, MathContext rootMC) {
       // General number and precision checking
      int sign = squarD.signum();
      if (sign == -1) {
          throw new ArithmeticException("Square root of a negative number: " + squarD);
      } else if(sign == 0) {
          return squarD.round(rootMC);
      }

      int prec = rootMC.getPrecision();           // the requested precision
      if (prec == 0) {
          throw new IllegalArgumentException("Most roots won't have infinite precision = 0");
      }

      // Initial precision is that of double numbers 2^63/2 ~ 4E18
      int BITS = 62;                              // 63-1 an even number of number bits
      int nInit = 16;                             // precision seems 16 to 18 digits
      MathContext nMC = new MathContext(18, RoundingMode.HALF_DOWN);

      // Iteration variables, for the square root x and the reciprocal v
      BigDecimal x = null, e = null;              // initial x:  x0 ~ sqrt()
      BigDecimal v = null, g = null;              // initial v:  v0 = 1/(2*x)

      // Estimate the square root with the foremost 62 bits of squarD
      BigInteger bi = squarD.unscaledValue();     // bi and scale are a tandem
      int biLen = bi.bitLength();
      int shift = Math.max(0, biLen - BITS + (biLen%2 == 0 ? 0 : 1));   // even shift..
      bi = bi.shiftRight(shift);                  // ..floors to 62 or 63 bit BigInteger

      double root = Math.sqrt(bi.doubleValue());
      BigDecimal halfBack = new BigDecimal(BigInteger.ONE.shiftLeft(shift/2));

      int scale = squarD.scale();
      if (scale % 2 == 1) {
          root *= SQRT_10;                        // 5 -> 2, -5 -> -3 need half a scale more..
      }
      scale = (int) Math.floor(scale/2.);         // ..where 100 -> 10 shifts the scale

      // Initial x - use double root - multiply by halfBack to unshift - set new scale
      x = new BigDecimal(root, nMC);
      x = x.multiply(halfBack, nMC);              // x0 ~ sqrt()
      if (scale != 0) {
          x = x.movePointLeft(scale);
      }

      if (prec < nInit) {                // for prec 15 root x0 must surely be OK
          return x.round(rootMC);        // return small prec roots without iterations
      }

      // Initial v - the reciprocal
      v = BigDecimal.ONE.divide(TWO.multiply(x), nMC);        // v0 = 1/(2*x)

      // Collect iteration precisions beforehand
      List<Integer> nPrecs = new ArrayList<Integer>();

      assert nInit > 3 : "Never ending loop!";                // assume nInit = 16 <= prec

      // Let m be the exact digits precision in an earlier! loop
      for (int m = prec + 1; m > nInit; m = m/2 + (m > 100 ? 1 : 2)) {
          nPrecs.add(m);
      }

      // The loop of "Square Root by Coupled Newton Iteration"
      for (int i = nPrecs.size() - 1; i > -1; i--) {
          // Increase precision - next iteration supplies n exact digits
          nMC = new MathContext(nPrecs.get(i), (i%2 == 1) ? RoundingMode.HALF_UP : 
                                                          RoundingMode.HALF_DOWN);

          // Next x                                        // e = d - x^2
          e = squarD.subtract(x.multiply(x, nMC), nMC);
          if (i != 0) {
              x = x.add(e.multiply(v, nMC));               // x += e*v     ~ sqrt()
          } else {
              x = x.add(e.multiply(v, rootMC), rootMC);    // root x is ready!
              break;
          }

          // Next v                                        // g = 1 - 2*x*v
          g = BigDecimal.ONE.subtract(TWO.multiply(x).multiply(v, nMC));

          v = v.add(g.multiply(v, nMC));                   // v += g*v     ~ 1/2/sqrt()
      }

      return x;                      // return sqrt(squarD) with precision of rootMC
    }
}// RubyBigdecimal
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2002-2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004-2005 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.common.IRubyWarnings.ID;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.marshal.MarshalStream;
import org.jruby.runtime.marshal.UnmarshalStream;

/**
 *
 * @author  jpetersen
 */
@JRubyClass(name="Bignum", parent="Integer")
public class RubyBignum extends RubyInteger {
    public static RubyClass createBignumClass(Ruby runtime) {
        RubyClass bignum = runtime.defineClass("Bignum", runtime.getInteger(),
                ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
        runtime.setBignum(bignum);
        bignum.index = ClassIndex.BIGNUM;
        
        bignum.defineAnnotatedMethods(RubyBignum.class);

        return bignum;
    }

    private static final int BIT_SIZE = 64;
    private static final long MAX = (1L << (BIT_SIZE - 1)) - 1;
    private static final BigInteger LONG_MAX = BigInteger.valueOf(MAX);
    private static final BigInteger LONG_MIN = BigInteger.valueOf(-MAX - 1);

    private final BigInteger value;

    public RubyBignum(Ruby runtime, BigInteger value) {
        super(runtime, runtime.getBignum());
        this.value = value;
    }
    
    public int getNativeTypeIndex() {
        return ClassIndex.BIGNUM;
    }
    
    public Class<?> getJavaClass() {
        return BigInteger.class;
    }

    public static RubyBignum newBignum(Ruby runtime, long value) {
        return newBignum(runtime, BigInteger.valueOf(value));
    }

    public static RubyBignum newBignum(Ruby runtime, double value) {
        return newBignum(runtime, new BigDecimal(value).toBigInteger());
    }

    public static RubyBignum newBignum(Ruby runtime, BigInteger value) {
        return new RubyBignum(runtime, value);
    }

    public static RubyBignum newBignum(Ruby runtime, String value) {
        return new RubyBignum(runtime, new BigInteger(value));
    }

    public double getDoubleValue() {
        return big2dbl(this);
    }

    public long getLongValue() {
        return big2long(this);
    }

    /** Getter for property value.
     * @return Value of property value.
     */
    public BigInteger getValue() {
        return value;
    }

    /*  ================
     *  Utility Methods
     *  ================ 
     */

    /* If the value will fit in a Fixnum, return one of those. */
    /** rb_big_norm
     * 
     */
    public static RubyInteger bignorm(Ruby runtime, BigInteger bi) {
        if (bi.compareTo(LONG_MIN) < 0 || bi.compareTo(LONG_MAX) > 0) {
            return newBignum(runtime, bi);
        }
        return runtime.newFixnum(bi.longValue());
    }

    /** rb_big2long
     * 
     */
    public static long big2long(RubyBignum value) {
        BigInteger big = value.getValue();

        if (big.compareTo(LONG_MIN) < 0 || big.compareTo(LONG_MAX) > 0) {
            throw value.getRuntime().newRangeError("bignum too big to convert into `long'");
        }
        return big.longValue();
    }

    /** rb_big2dbl
     * 
     */
    public static double big2dbl(RubyBignum value) {
        BigInteger big = value.getValue();
        double dbl = convertToDouble(big);
        if (dbl == Double.NEGATIVE_INFINITY || dbl == Double.POSITIVE_INFINITY) {
            value.getRuntime().getWarnings().warn(ID.BIGNUM_FROM_FLOAT_RANGE, "Bignum out of Float range");
    }
        return dbl;
    }
    
    private IRubyObject checkShiftDown(RubyBignum other) {
        if (other.value.signum() == 0) return RubyFixnum.zero(getRuntime()); 
        if (value.compareTo(LONG_MIN) < 0 || value.compareTo(LONG_MAX) > 0) {
            return other.value.signum() >= 0 ? RubyFixnum.zero(getRuntime()) : RubyFixnum.minus_one(getRuntime());
        }
        return getRuntime().getNil();
    }

    /**
     * BigInteger#doubleValue is _really_ slow currently.
     * This is faster, and mostly correct (?)
     */
    static double convertToDouble(BigInteger bigint) {
        byte[] arr = bigint.toByteArray();
        double res = 0;
        double acc = 1;
        for (int i = arr.length - 1; i > 0 ; i--)
        {
            res += (double) (arr[i] & 0xff) * acc;
            acc *= 256;
        }
        res += (double) arr[0] * acc; // final byte sign is significant
        return res;
    }
    
    /** rb_int2big
     * 
     */
    public static BigInteger fix2big(RubyFixnum arg) {
        return BigInteger.valueOf(arg.getLongValue());
    }

    /*  ================
     *  Instance Methods
     *  ================ 
     */

    /** rb_big_to_s
     * 
     */
    @JRubyMethod(name = "to_s", optional = 1)
    public IRubyObject to_s(IRubyObject[] args) {
        int base = args.length == 0 ? 10 : num2int(args[0]);
        if (base < 2 || base > 36) {
            throw getRuntime().newArgumentError("illegal radix " + base);
    }
        return getRuntime().newString(getValue().toString(base));
    }

    /** rb_big_coerce
     * 
     */
    @JRubyMethod(name = "coerce", required = 1)
    public IRubyObject coerce(IRubyObject other) {
        if (other instanceof RubyFixnum) {
            return getRuntime().newArray(newBignum(getRuntime(), ((RubyFixnum) other).getLongValue()), this);
        } else if (other instanceof RubyBignum) {
            return getRuntime().newArray(newBignum(getRuntime(), ((RubyBignum) other).getValue()), this);
    }

        throw getRuntime().newTypeError("Can't coerce " + other.getMetaClass().getName() + " to Bignum");
    }

    /** rb_big_uminus
     * 
     */
    @JRubyMethod(name = "-@")
    public IRubyObject op_uminus() {
        return bignorm(getRuntime(), value.negate());
    }

    /** rb_big_plus
     * 
     */
    @JRubyMethod(name = "+", required = 1)
    public IRubyObject op_plus(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyFixnum) {
            return addFixnum((RubyFixnum)other);
        } else if (other instanceof RubyBignum) {
            return addBignum((RubyBignum)other);
        } else if (other instanceof RubyFloat) {
            return addFloat((RubyFloat)other);
        }
        return addOther(context, other);
    }
    
    private IRubyObject addFixnum(RubyFixnum other) {
        return bignorm(getRuntime(), value.add(fix2big(other)));
    }
    
    private IRubyObject addBignum(RubyBignum other) {
        return bignorm(getRuntime(), value.add(other.value));
    }
    
    private IRubyObject addFloat(RubyFloat other) {
        return RubyFloat.newFloat(getRuntime(), big2dbl(this) + other.getDoubleValue());
    }
    
    private IRubyObject addOther(ThreadContext context, IRubyObject other) {
        return coerceBin(context, "+", other);
    }

    /** rb_big_minus
     * 
     */
    @JRubyMethod(name = "-", required = 1)
    public IRubyObject op_minus(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyFixnum) {
            return subtractFixnum((RubyFixnum)other);
        } else if (other instanceof RubyBignum) {
            return subtractBignum((RubyBignum)other);
        } else if (other instanceof RubyFloat) {
            return subtractFloat((RubyFloat)other);
        }
        return subtractOther(context, other);
    }
    
    private IRubyObject subtractFixnum(RubyFixnum other) {
        return bignorm(getRuntime(), value.subtract(fix2big(((RubyFixnum) other))));
    }
    
    private IRubyObject subtractBignum(RubyBignum other) {
        return bignorm(getRuntime(), value.subtract(((RubyBignum) other).value));
    }
    
    private IRubyObject subtractFloat(RubyFloat other) {
        return RubyFloat.newFloat(getRuntime(), big2dbl(this) - ((RubyFloat) other).getDoubleValue());
    }
    
    private IRubyObject subtractOther(ThreadContext context, IRubyObject other) {
        return coerceBin(context, "-", other);
    }

    /** rb_big_mul
     * 
     */
    @JRubyMethod(name = "*", required = 1)
    public IRubyObject op_mul(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyFixnum) {
            return bignorm(getRuntime(), value.multiply(fix2big(((RubyFixnum) other))));
        }
        if (other instanceof RubyBignum) {
            return bignorm(getRuntime(), value.multiply(((RubyBignum) other).value));
        } else if (other instanceof RubyFloat) {
            return RubyFloat.newFloat(getRuntime(), big2dbl(this) * ((RubyFloat) other).getDoubleValue());
        }
        return coerceBin(context, "*", other);
    }

    /**
     * rb_big_divide. Shared part for both "/" and "div" operations.
     */
    private IRubyObject op_divide(ThreadContext context, IRubyObject other, String op) {
        assert ("/".equals(op) || "div".equals(op));

        final BigInteger otherValue;
        if (other instanceof RubyFixnum) {
            otherValue = fix2big((RubyFixnum) other);
        } else if (other instanceof RubyBignum) {
            otherValue = ((RubyBignum) other).value;
        } else if (other instanceof RubyFloat) {
            double div = big2dbl(this) / ((RubyFloat) other).getDoubleValue();
            if ("/".equals(op)) {
                return RubyFloat.newFloat(getRuntime(),
                        big2dbl(this) / ((RubyFloat) other).getDoubleValue());
            } else {
                return RubyNumeric.dbl2num(getRuntime(), div);
            }
        } else {
            return coerceBin(context, op, other);
        }

        if (otherValue.equals(BigInteger.ZERO)) {
            throw getRuntime().newZeroDivisionError();
        }

        BigInteger[] results = value.divideAndRemainder(otherValue);

        if ((value.signum() * otherValue.signum()) == -1 && results[1].signum() != 0) {
            return bignorm(getRuntime(), results[0].subtract(BigInteger.ONE));
        }
        return bignorm(getRuntime(), results[0]);
    }

    /** rb_big_div
     *
     */
    @JRubyMethod(name = {"/"}, required = 1)
    public IRubyObject op_div(ThreadContext context, IRubyObject other) {
        return op_divide(context, other, "/");
    }

    /** rb_big_idiv
     *
     */
    @JRubyMethod(name = {"div"}, required = 1)
    public IRubyObject op_idiv(ThreadContext context, IRubyObject other) {
        return op_divide(context, other, "div");
    }

    /** rb_big_divmod
     * 
     */
    @JRubyMethod(name = "divmod", required = 1)
    public IRubyObject divmod(ThreadContext context, IRubyObject other) {
        final BigInteger otherValue;
        if (other instanceof RubyFixnum) {
            otherValue = fix2big((RubyFixnum) other);
        } else if (other instanceof RubyBignum) {
            otherValue = ((RubyBignum) other).value;
        } else {
            return coerceBin(context, "divmod", other);
        }

        if (otherValue.equals(BigInteger.ZERO)) {
            throw getRuntime().newZeroDivisionError();
        }

        BigInteger[] results = value.divideAndRemainder(otherValue);

        if ((value.signum() * otherValue.signum()) == -1 && results[1].signum() != 0) {
            results[0] = results[0].subtract(BigInteger.ONE);
            results[1] = otherValue.add(results[1]);
    	}
        final Ruby runtime = getRuntime();
        return RubyArray.newArray(getRuntime(), bignorm(runtime, results[0]), bignorm(runtime, results[1]));
    }

    /** rb_big_modulo
     * 
     */
    @JRubyMethod(name = {"%", "modulo"}, required = 1)
    public IRubyObject op_mod(ThreadContext context, IRubyObject other) {
        final BigInteger otherValue;
        if (other instanceof RubyFixnum) {
            otherValue = fix2big((RubyFixnum) other);
        } else if (other instanceof RubyBignum) {
            otherValue = ((RubyBignum) other).value;
        } else {
            return coerceBin(context, "%", other);
        }
        if (otherValue.equals(BigInteger.ZERO)) {
            throw getRuntime().newZeroDivisionError();
        }
        BigInteger result = value.mod(otherValue.abs());
        if (otherValue.signum() == -1 && result.signum() != 0) {
            result = otherValue.add(result);
        }
        return bignorm(getRuntime(), result);

            }

    /** rb_big_remainder
     * 
     */
    @JRubyMethod(name = "remainder", required = 1)
    public IRubyObject remainder(ThreadContext context, IRubyObject other) {
        final BigInteger otherValue;
        if (other instanceof RubyFixnum) {
            otherValue = fix2big(((RubyFixnum) other));
        } else if (other instanceof RubyBignum) {
            otherValue = ((RubyBignum) other).value;
        } else {
            return coerceBin(context, "remainder", other);
        }
        if (otherValue.equals(BigInteger.ZERO)) {
            throw getRuntime().newZeroDivisionError();
    }
        return bignorm(getRuntime(), value.remainder(otherValue));
    }

    /** rb_big_quo

     * 
     */
    @JRubyMethod(name = "quo", required = 1)
    public IRubyObject quo(ThreadContext context, IRubyObject other) {
    	if (other instanceof RubyNumeric) {
            return RubyFloat.newFloat(getRuntime(), big2dbl(this) / ((RubyNumeric) other).getDoubleValue());
        } else {
            return coerceBin(context, "quo", other);
    	}
    }

    /** rb_big_pow
     * 
     */
    @JRubyMethod(name = {"**", "power"}, required = 1)
    public IRubyObject op_pow(ThreadContext context, IRubyObject other) {
        double d;
        if (other instanceof RubyFixnum) {
            RubyFixnum fix = (RubyFixnum) other;
            long fixValue = fix.getLongValue();
            // MRI issuses warning here on (RBIGNUM(x)->len * SIZEOF_BDIGITS * yy > 1024*1024)
            if (((value.bitLength() + 7) / 8) * 4 * Math.abs(fixValue) > 1024 * 1024) {
                getRuntime().getWarnings().warn(ID.MAY_BE_TOO_BIG, "in a**b, b may be too big", fixValue);
    	}
            if (fixValue >= 0) {
                return bignorm(getRuntime(), value.pow((int) fixValue)); // num2int is also implemented
            } else {
                return RubyFloat.newFloat(getRuntime(), Math.pow(big2dbl(this), (double)fixValue));
            }
        } else if (other instanceof RubyBignum) {
            d = ((RubyBignum) other).getDoubleValue();
            getRuntime().getWarnings().warn(ID.MAY_BE_TOO_BIG, "in a**b, b may be too big", d);
        } else if (other instanceof RubyFloat) {
            d = ((RubyFloat) other).getDoubleValue();
        } else {
            return coerceBin(context, "**", other);

        }
        return RubyFloat.newFloat(getRuntime(), Math.pow(big2dbl(this), d));
    }

    /** rb_big_pow
     * 
     */
    @JRubyMethod(name = {"**", "power"}, required = 1, compat = CompatVersion.RUBY1_9)
    public IRubyObject op_pow_19(ThreadContext context, IRubyObject other) {
        Ruby runtime = context.getRuntime();
        if (other == RubyFixnum.zero(runtime)) return RubyFixnum.one(runtime);
        double d;
        if (other instanceof RubyFixnum) {
            RubyFixnum fix = (RubyFixnum) other;
            long fixValue = fix.getLongValue();
            
            if (fixValue < 0) {
                return RubyRational.newRationalRaw(runtime, this).callMethod(context, "**", other);
            }
            // MRI issuses warning here on (RBIGNUM(x)->len * SIZEOF_BDIGITS * yy > 1024*1024)
            if (((value.bitLength() + 7) / 8) * 4 * Math.abs(fixValue) > 1024 * 1024) {
                getRuntime().getWarnings().warn(ID.MAY_BE_TOO_BIG, "in a**b, b may be too big", fixValue);
            }
            if (fixValue >= 0) {
                return bignorm(runtime, value.pow((int) fixValue)); // num2int is also implemented
            } else {
                return RubyFloat.newFloat(runtime, Math.pow(big2dbl(this), (double)fixValue));
            }
        } else if (other instanceof RubyBignum) {
            if (other.callMethod(context, "<", RubyFixnum.zero(runtime)).isTrue()) {
                return RubyRational.newRationalRaw(runtime, this).callMethod(context, "**", other);
            }
            d = ((RubyBignum) other).getDoubleValue();
            getRuntime().getWarnings().warn(ID.MAY_BE_TOO_BIG, "in a**b, b may be too big", d);
        } else if (other instanceof RubyFloat) {
            d = ((RubyFloat) other).getDoubleValue();
        } else {
            return coerceBin(context, "**", other);

        }
        return RubyNumeric.dbl2num(runtime, Math.pow(big2dbl(this), d));
    }    
    
    /** rb_big_and
     * 
     */
    @JRubyMethod(name = "&", required = 1)
    public IRubyObject op_and(ThreadContext context, IRubyObject other) {
        other = other.convertToInteger();
        if (other instanceof RubyBignum) {
            return bignorm(getRuntime(), value.and(((RubyBignum) other).value));
        } else if(other instanceof RubyFixnum) {
            return bignorm(getRuntime(), value.and(fix2big((RubyFixnum)other)));
        }
        return coerceBin(context, "&", other);
    }

    /** rb_big_or
     * 
     */
    @JRubyMethod(name = "|", required = 1)
    public IRubyObject op_or(ThreadContext context, IRubyObject other) {
        other = other.convertToInteger();
        if (other instanceof RubyBignum) {
            return bignorm(getRuntime(), value.or(((RubyBignum) other).value));
        }
        if (other instanceof RubyFixnum) { // no bignorm here needed
            return bignorm(getRuntime(), value.or(fix2big((RubyFixnum)other)));
        }
        return coerceBin(context, "|", other);
    }

    /** rb_big_xor
     * 
     */
    @JRubyMethod(name = "^", required = 1)
    public IRubyObject op_xor(ThreadContext context, IRubyObject other) {
        other = other.convertToInteger();
        if (other instanceof RubyBignum) {
            return bignorm(getRuntime(), value.xor(((RubyBignum) other).value));
		}
        if (other instanceof RubyFixnum) {
            return bignorm(getRuntime(), value.xor(BigInteger.valueOf(((RubyFixnum) other).getLongValue())));
        }
        return coerceBin(context, "^", other);
    }

    /** rb_big_neg     
     * 
     */
    @JRubyMethod(name = "~")
    public IRubyObject op_neg() {
        return RubyBignum.newBignum(getRuntime(), value.not());
    }

    /** rb_big_lshift     
     * 
     */
    @JRubyMethod(name = "<<", required = 1)
    public IRubyObject op_lshift(IRubyObject other) {
        long shift;
        boolean neg = false;

        for (;;) {
            if (other instanceof RubyFixnum) {
                shift = ((RubyFixnum)other).getLongValue();
                if (shift < 0) {
                    neg = true;
                    shift = -shift;
                }
                break;
            } else if (other instanceof RubyBignum) {
                RubyBignum otherBignum = (RubyBignum)other;
                if (otherBignum.value.signum() < 0) {
                    IRubyObject tmp = otherBignum.checkShiftDown(this);
                    if (!tmp.isNil()) return tmp;
                    neg = true;
                }
                shift = big2long(otherBignum);
                break;
            }
            other = other.convertToInteger();
        }

        return bignorm(getRuntime(), neg ? value.shiftRight((int)shift) : value.shiftLeft((int)shift));
    }

    /** rb_big_rshift     
     * 
     */
    @JRubyMethod(name = ">>", required = 1)
    public IRubyObject op_rshift(IRubyObject other) {
        long shift;
        boolean neg = false;

        for (;;) {
            if (other instanceof RubyFixnum) {
                shift = ((RubyFixnum)other).getLongValue();
                if (shift < 0) {
                    neg = true;
                    shift = -shift;
                }
                break;
            } else if (other instanceof RubyBignum) {
                RubyBignum otherBignum = (RubyBignum)other;
                if (otherBignum.value.signum() >= 0) {
                    IRubyObject tmp = otherBignum.checkShiftDown(this);
                    if (!tmp.isNil()) return tmp;
                } else {
                    neg = true;
                }
                shift = big2long(otherBignum);
                break;
            }
            other = other.convertToInteger();
        }
        return bignorm(getRuntime(), neg ? value.shiftLeft((int)shift) : value.shiftRight((int)shift));
    }

    /** rb_big_aref
     *
     */
    @JRubyMethod(name = "[]", required = 1)
    public RubyFixnum op_aref(IRubyObject other) {
        if (other instanceof RubyBignum) {
            if (((RubyBignum) other).value.signum() >= 0 || value.signum() == -1) {
                return RubyFixnum.zero(getRuntime());
            }
            return RubyFixnum.one(getRuntime());
        }
        long position = num2long(other);
        if (position < 0 || position > Integer.MAX_VALUE) {
            return RubyFixnum.zero(getRuntime());
        }
        
        return value.testBit((int)position) ? RubyFixnum.one(getRuntime()) : RubyFixnum.zero(getRuntime());
    }

    /** rb_big_cmp     
     * 
     */
    @JRubyMethod(name = "<=>", required = 1)
    public IRubyObject op_cmp(ThreadContext context, IRubyObject other) {
        final BigInteger otherValue;
        if (other instanceof RubyFixnum) {
            otherValue = fix2big((RubyFixnum) other);
        } else if (other instanceof RubyBignum) {
            otherValue = ((RubyBignum) other).value;
        } else if (other instanceof RubyFloat) {
            return dbl_cmp(getRuntime(), big2dbl(this), ((RubyFloat) other).getDoubleValue());
        } else {
            return coerceCmp(context, "<=>", other);
        }

        // wow, the only time we can use the java protocol ;)        
        return RubyFixnum.newFixnum(getRuntime(), value.compareTo(otherValue));
    }

    /** rb_big_eq     
     * 
     */
    @JRubyMethod(name = "==", required = 1)
    public IRubyObject op_equal(IRubyObject other) {
        final BigInteger otherValue;
        if (other instanceof RubyFixnum) {
            otherValue = fix2big((RubyFixnum) other);
        } else if (other instanceof RubyBignum) {
            otherValue = ((RubyBignum) other).value;
        } else if (other instanceof RubyFloat) {
            double a = ((RubyFloat) other).getDoubleValue();
            if (Double.isNaN(a)) {
                return getRuntime().getFalse();
            }
            return RubyBoolean.newBoolean(getRuntime(), a == big2dbl(this));
        } else {
            return other.op_eqq(getRuntime().getCurrentContext(), this);
        }
        return RubyBoolean.newBoolean(getRuntime(), value.compareTo(otherValue) == 0);
    }

    /** rb_big_eql     
     * 
     */
    @JRubyMethod(name = {"eql?", "==="}, required = 1)
    public IRubyObject eql_p(IRubyObject other) {
        if (other instanceof RubyBignum) {
            return value.compareTo(((RubyBignum)other).value) == 0 ? getRuntime().getTrue() : getRuntime().getFalse();
        }
        return getRuntime().getFalse();
    }

    /** rb_big_hash
     * 
     */
    @JRubyMethod(name = "hash")
    public RubyFixnum hash() {
        return getRuntime().newFixnum(value.hashCode());
        }

    /** rb_big_to_f
     * 
     */
    @JRubyMethod(name = "to_f")
    public IRubyObject to_f() {
        return RubyFloat.newFloat(getRuntime(), getDoubleValue());
    }

    /** rb_big_abs
     * 
     */
    @JRubyMethod(name = "abs")
    public IRubyObject abs() {
        return RubyBignum.newBignum(getRuntime(), value.abs());
    }

    /** rb_big_size
     * 
     */
    @JRubyMethod(name = "size")
    public IRubyObject size() {
        return getRuntime().newFixnum((value.bitLength() + 7) / 8);
    }

    public static void marshalTo(RubyBignum bignum, MarshalStream output) throws IOException {
        output.registerLinkTarget(bignum);

        output.write(bignum.value.signum() >= 0 ? '+' : '-');
        
        BigInteger absValue = bignum.value.abs();
        
        byte[] digits = absValue.toByteArray();
        
        boolean oddLengthNonzeroStart = (digits.length % 2 != 0 && digits[0] != 0);
        int shortLength = digits.length / 2;
        if (oddLengthNonzeroStart) {
            shortLength++;
        }
        output.writeInt(shortLength);
        
        for (int i = 1; i <= shortLength * 2 && i <= digits.length; i++) {
            output.write(digits[digits.length - i]);
        }
        
        if (oddLengthNonzeroStart) {
            // Pad with a 0
            output.write(0);
        }
    }

    public static RubyNumeric unmarshalFrom(UnmarshalStream input) throws IOException {
        boolean positive = input.readUnsignedByte() == '+';
        int shortLength = input.unmarshalInt();

        // BigInteger required a sign byte in incoming array
        byte[] digits = new byte[shortLength * 2 + 1];

        for (int i = digits.length - 1; i >= 1; i--) {
        	digits[i] = input.readSignedByte();
        }

        BigInteger value = new BigInteger(digits);
        if (!positive) {
            value = value.negate();
        }

        RubyNumeric result = bignorm(input.getRuntime(), value);
        input.registerLinkTarget(result);
        return result;
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Chad Fowler <chadfowler@chadfowler.com>
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2002-2005 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2005 Charles O Nutter <headius@headius.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.Binding;
import org.jruby.runtime.Frame;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;

/**
 * @author  jpetersen
 */
@JRubyClass(name="Binding")
public class RubyBinding extends RubyObject {
    private Binding binding;

    public RubyBinding(Ruby runtime, RubyClass rubyClass, Binding binding) {
        super(runtime, rubyClass);
        
        this.binding = binding;
    }

    private RubyBinding(Ruby runtime, RubyClass rubyClass) {
        super(runtime, rubyClass);
    }
    
    private static ObjectAllocator BINDING_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            RubyBinding instance = new RubyBinding(runtime, klass);
            
            return instance;
        }
    };
    
    public static RubyClass createBindingClass(Ruby runtime) {
        RubyClass bindingClass = runtime.defineClass("Binding", runtime.getObject(), BINDING_ALLOCATOR);
        runtime.setBinding(bindingClass);
        
        bindingClass.defineAnnotatedMethods(RubyBinding.class);
        
        return bindingClass;
    }

    public Binding getBinding() {
        return binding;
    }

    // Proc class
    
    public static RubyBinding newBinding(Ruby runtime, Binding binding) {
        return new RubyBinding(runtime, runtime.getBinding(), binding);
    }

    public static RubyBinding newBinding(Ruby runtime) {
        ThreadContext context = runtime.getCurrentContext();
        
        // FIXME: We should be cloning, not reusing: frame, scope, dynvars, and potentially iter/block info
        Frame frame = context.getCurrentFrame();
        Binding binding = new Binding(frame, context.getBindingRubyClass(), context.getCurrentScope());
        
        return new RubyBinding(runtime, runtime.getBinding(), binding);
    }

    /**
     * Create a binding appropriate for a bare "eval", by using the previous (caller's) frame and current
     * scope.
     */
    public static RubyBinding newBindingForEval(ThreadContext context) {
        // This requires some explaining.  We use Frame values when executing blocks to fill in 
        // various values in ThreadContext and EvalState.eval like rubyClass, cref, and self.
        // Largely, for an eval that is using the logical binding at a place where the eval is 
        // called we mostly want to use the current frames value for this.  Most importantly, 
        // we need that self (JRUBY-858) at this point.  We also need to make sure that returns
        // jump to the right place (which happens to be the previous frame).  Lastly, we do not
        // want the current frames klazz since that will be the klazz represented of self.  We
        // want the class right before the eval (well we could use cref class for this too I think).
        // Once we end up having Frames created earlier I think the logic of stuff like this will
        // be better since we won't be worried about setting Frame to setup other variables/stacks
        // but just making sure Frame itself is correct...
        
        Frame previousFrame = context.getPreviousFrame();
        Frame currentFrame = context.getCurrentFrame();
        currentFrame.setKlazz(previousFrame.getKlazz());
        
        // Set jump target to whatever the previousTarget thinks is good.
//        currentFrame.setJumpTarget(previousFrame.getJumpTarget() != null ? previousFrame.getJumpTarget() : previousFrame);
        
        Binding binding = new Binding(previousFrame, context.getBindingRubyClass(), context.getCurrentScope());
        Ruby runtime = context.getRuntime();
        
        return new RubyBinding(runtime, runtime.getBinding(), binding);
    }
    
    @JRubyMethod(name = "initialize", visibility = Visibility.PRIVATE)
    public IRubyObject initialize(ThreadContext context) {
        // FIXME: We should be cloning, not reusing: frame, scope, dynvars, and potentially iter/block info
        Frame frame = context.getCurrentFrame();
        binding = new Binding(frame, context.getBindingRubyClass(), context.getCurrentScope());
        
        return this;
    }
    
    @JRubyMethod(name = "initialize_copy", required = 1, visibility = Visibility.PRIVATE)
    @Override
    public IRubyObject initialize_copy(IRubyObject other) {
        RubyBinding otherBinding = (RubyBinding)other;
        
        binding = otherBinding.binding;
        
        return this;
    }
}
/*
 ***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.marshal.MarshalStream;

/**
 *
 * @author  jpetersen
 */
@JRubyClass(name={"TrueClass", "FalseClass"})
public class RubyBoolean extends RubyObject {
    
    public RubyBoolean(Ruby runtime, boolean value) {
        super(runtime, (value ? runtime.getTrueClass() : runtime.getFalseClass()), // Don't initialize with class
                false); // Don't put in object space

        if (!value) flags = FALSE_F;
    }
    
    @Override
    public int getNativeTypeIndex() {
        return (flags & FALSE_F) == 0 ? ClassIndex.TRUE : ClassIndex.FALSE;
    }
    
    @Override
    public boolean isImmediate() {
        return true;
    }

    @Override
    public RubyClass getSingletonClass() {
        return metaClass;
    }

    @Override
    public Class<?> getJavaClass() {
        return boolean.class;
    }

    public static RubyClass createFalseClass(Ruby runtime) {
        RubyClass falseClass = runtime.defineClass("FalseClass", runtime.getObject(), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
        runtime.setFalseClass(falseClass);
        falseClass.index = ClassIndex.FALSE;
        
        falseClass.defineAnnotatedMethods(False.class);
        
        falseClass.getMetaClass().undefineMethod("new");
        
        return falseClass;
    }
    
    public static RubyClass createTrueClass(Ruby runtime) {
        RubyClass trueClass = runtime.defineClass("TrueClass", runtime.getObject(), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
        runtime.setTrueClass(trueClass);
        trueClass.index = ClassIndex.TRUE;
        
        trueClass.defineAnnotatedMethods(True.class);
        
        trueClass.getMetaClass().undefineMethod("new");
        
        return trueClass;
    }
    
    public static RubyBoolean newBoolean(Ruby runtime, boolean value) {
        return value ? runtime.getTrue() : runtime.getFalse();
    }
    
    public static class False {
        @JRubyMethod(name = "&")
        public static IRubyObject false_and(IRubyObject f, IRubyObject oth) {
            return f;
        }

        @JRubyMethod(name = "|")
        public static IRubyObject false_or(IRubyObject f, IRubyObject oth) {
            return oth.isTrue() ? f.getRuntime().getTrue() : f;
        }

        @JRubyMethod(name = "^")
        public static IRubyObject false_xor(IRubyObject f, IRubyObject oth) {
            return oth.isTrue() ? f.getRuntime().getTrue() : f;
        }

        @JRubyMethod(name = "to_s")
        public static IRubyObject false_to_s(IRubyObject f) {
            return f.getRuntime().newString("false");
        }
    }
    
    public static class True {
        @JRubyMethod(name = "&")
        public static IRubyObject true_and(IRubyObject t, IRubyObject oth) {
            return oth.isTrue() ? t : t.getRuntime().getFalse();
        }

        @JRubyMethod(name = "|")
        public static IRubyObject true_or(IRubyObject t, IRubyObject oth) {
            return t;
        }

        @JRubyMethod(name = "^")
        public static IRubyObject true_xor(IRubyObject t, IRubyObject oth) {
            return oth.isTrue() ? t.getRuntime().getFalse() : t;
        }

        @JRubyMethod(name = "to_s")
        public static IRubyObject true_to_s(IRubyObject t) {
            return t.getRuntime().newString("true");
        }
    }
    
    @Override
    public RubyFixnum id() {
        if ((flags & FALSE_F) == 0) {
            return RubyFixnum.newFixnum(getRuntime(), 2);
        } else {
            return RubyFixnum.zero(getRuntime());
        }
    }

    @Override
    public IRubyObject taint(ThreadContext context) {
        return this;
    }

    @Override
    public IRubyObject freeze(ThreadContext context) {
        return this;
    }
    
    public void marshalTo(MarshalStream output) throws java.io.IOException {
        output.write(isTrue() ? 'T' : 'F');
    }
}

/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004-2005 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;

import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.internal.runtime.methods.JavaMethod;
import org.jruby.javasupport.util.RuntimeHelpers;
import org.jruby.runtime.Block;
import org.jruby.runtime.CallSite;
import org.jruby.runtime.CallSite.InlineCachingCallSite;
import org.jruby.runtime.CallType;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ObjectMarshal;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.marshal.MarshalStream;
import org.jruby.runtime.marshal.UnmarshalStream;
import org.jruby.util.collections.WeakHashSet;

/**
 *
 * @author  jpetersen
 */
@JRubyClass(name="Class", parent="Module")
public class RubyClass extends RubyModule {
    public static final int CS_IDX_INITIALIZE = 0;
    public static final String[] CS_NAMES = {
        "initialize"
    };
    private final CallSite[] baseCallSites = new CallSite[CS_NAMES.length];
    {
        for(int i = 0; i < CS_NAMES.length; i++) {
            baseCallSites[i] = new InlineCachingCallSite(CS_NAMES[i], CallType.FUNCTIONAL);
        }
    }
    
    private CallSite[] extraCallSites;
    
    public static void createClassClass(Ruby runtime, RubyClass classClass) {
        classClass.index = ClassIndex.CLASS;
        classClass.kindOf = new RubyModule.KindOf() {
            @Override
            public boolean isKindOf(IRubyObject obj, RubyModule type) {
                return obj instanceof RubyClass;
            }
        };
        
        classClass.undefineMethod("module_function");
        classClass.undefineMethod("append_features");
        classClass.undefineMethod("extend_object");
        
        classClass.defineAnnotatedMethods(RubyClass.class);
        
        classClass.addMethod("new", new SpecificArityNew(classClass, Visibility.PUBLIC));
        
        // This is a non-standard method; have we decided to start extending Ruby?
        //classClass.defineFastMethod("subclasses", callbackFactory.getFastOptMethod("subclasses"));
        
        // FIXME: for some reason this dispatcher causes a VerifyError...
        //classClass.dispatcher = callbackFactory.createDispatcher(classClass);
    }
    
    public static final ObjectAllocator CLASS_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            RubyClass clazz = new RubyClass(runtime);
            clazz.allocator = ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR; // Class.allocate object is not allocatable before it is initialized
            return clazz;
        }
    };

    public ObjectAllocator getAllocator() {
        return allocator;
    }

    public void setAllocator(ObjectAllocator allocator) {
        this.allocator = allocator;
    }

    @JRubyMethod(name = "allocate")
    public IRubyObject allocate() {
        if (superClass == null) throw runtime.newTypeError("can't instantiate uninitialized class");
        IRubyObject obj = allocator.allocate(runtime, this);
        if (obj.getMetaClass().getRealClass() != getRealClass()) throw runtime.newTypeError("wrong instance allocation");
        return obj;
    }

    public CallSite[] getBaseCallSites() {
        return baseCallSites;
    }
    
    public CallSite[] getExtraCallSites() {
        return extraCallSites;
    }

    @Override
    public int getNativeTypeIndex() {
        return ClassIndex.CLASS;
    }
    
    @Override
    public boolean isModule() {
        return false;
    }

    @Override
    public boolean isClass() {
        return true;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }

    /** boot_defclass
     * Create an initial Object meta class before Module and Kernel dependencies have
     * squirreled themselves together.
     * 
     * @param runtime we need it
     * @return a half-baked meta class for object
     */
    public static RubyClass createBootstrapClass(Ruby runtime, String name, RubyClass superClass, ObjectAllocator allocator) {
        RubyClass obj;

        if (superClass == null ) {  // boot the Object class 
            obj = new RubyClass(runtime);
            obj.marshal = DEFAULT_OBJECT_MARSHAL;
        } else {                    // boot the Module and Class classes
            obj = new RubyClass(runtime, superClass);
        }
        obj.setAllocator(allocator);
        obj.setBaseName(name);
        return obj;
    }

    private final Ruby runtime;
    private ObjectAllocator allocator; // the default allocator
    protected ObjectMarshal marshal;
    private Set<RubyClass> subclasses;

    /** separate path for MetaClass and IncludedModuleWrapper construction
     *  (rb_class_boot version for MetaClasses)
     *  no marshal, allocator initialization and addSubclass(this) here!
     */
    protected RubyClass(Ruby runtime, RubyClass superClass, boolean objectSpace) {
        super(runtime, runtime.getClassClass(), objectSpace);
        this.runtime = runtime;
        this.superClass = superClass; // this is the only case it might be null here (in MetaClass construction)
    }
    
    /** used by CLASS_ALLOCATOR (any Class' class will be a Class!)
     *  also used to bootstrap Object class
     */
    protected RubyClass(Ruby runtime) {
        super(runtime, runtime.getClassClass());
        this.runtime = runtime;
        index = ClassIndex.CLASS;
    }
    
    /** rb_class_boot (for plain Classes)
     *  also used to bootstrap Module and Class classes 
     */
    protected RubyClass(Ruby runtime, RubyClass superClazz) {
        this(runtime);
        superClass = superClazz;
        marshal = superClazz.marshal; // use parent's marshal
        superClazz.addSubclass(this);
        
        infectBy(superClass);        
    }
    
    /** 
     * A constructor which allows passing in an array of supplementary call sites.
     */
    protected RubyClass(Ruby runtime, RubyClass superClazz, CallSite[] extraCallSites) {
        this(runtime);
        this.superClass = superClazz;
        this.marshal = superClazz.marshal; // use parent's marshal
        superClazz.addSubclass(this);
        
        this.extraCallSites = extraCallSites;
        
        infectBy(superClass);        
    }

    /** 
     * Construct a new class with the given name scoped under Object (global)
     * and with Object as its immediate superclass.
     * Corresponds to rb_class_new in MRI.
     */
    public static RubyClass newClass(Ruby runtime, RubyClass superClass) {
        if (superClass == runtime.getClassClass()) throw runtime.newTypeError("can't make subclass of Class");
        if (superClass.isSingleton()) throw runtime.newTypeError("can't make subclass of virtual class");
        return new RubyClass(runtime, superClass);        
    }

    /** 
     * A variation on newClass that allow passing in an array of supplementary
     * call sites to improve dynamic invocation.
     */
    public static RubyClass newClass(Ruby runtime, RubyClass superClass, CallSite[] extraCallSites) {
        if (superClass == runtime.getClassClass()) throw runtime.newTypeError("can't make subclass of Class");
        if (superClass.isSingleton()) throw runtime.newTypeError("can't make subclass of virtual class");
        return new RubyClass(runtime, superClass, extraCallSites);        
    }

    /** 
     * Construct a new class with the given name, allocator, parent class,
     * and containing class. If setParent is true, the class's parent will be
     * explicitly set to the provided parent (rather than the new class just
     * being assigned to a constant in that parent).
     * Corresponds to rb_class_new/rb_define_class_id/rb_name_class/rb_set_class_path
     * in MRI.
     */
    public static RubyClass newClass(Ruby runtime, RubyClass superClass, String name, ObjectAllocator allocator, RubyModule parent, boolean setParent) {
        RubyClass clazz = newClass(runtime, superClass);
        clazz.setBaseName(name);
        clazz.setAllocator(allocator);
        clazz.makeMetaClass(superClass.getMetaClass());
        if (setParent) clazz.setParent(parent);
        parent.setConstant(name, clazz);
        clazz.inherit(superClass);
        return clazz;
    }

    /** 
     * A variation on newClass that allows passing in an array of supplementary
     * call sites to improve dynamic invocation performance.
     */
    public static RubyClass newClass(Ruby runtime, RubyClass superClass, String name, ObjectAllocator allocator, RubyModule parent, boolean setParent, CallSite[] extraCallSites) {
        RubyClass clazz = newClass(runtime, superClass, extraCallSites);
        clazz.setBaseName(name);
        clazz.setAllocator(allocator);
        clazz.makeMetaClass(superClass.getMetaClass());
        if (setParent) clazz.setParent(parent);
        parent.setConstant(name, clazz);
        clazz.inherit(superClass);
        return clazz;
    }

    /** rb_make_metaclass
     *
     */
    @Override
    public RubyClass makeMetaClass(RubyClass superClass) {
        if (isSingleton()) { // could be pulled down to RubyClass in future
            MetaClass klass = new MetaClass(getRuntime(), superClass); // rb_class_boot
            setMetaClass(klass);

            klass.setAttached(this);
            klass.setMetaClass(klass);
            klass.setSuperClass(getSuperClass().getRealClass().getMetaClass());
            
            return klass;
        } else {
            return super.makeMetaClass(superClass);
        }
    }
    
    @Deprecated
    public IRubyObject invoke(ThreadContext context, IRubyObject self, int methodIndex, String name, IRubyObject[] args, CallType callType, Block block) {
        return invoke(context, self, name, args, callType, block);
    }
    
    public boolean notVisibleAndNotMethodMissing(DynamicMethod method, String name, IRubyObject caller, CallType callType) {
        return !method.isCallableFrom(caller, callType) && !name.equals("method_missing");
    }
    
    public IRubyObject invoke(ThreadContext context, IRubyObject self, String name,
            CallType callType, Block block) {
        DynamicMethod method = searchMethod(name);
        IRubyObject caller = context.getFrameSelf();
        if (shouldCallMethodMissing(method, name, caller, callType)) {
            return RuntimeHelpers.callMethodMissing(context, self, method, name, caller, callType, block);
        }
        return method.call(context, self, this, name, block);
    }
    
    public IRubyObject finvoke(ThreadContext context, IRubyObject self, String name, Block block) {
        DynamicMethod method = searchMethod(name);
        if (shouldCallMethodMissing(method)) {
            return RuntimeHelpers.callMethodMissing(context, self, method, name, context.getFrameSelf(), CallType.FUNCTIONAL, block);
        }
        return method.call(context, self, this, name, block);
    }
    
    public IRubyObject invoke(ThreadContext context, IRubyObject self, String name,
            IRubyObject[] args, CallType callType, Block block) {
        assert args != null;
        DynamicMethod method = searchMethod(name);
        IRubyObject caller = context.getFrameSelf();
        if (shouldCallMethodMissing(method, name, caller, callType)) {
            return RuntimeHelpers.callMethodMissing(context, self, method, name, args, caller, callType, block);
        }
        return method.call(context, self, this, name, args, block);
    }
    
    public IRubyObject finvoke(ThreadContext context, IRubyObject self, String name,
            IRubyObject[] args, Block block) {
        assert args != null;
        DynamicMethod method = searchMethod(name);
        if (shouldCallMethodMissing(method)) {
            return RuntimeHelpers.callMethodMissing(context, self, method, name, args, context.getFrameSelf(), CallType.FUNCTIONAL, block);
        }
        return method.call(context, self, this, name, args, block);
    }
    
    public IRubyObject invoke(ThreadContext context, IRubyObject self, String name,
            IRubyObject arg, CallType callType, Block block) {
        DynamicMethod method = searchMethod(name);
        IRubyObject caller = context.getFrameSelf();
        if (shouldCallMethodMissing(method, name, caller, callType)) {
            return RuntimeHelpers.callMethodMissing(context, self, method, name, arg, caller, callType, block);
        }
        return method.call(context, self, this, name, arg, block);
    }
    
    public IRubyObject finvoke(ThreadContext context, IRubyObject self, String name,
            IRubyObject arg, Block block) {
        DynamicMethod method = searchMethod(name);
        if (shouldCallMethodMissing(method)) {
            return RuntimeHelpers.callMethodMissing(context, self, method, name, arg, context.getFrameSelf(), CallType.FUNCTIONAL, block);
        }
        return method.call(context, self, this, name, arg, block);
    }
    
    public IRubyObject invoke(ThreadContext context, IRubyObject self, String name,
            IRubyObject arg0, IRubyObject arg1, CallType callType, Block block) {
        DynamicMethod method = searchMethod(name);
        IRubyObject caller = context.getFrameSelf();
        if (shouldCallMethodMissing(method, name, caller, callType)) {
            return RuntimeHelpers.callMethodMissing(context, self, method, name, arg0, arg1, caller, callType, block);
        }
        return method.call(context, self, this, name, arg0, arg1, block);
    }
    
    public IRubyObject finvoke(ThreadContext context, IRubyObject self, String name,
            IRubyObject arg0, IRubyObject arg1, Block block) {
        DynamicMethod method = searchMethod(name);
        if (shouldCallMethodMissing(method)) {
            return RuntimeHelpers.callMethodMissing(context, self, method, name, arg0, arg1, context.getFrameSelf(), CallType.FUNCTIONAL, block);
        }
        return method.call(context, self, this, name, arg0, arg1, block);
    }
    
    public IRubyObject invoke(ThreadContext context, IRubyObject self, String name,
            IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, CallType callType, Block block) {
        DynamicMethod method = searchMethod(name);
        IRubyObject caller = context.getFrameSelf();
        if (shouldCallMethodMissing(method, name, caller, callType)) {
            return RuntimeHelpers.callMethodMissing(context, self, method, name, arg0, arg1, arg2, caller, callType, block);
        }
        return method.call(context, self, this, name, arg0, arg1, arg2, block);
    }
    
    public IRubyObject finvoke(ThreadContext context, IRubyObject self, String name,
            IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Block block) {
        DynamicMethod method = searchMethod(name);
        if (shouldCallMethodMissing(method)) {
            return RuntimeHelpers.callMethodMissing(context, self, method, name, arg0, arg1, arg2, context.getFrameSelf(), CallType.FUNCTIONAL, block);
        }
        return method.call(context, self, this, name, arg0, arg1, arg2, block);
    }
    
    public IRubyObject invoke(ThreadContext context, IRubyObject self, String name,
            CallType callType) {
        DynamicMethod method = searchMethod(name);
        IRubyObject caller = context.getFrameSelf();
        if (shouldCallMethodMissing(method, name, caller, callType)) {
            return RuntimeHelpers.callMethodMissing(context, self, method, name, caller, callType, Block.NULL_BLOCK);
        }
        return method.call(context, self, this, name);
    }
    
    public IRubyObject finvoke(ThreadContext context, IRubyObject self, String name) {
        DynamicMethod method = searchMethod(name);
        if (shouldCallMethodMissing(method)) {
            return RuntimeHelpers.callMethodMissing(context, self, method, name, context.getFrameSelf(), CallType.FUNCTIONAL, Block.NULL_BLOCK);
        }
        return method.call(context, self, this, name);
    }
    
    public IRubyObject invoke(ThreadContext context, IRubyObject self, String name,
            IRubyObject[] args, CallType callType) {
        assert args != null;
        DynamicMethod method = searchMethod(name);
        IRubyObject caller = context.getFrameSelf();
        if (shouldCallMethodMissing(method, name, caller, callType)) {
            return RuntimeHelpers.callMethodMissing(context, self, method, name, args, caller, callType, Block.NULL_BLOCK);
        }
        return method.call(context, self, this, name, args);
    }
    
    public IRubyObject finvoke(ThreadContext context, IRubyObject self, String name,
            IRubyObject[] args) {
        assert args != null;
        DynamicMethod method = searchMethod(name);
        if (shouldCallMethodMissing(method)) {
            return RuntimeHelpers.callMethodMissing(context, self, method, name, args, context.getFrameSelf(), CallType.FUNCTIONAL, Block.NULL_BLOCK);
        }
        return method.call(context, self, this, name, args);
    }
    
    public IRubyObject invoke(ThreadContext context, IRubyObject self, String name,
            IRubyObject arg, CallType callType) {
        DynamicMethod method = searchMethod(name);
        IRubyObject caller = context.getFrameSelf();
        if (shouldCallMethodMissing(method, name, caller, callType)) {
            return RuntimeHelpers.callMethodMissing(context, self, method, name, arg, caller, callType, Block.NULL_BLOCK);
        }
        return method.call(context, self, this, name, arg);
    }
    
    public IRubyObject finvoke(ThreadContext context, IRubyObject self, String name,
            IRubyObject arg) {
        DynamicMethod method = searchMethod(name);
        if (shouldCallMethodMissing(method)) {
            return RuntimeHelpers.callMethodMissing(context, self, method, name, arg, context.getFrameSelf(), CallType.FUNCTIONAL, Block.NULL_BLOCK);
        }
        return method.call(context, self, this, name, arg);
    }
    
    public IRubyObject invoke(ThreadContext context, IRubyObject self, String name,
            IRubyObject arg0, IRubyObject arg1, CallType callType) {
        DynamicMethod method = searchMethod(name);
        IRubyObject caller = context.getFrameSelf();
        if (shouldCallMethodMissing(method, name, caller, callType)) {
            return RuntimeHelpers.callMethodMissing(context, self, method, name, arg0, arg1, caller, callType, Block.NULL_BLOCK);
        }
        return method.call(context, self, this, name, arg0, arg1);
    }
    
    public IRubyObject finvoke(ThreadContext context, IRubyObject self, String name,
            IRubyObject arg0, IRubyObject arg1) {
        DynamicMethod method = searchMethod(name);
        if (shouldCallMethodMissing(method)) {
            return RuntimeHelpers.callMethodMissing(context, self, method, name, arg0, arg1, context.getFrameSelf(), CallType.FUNCTIONAL, Block.NULL_BLOCK);
        }
        return method.call(context, self, this, name, arg0, arg1);
    }
    
    public IRubyObject invoke(ThreadContext context, IRubyObject self, String name,
            IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, CallType callType) {
        DynamicMethod method = searchMethod(name);
        IRubyObject caller = context.getFrameSelf();
        if (shouldCallMethodMissing(method, name, caller, callType)) {
            return RuntimeHelpers.callMethodMissing(context, self, method, name, arg0, arg1, arg2, caller, callType, Block.NULL_BLOCK);
        }
        return method.call(context, self, this, name, arg0, arg1, arg2);
    }
    
    public IRubyObject finvoke(ThreadContext context, IRubyObject self, String name,
            IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) {
        DynamicMethod method = searchMethod(name);
        if (shouldCallMethodMissing(method)) {
            return RuntimeHelpers.callMethodMissing(context, self, method, name, arg0, arg1, arg2, context.getFrameSelf(), CallType.FUNCTIONAL, Block.NULL_BLOCK);
        }
        return method.call(context, self, this, name, arg0, arg1, arg2);
    }
    
    private boolean shouldCallMethodMissing(DynamicMethod method) {
        return method.isUndefined();
    }
    private boolean shouldCallMethodMissing(DynamicMethod method, String name, IRubyObject caller, CallType callType) {
        return method.isUndefined() || notVisibleAndNotMethodMissing(method, name, caller, callType);
    }
    
    public IRubyObject invokeInherited(ThreadContext context, IRubyObject self, IRubyObject subclass) {
        DynamicMethod method = getMetaClass().searchMethod("inherited");

        if (method.isUndefined()) {
            return RuntimeHelpers.callMethodMissing(context, self, method, "inherited", subclass, context.getFrameSelf(), CallType.FUNCTIONAL, Block.NULL_BLOCK);
        }

        return method.call(context, self, getMetaClass(), "inherited", subclass, Block.NULL_BLOCK);
    }

    /** rb_class_new_instance
    *
    */
    public IRubyObject newInstance(ThreadContext context, IRubyObject[] args, Block block) {
        IRubyObject obj = allocate();
        baseCallSites[CS_IDX_INITIALIZE].call(context, obj, args, block);
        return obj;
    }
    
    // TODO: replace this with a smarter generated invoker that can handle 0-N args
    public static class SpecificArityNew extends JavaMethod {
        public SpecificArityNew(RubyModule implClass, Visibility visibility) {
            super(implClass, visibility);
        }
        public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args, Block block) {
            RubyClass cls = (RubyClass)self;
            IRubyObject obj = cls.allocate();
            cls.baseCallSites[CS_IDX_INITIALIZE].call(context, obj, args, block);
            return obj;
        }
        public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, Block block) {
            RubyClass cls = (RubyClass)self;
            IRubyObject obj = cls.allocate();
            cls.baseCallSites[CS_IDX_INITIALIZE].call(context, obj, block);
            return obj;
        }
        public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, Block block) {
            RubyClass cls = (RubyClass)self;
            IRubyObject obj = cls.allocate();
            cls.baseCallSites[CS_IDX_INITIALIZE].call(context, obj, arg0, block);
            return obj;
        }
        public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, IRubyObject arg1, Block block) {
            RubyClass cls = (RubyClass)self;
            IRubyObject obj = cls.allocate();
            cls.baseCallSites[CS_IDX_INITIALIZE].call(context, obj, arg0, arg1, block);
            return obj;
        }
        public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Block block) {
            RubyClass cls = (RubyClass)self;
            IRubyObject obj = cls.allocate();
            cls.baseCallSites[CS_IDX_INITIALIZE].call(context, obj, arg0, arg1, arg2, block);
            return obj;
        }
    }

    /** rb_class_initialize
     * 
     */
    @JRubyMethod(name = "initialize", optional = 1, frame = true, visibility = Visibility.PRIVATE)
    public IRubyObject initialize(IRubyObject[] args, Block block) {
        if (superClass != null) {
            throw getRuntime().newTypeError("already initialized class");
        }
 
        IRubyObject superObject;
        if (args.length == 0) {
            superObject = getRuntime().getObject();
        } else {
            superObject = args[0];
            checkInheritable(superObject);
        }
 
        RubyClass superClazz = (RubyClass) superObject;

        superClass = superClazz;
        allocator = superClazz.allocator;
        makeMetaClass(superClazz.getMetaClass());
        
        marshal = superClazz.marshal;
       
        superClazz.addSubclass(this);
       
        super.initialize(block);
       
        inherit(superClazz);

        return this;        
    }    

    /** rb_class_init_copy
     * 
     */
    @JRubyMethod(name = "initialize_copy", required = 1)
    @Override
    public IRubyObject initialize_copy(IRubyObject original){
        if (superClass != null) throw runtime.newTypeError("already initialized class");
        if (original instanceof MetaClass) throw getRuntime().newTypeError("can't copy singleton class");        
        
        super.initialize_copy(original);
        allocator = ((RubyClass)original).allocator; 
        return this;        
    }
    
    // TODO: Someday, enable.
    // @JRubyMethod(name = "subclasses", optional = 1)
    public IRubyObject subclasses(ThreadContext context, IRubyObject[] args) {
        boolean recursive = false;
        if (args.length == 1) {
            if (args[0] instanceof RubyBoolean) {
                recursive = args[0].isTrue();
            } else {
                context.getRuntime().newTypeError(args[0], context.getRuntime().fastGetClass("Boolean"));
            }
        }
        
        return RubyArray.newArray(context.getRuntime(), subclasses(recursive)).freeze(context);
    }
    
    public Collection subclasses(boolean includeDescendants) {
        if (subclasses != null) {
            Collection<RubyClass> mine = new ArrayList<RubyClass>(subclasses);
            if (includeDescendants) {
                for (RubyClass i: subclasses) {
                    mine.addAll(i.subclasses(includeDescendants));
                }
            }

            return mine;
        } else {
            return Collections.EMPTY_LIST;
        }
    }
    
    public synchronized void addSubclass(RubyClass subclass) {
        if (subclasses == null) subclasses = new WeakHashSet<RubyClass>();
        subclasses.add(subclass);
    }
    
    public Ruby getClassRuntime() {
            return runtime;
    }

    public RubyClass getRealClass() {
        return this;
    }    

    @JRubyMethod(name = "inherited", required = 1)
    public IRubyObject inherited(ThreadContext context, IRubyObject arg) {
        return context.getRuntime().getNil();
    }

    /** rb_class_inherited (reversed semantics!)
     * 
     */
    public void inherit(RubyClass superClazz) {
        if (superClazz == null) superClazz = getRuntime().getObject();
        
        superClazz.invokeInherited(getRuntime().getCurrentContext(), superClazz, this);
    }

    /** Return the real super class of this class.
     * 
     * rb_class_superclass
     *
     */
    @JRubyMethod(name = "superclass")
    public IRubyObject superclass(ThreadContext context) {
        RubyClass superClazz = superClass;

        if (superClazz == null) throw context.getRuntime().newTypeError("uninitialized class");
        
        if (isSingleton()) superClazz = metaClass;
        while (superClazz != null && superClazz.isIncluded()) superClazz = superClazz.superClass;

        return superClazz != null ? superClazz : context.getRuntime().getNil();
    }

    /** rb_check_inheritable
     * 
     */
    public static void checkInheritable(IRubyObject superClass) {
        if (!(superClass instanceof RubyClass)) {
            throw superClass.getRuntime().newTypeError("superclass must be a Class (" + superClass.getMetaClass() + " given)"); 
        }
        if (((RubyClass)superClass).isSingleton()) {
            throw superClass.getRuntime().newTypeError("can't make subclass of virtual class");
        }        
    }

    public final ObjectMarshal getMarshal() {
        return marshal;
    }
    
    public final void setMarshal(ObjectMarshal marshal) {
        this.marshal = marshal;
    }
    
    public final void marshal(Object obj, MarshalStream marshalStream) throws IOException {
        getMarshal().marshalTo(getRuntime(), obj, this, marshalStream);
    }
    
    public final Object unmarshal(UnmarshalStream unmarshalStream) throws IOException {
        return getMarshal().unmarshalFrom(getRuntime(), this, unmarshalStream);
    }
    
    public static void marshalTo(RubyClass clazz, MarshalStream output) throws java.io.IOException {
        output.registerLinkTarget(clazz);
        output.writeString(MarshalStream.getPathFromClass(clazz));
    }

    public static RubyClass unmarshalFrom(UnmarshalStream input) throws java.io.IOException {
        String name = RubyString.byteListToString(input.unmarshalString());
        RubyClass result = UnmarshalStream.getClassFromPath(input.getRuntime(), name);
        input.registerLinkTarget(result);
        return result;
    }

    protected static final ObjectMarshal DEFAULT_OBJECT_MARSHAL = new ObjectMarshal() {
        public void marshalTo(Ruby runtime, Object obj, RubyClass type,
                              MarshalStream marshalStream) throws IOException {
            IRubyObject object = (IRubyObject)obj;
            
            marshalStream.registerLinkTarget(object);
            marshalStream.dumpVariables(object.getVariableList());
        }

        public Object unmarshalFrom(Ruby runtime, RubyClass type,
                                    UnmarshalStream unmarshalStream) throws IOException {
            IRubyObject result = type.allocate();
            
            unmarshalStream.registerLinkTarget(result);

            unmarshalStream.defaultVariablesUnmarshal(result);

            return result;
        }
    };    
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2007 Ola Bini <ola@ologix.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import org.jruby.anno.JRubyMethod;

import org.jruby.runtime.Block;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;

/**
 * @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
 */
public class RubyClassPathVariable extends RubyObject {
    public static void createClassPathVariable(Ruby runtime) {
        RubyClassPathVariable self = new RubyClassPathVariable(runtime);
        runtime.getEnumerable().extend_object(self);
        runtime.defineReadonlyVariable("$CLASSPATH", self);
        
        self.getMetaClass().defineAnnotatedMethods(RubyClassPathVariable.class);
    }

    private RubyClassPathVariable(Ruby runtime) {
        super(runtime, runtime.getObject());
    }

    @JRubyMethod(name = {"append", "<<"}, required = 1)
    public IRubyObject append(IRubyObject obj) throws Exception {
        String ss = obj.convertToString().toString();
        URL url = getURL(ss);
        getRuntime().getJRubyClassLoader().addURL(url);
        return this;
    }
    
    private URL getURL(String target) throws MalformedURLException {
        if(target.indexOf("://") == -1) {
            return new File(target).toURI().toURL();
        } else {
            return new URL(target);
        }
    }

    @JRubyMethod(name = {"size", "length"})
    public IRubyObject size() {
        return getRuntime().newFixnum(getRuntime().getJRubyClassLoader().getURLs().length);
    }

    @JRubyMethod(name = "each", frame = true)
    public IRubyObject each(Block block) {
        URL[] urls = getRuntime().getJRubyClassLoader().getURLs();
        ThreadContext ctx = getRuntime().getCurrentContext();
        for(int i=0,j=urls.length;i<j;i++) {
            block.yield(ctx, getRuntime().newString(urls[i].toString()));
        }
        return getRuntime().getNil();
    }

    @JRubyMethod(name = "to_s")
    public IRubyObject to_s() {
        return callMethod(getRuntime().getCurrentContext(), "to_a").callMethod(getRuntime().getCurrentContext(), "to_s");
    }    

    @JRubyMethod(name = "inspect")
    public IRubyObject inspect() {
        return callMethod(getRuntime().getCurrentContext(), "to_a").callMethod(getRuntime().getCurrentContext(), "inspect");
    }    
}// RubyClassPathVariable
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2005 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2006 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * Copyright (C) 2006 Thomas E Enebo <enebo@acm.org>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;
import org.jruby.exceptions.RaiseException;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;

/** Implementation of the Comparable module.
 *
 */
@JRubyModule(name="Comparable")
public class RubyComparable {
    public static RubyModule createComparable(Ruby runtime) {
        RubyModule comparableModule = runtime.defineModule("Comparable");
        runtime.setComparable(comparableModule);
        
        comparableModule.defineAnnotatedMethods(RubyComparable.class);

        return comparableModule;
    }

    /*  ================
     *  Utility Methods
     *  ================ 
     */

    /** rb_cmpint
     * 
     */
    public static int cmpint(ThreadContext context, IRubyObject val, IRubyObject a, IRubyObject b) {
        if (val.isNil()) cmperr(a, b);
        if (val instanceof RubyFixnum) return RubyNumeric.fix2int((RubyFixnum) val);
        if (val instanceof RubyBignum) return ((RubyBignum) val).getValue().signum() == -1 ? 1 : -1;

        RubyFixnum zero = RubyFixnum.zero(context.getRuntime());
        
        if (val.callMethod(context, MethodIndex.OP_GT, ">", zero).isTrue()) return 1;
        if (val.callMethod(context, MethodIndex.OP_LT, "<", zero).isTrue()) return -1;

        return 0;
    }

    /** rb_cmperr
     * 
     */
    public static IRubyObject cmperr(IRubyObject recv, IRubyObject other) {
        IRubyObject target;
        if (other.isImmediate() || !(other.isNil() || other.isTrue() || other == recv.getRuntime().getFalse())) {
            target = other.inspect();
        } else {
            target = other.getType();
        }

        throw recv.getRuntime().newArgumentError("comparison of " + recv.getType() + " with " + target + " failed");
    }

    /*  ================
     *  Module Methods
     *  ================ 
     */

    /** cmp_equal (cmp_eq inlined here)
     * 
     */
    @JRubyMethod(name = "==", required = 1)
    public static IRubyObject op_equal(ThreadContext context, IRubyObject recv, IRubyObject other) {
        Ruby runtime = context.getRuntime();

        if (recv == other) return runtime.getTrue();

        try {
            IRubyObject result = recv.callMethod(context, MethodIndex.OP_SPACESHIP, "<=>", other);
            
            return RubyBoolean.newBoolean(runtime, cmpint(context, result, recv, other) == 0);
        } catch (RaiseException e) {
            if (e.getException().kind_of_p(context, runtime.getStandardError()).isTrue()) {
                return runtime.getNil();
            } else {
                throw e;
            }
        }
    }

    /** cmp_gt
     * 
     */
    // <=> may return nil in many circumstances, e.g. 3 <=> NaN        
    @JRubyMethod(name = ">", required = 1)
    public static RubyBoolean op_gt(ThreadContext context, IRubyObject recv, IRubyObject other) {
        IRubyObject result = recv.callMethod(context, MethodIndex.OP_SPACESHIP, "<=>", other);
        
        if (result.isNil()) cmperr(recv, other);

        return RubyBoolean.newBoolean(context.getRuntime(), cmpint(context, result, recv, other) > 0);
    }

    /** cmp_ge
     * 
     */
    @JRubyMethod(name = ">=", required = 1)
    public static RubyBoolean op_ge(ThreadContext context, IRubyObject recv, IRubyObject other) {
        IRubyObject result = recv.callMethod(context, MethodIndex.OP_SPACESHIP, "<=>", other);
        
        if (result.isNil()) cmperr(recv, other);

        return RubyBoolean.newBoolean(context.getRuntime(), cmpint(context, result, recv, other) >= 0);
    }

    /** cmp_lt
     * 
     */
    @JRubyMethod(name = "<", required = 1)
    public static RubyBoolean op_lt(ThreadContext context, IRubyObject recv, IRubyObject other) {
        IRubyObject result = recv.callMethod(context, MethodIndex.OP_SPACESHIP, "<=>", other);

        if (result.isNil()) cmperr(recv, other);

        return RubyBoolean.newBoolean(context.getRuntime(), cmpint(context, result, recv, other) < 0);
    }

    /** cmp_le
     * 
     */
    @JRubyMethod(name = "<=", required = 1)
    public static RubyBoolean op_le(ThreadContext context, IRubyObject recv, IRubyObject other) {
        IRubyObject result = recv.callMethod(context, MethodIndex.OP_SPACESHIP, "<=>", other);

        if (result.isNil()) cmperr(recv, other);

        return RubyBoolean.newBoolean(context.getRuntime(), cmpint(context, result, recv, other) <= 0);
    }

    /** cmp_between
     * 
     */
    @JRubyMethod(name = "between?", required = 2)
    public static RubyBoolean between_p(ThreadContext context, IRubyObject recv, IRubyObject first, IRubyObject second) {
        return context.getRuntime().newBoolean(op_lt(context, recv, first).isFalse() && op_gt(context, recv, second).isFalse());
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import static org.jruby.util.Numeric.f_abs;
import static org.jruby.util.Numeric.f_abs2;
import static org.jruby.util.Numeric.f_add;
import static org.jruby.util.Numeric.f_arg;
import static org.jruby.util.Numeric.f_conjugate;
import static org.jruby.util.Numeric.f_denominator;
import static org.jruby.util.Numeric.f_div;
import static org.jruby.util.Numeric.f_divmod;
import static org.jruby.util.Numeric.f_equal_p;
import static org.jruby.util.Numeric.f_exact_p;
import static org.jruby.util.Numeric.f_expt;
import static org.jruby.util.Numeric.f_gt_p;
import static org.jruby.util.Numeric.f_inspect;
import static org.jruby.util.Numeric.f_lcm;
import static org.jruby.util.Numeric.f_mul;
import static org.jruby.util.Numeric.f_negate;
import static org.jruby.util.Numeric.f_negative_p;
import static org.jruby.util.Numeric.f_numerator;
import static org.jruby.util.Numeric.f_one_p;
import static org.jruby.util.Numeric.f_polar;
import static org.jruby.util.Numeric.f_quo;
import static org.jruby.util.Numeric.f_scalar_p;
import static org.jruby.util.Numeric.f_sub;
import static org.jruby.util.Numeric.f_to_f;
import static org.jruby.util.Numeric.f_to_i;
import static org.jruby.util.Numeric.f_to_r;
import static org.jruby.util.Numeric.f_to_s;
import static org.jruby.util.Numeric.f_xor;
import static org.jruby.util.Numeric.f_zero_p;

import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.Arity;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.Frame;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;
import org.jruby.util.Numeric;

/**
 *  1.9 complex.c as of revision: 18876
 */

@JRubyClass(name = "Complex", parent = "Numeric")
public class RubyComplex extends RubyNumeric {

    public static RubyClass createComplexClass(Ruby runtime) {
        RubyClass complexc = runtime.defineClass("Complex", runtime.getNumeric(), COMPLEX_ALLOCATOR); // because one can Complex.send(:allocate)
        runtime.setComplex(complexc);

        complexc.index = ClassIndex.COMPLEX;
        complexc.kindOf = new RubyModule.KindOf() {
            @Override
            public boolean isKindOf(IRubyObject obj, RubyModule type) {
                return obj instanceof RubyComplex;
            }
        };

        ThreadContext context = runtime.getCurrentContext();
        complexc.callMethod(context, "private_class_method", runtime.newSymbol("allocate"));

        complexc.defineAnnotatedMethods(RubyComplex.class);

        String[]undefined = {"<", "<=", "<=>", ">", ">=", "between?", "divmod",
                             "floor", "ceil", "modulo", "round", "step", "truncate"};

        for (String undef : undefined) {
            complexc.undefineMethod(undef);
        }

        complexc.defineConstant("I", RubyComplex.newComplexConvert(context, RubyFixnum.zero(runtime), RubyFixnum.one(runtime)));

        return complexc;
    }

    private static ObjectAllocator COMPLEX_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return new RubyComplex(runtime, klass, RubyFixnum.zero(runtime), RubyFixnum.zero(runtime));
        }
    };

    /** internal
     * 
     */
    private RubyComplex(Ruby runtime, IRubyObject clazz, IRubyObject real, IRubyObject image) {
        super(runtime, (RubyClass)clazz);
        this.real = real;
        this.image = image;
    }

    /** rb_complex_raw
     * 
     */
    static RubyComplex newComplexRaw(Ruby runtime, IRubyObject x, RubyObject y) {
        return new RubyComplex(runtime, runtime.getComplex(), x, y);
    }

    /** rb_complex_raw1
     * 
     */
    static RubyComplex newComplexRaw(Ruby runtime, IRubyObject x) {
        return new RubyComplex(runtime, runtime.getComplex(), x, RubyFixnum.zero(runtime));
    }

    /** rb_complex_new1
     * 
     */
    public static IRubyObject newComplexCanonicalize(ThreadContext context, IRubyObject x) {
         return newComplexCanonicalize(context, x, RubyFixnum.zero(context.getRuntime()));
    }
    
    /** rb_complex_new
     * 
     */
    public static IRubyObject newComplexCanonicalize(ThreadContext context, IRubyObject x, IRubyObject y) {
        return canonicalizeInternal(context, context.getRuntime().getComplex(), x, y);
    }

    /** rb_complex_polar
     * 
     */
    static IRubyObject newComplexPolar(ThreadContext context, IRubyObject x, IRubyObject y) {
        return polar(context, context.getRuntime().getComplex(), x, y);
    }

    /** f_complex_new1
     * 
     */
    static IRubyObject newComplex(ThreadContext context, IRubyObject clazz, IRubyObject x) {
        return newComplex(context, clazz, x, RubyFixnum.zero(context.getRuntime()));
    }

    /** f_complex_new2
     * 
     */
    static IRubyObject newComplex(ThreadContext context, IRubyObject clazz, IRubyObject x, IRubyObject y) {
        assert !(x instanceof RubyComplex);
        return canonicalizeInternal(context, clazz, x, y);
    }
    
    /** f_complex_new_bang2
     * 
     */
    static RubyComplex newComplexBang(ThreadContext context, IRubyObject clazz, IRubyObject x, IRubyObject y) {
        assert x instanceof RubyComplex && y instanceof RubyComplex;
        return new RubyComplex(context.getRuntime(), clazz, x, y);
    }

    /** f_complex_new_bang1
     * 
     */
    public static RubyComplex newComplexBang(ThreadContext context, IRubyObject clazz, IRubyObject x) {
        assert x instanceof RubyComplex;
        return newComplexBang(context, clazz, x, RubyFixnum.zero(context.getRuntime()));
    }

    private IRubyObject real;
    private IRubyObject image;
    
    IRubyObject getImage() {
        return image;
    }

    IRubyObject getReal() {
        return real;
    }

    /** m_cos
     * 
     */
    private static IRubyObject m_cos(ThreadContext context, IRubyObject x) {
        if (f_scalar_p(context, x).isTrue()) return RubyMath.cos(x, x);
        RubyComplex complex = (RubyComplex)x;
        return newComplex(context, context.getRuntime().getComplex(),
                          f_mul(context, RubyMath.cos(x, complex.real), RubyMath.cosh(x, complex.image)),
                          f_mul(context, f_negate(context, RubyMath.sin(x, complex.real)), RubyMath.sinh(x, complex.image)));
    }

    /** m_sin
     * 
     */
    private static IRubyObject m_sin(ThreadContext context, IRubyObject x) {
        if (f_scalar_p(context, x).isTrue()) return RubyMath.sin(x, x);
        RubyComplex complex = (RubyComplex)x;
        return newComplex(context, context.getRuntime().getComplex(),
                          f_mul(context, RubyMath.sin(x, complex.real), RubyMath.cosh(x, complex.image)),
                          f_mul(context, RubyMath.cos(x, complex.real), RubyMath.sinh(x, complex.image)));
    }    
    
    /** m_sqrt
     * 
     */
    private static IRubyObject m_sqrt(ThreadContext context, IRubyObject x) {
        if (f_scalar_p(context, x).isTrue()) {
            if (!f_negative_p(context, x)) return RubyMath.sqrt(x, x);
            return newComplex(context, context.getRuntime().getComplex(), 
                              RubyFixnum.zero(context.getRuntime()),
                              RubyMath.sqrt(x, f_negate(context, x)));
        } else {
            RubyComplex complex = (RubyComplex)x;
            if (f_negative_p(context, complex.image)) {
                return f_conjugate(context, m_sqrt(context, f_conjugate(context, x)));
            } else {
                IRubyObject a = f_abs(context, x);
                IRubyObject two = RubyFixnum.two(context.getRuntime());
                return newComplex(context, context.getRuntime().getComplex(),
                                  RubyMath.sqrt(x, f_div(context, f_add(context, a, complex.real), two)),
                                  RubyMath.sqrt(x, f_div(context, f_sub(context, a, complex.real), two)));
            }
        }
    }

    /** nucomp_s_new_bang
     *
     */
    @Deprecated
    public static IRubyObject newInstanceBang(ThreadContext context, IRubyObject recv, IRubyObject[]args) {
        switch (args.length) {
        case 1: return newInstanceBang(context, recv, args[0]);
        case 2: return newInstanceBang(context, recv, args[0], args[1]);
        }
        Arity.raiseArgumentError(context.getRuntime(), args.length, 1, 1);
        return null;
    }
    
    @JRubyMethod(name = "new!", meta = true, visibility = Visibility.PRIVATE)
    public static IRubyObject newInstanceBang(ThreadContext context, IRubyObject recv, IRubyObject real) {
        if (!(real instanceof RubyNumeric)) real = f_to_i(context, real);
        return new RubyComplex(context.getRuntime(), recv, real, RubyFixnum.zero(context.getRuntime()));
    }

    @JRubyMethod(name = "new!", meta = true, visibility = Visibility.PRIVATE)
    public static IRubyObject newInstanceBang(ThreadContext context, IRubyObject recv, IRubyObject real, IRubyObject image) {
        if (!(real instanceof RubyNumeric)) real = f_to_i(context, real);
        if (!(image instanceof RubyNumeric)) image = f_to_i(context, image);
        return new RubyComplex(context.getRuntime(), recv, real, image);
    }

    /** nucomp_real_check (might go to bimorphic)
     * 
     */
    private static void realCheck(ThreadContext context, IRubyObject num) {
        switch (num.getMetaClass().index) {
        case ClassIndex.FIXNUM:
        case ClassIndex.BIGNUM:
        case ClassIndex.FLOAT:
        case ClassIndex.RATIONAL:
            break;
        default:
             if (!(num instanceof RubyNumeric ) || !f_scalar_p(context, num).isTrue()) {
                 throw context.getRuntime().newArgumentError("not a real");
             }
        }
    }

    /** nucomp_s_canonicalize_internal
     * 
     */
    private static final boolean CL_CANNON = true;
    private static IRubyObject canonicalizeInternal(ThreadContext context, IRubyObject clazz, IRubyObject real, IRubyObject image) {
        if (f_zero_p(context, image) &&
            ((RubyModule)clazz).fastHasConstant("Unify") &&
            (!CL_CANNON ||
            (!(real instanceof RubyFloat) &&
            !(image instanceof RubyFloat)))) {
            return real;
        } else if (f_scalar_p(context, real).isTrue() &&
                   f_scalar_p(context, image).isTrue()) {
            return new RubyComplex(context.getRuntime(), clazz, real, image);
        } else if (f_scalar_p(context, real).isTrue()) {
            RubyComplex complex = (RubyComplex)image;
            return new RubyComplex(context.getRuntime(), clazz,
                                   f_sub(context, real, complex.image),
                                   f_add(context, RubyFixnum.zero(context.getRuntime()), complex.real));
        } else if (f_scalar_p(context, image).isTrue()) {
            RubyComplex complex = (RubyComplex)real;
            return new RubyComplex(context.getRuntime(), clazz,
                                   complex.real,
                                   f_add(context, complex.image, image));
        } else {
            RubyComplex complex1 = (RubyComplex)real;
            RubyComplex complex2 = (RubyComplex)image;
            return new RubyComplex(context.getRuntime(), clazz,
                                   f_sub(context, complex1.real, complex2.image),
                                   f_add(context, complex1.image, complex2.real));
        }
    }
    
    /** nucomp_s_new
     * 
     */
    @Deprecated
    public static IRubyObject newInstance(ThreadContext context, IRubyObject recv, IRubyObject[]args) {
        switch (args.length) {
        case 1: return newInstance(context, recv, args[0]);
        case 2: return newInstance(context, recv, args[0], args[1]);
        }
        Arity.raiseArgumentError(context.getRuntime(), args.length, 1, 1);
        return null;
    }

    @JRubyMethod(name = {"new", "rect", "rectangular"}, meta = true)
    public static IRubyObject newInstance(ThreadContext context, IRubyObject recv, IRubyObject real) {
        realCheck(context, real);
        return canonicalizeInternal(context, recv, real, RubyFixnum.zero(context.getRuntime()));
    }

    @JRubyMethod(name = {"new", "rect", "rectangular"}, meta = true)
    public static IRubyObject newInstance(ThreadContext context, IRubyObject recv, IRubyObject real, IRubyObject image) {
        realCheck(context, real);
        realCheck(context, image);
        return canonicalizeInternal(context, recv, real, image);
    }

    /** f_complex_polar
     * 
     */
    private static IRubyObject f_complex_polar(ThreadContext context, IRubyObject clazz, IRubyObject x, IRubyObject y) {
        assert !(x instanceof RubyComplex) && !(y instanceof RubyComplex);
        return canonicalizeInternal(context, clazz,
                                             f_mul(context, x, m_cos(context, y)),
                                             f_mul(context, x, m_sin(context, y)));
    }

    /** nucomp_s_polar
     * 
     */
    @JRubyMethod(name = "polar", meta = true)
    public static IRubyObject polar(ThreadContext context, IRubyObject clazz, IRubyObject abs, IRubyObject arg) {
        return f_complex_polar(context, clazz, abs, arg);
    }

    /** rb_Complex1
     * 
     */
    public static IRubyObject newComplexConvert(ThreadContext context, IRubyObject x) {
        return newComplexConvert(context, x, RubyFixnum.zero(context.getRuntime()));
    }

    /** rb_Complex/rb_Complex2
     * 
     */
    public static IRubyObject newComplexConvert(ThreadContext context, IRubyObject x, IRubyObject y) {
        return convert(context, context.getRuntime().getComplex(), x, y);
    }

    @Deprecated
    public static IRubyObject convert(ThreadContext context, IRubyObject clazz, IRubyObject[]args) {
        switch (args.length) {
        case 0: return convert(context, clazz);
        case 1: return convert(context, clazz, args[0]);        
        case 2: return convert(context, clazz, args[0], args[1]);
        }
        Arity.raiseArgumentError(context.getRuntime(), args.length, 0, 2);
        return null;
    }

    /** nucomp_s_convert
     * 
     */
    @JRubyMethod(name = "convert", meta = true, visibility = Visibility.PRIVATE)
    public static IRubyObject convert(ThreadContext context, IRubyObject recv) {
        IRubyObject nil = context.getRuntime().getNil();
        return convertCommon(context, recv, nil, nil);
    }
    
    /** nucomp_s_convert
     * 
     */
    @JRubyMethod(name = "convert", meta = true, visibility = Visibility.PRIVATE)
    public static IRubyObject convert(ThreadContext context, IRubyObject recv, IRubyObject a1) {
        return convertCommon(context, recv, a1, context.getRuntime().getNil());
    }

    /** nucomp_s_convert
     * 
     */
    @JRubyMethod(name = "convert", meta = true, visibility = Visibility.PRIVATE)
    public static IRubyObject convert(ThreadContext context, IRubyObject recv, IRubyObject a1, IRubyObject a2) {
        return convertCommon(context, recv, a1, a2);
    }
    
    private static IRubyObject convertCommon(ThreadContext context, IRubyObject recv, IRubyObject a1, IRubyObject a2) {
        Frame frame = context.getCurrentFrame();
        IRubyObject backref = frame.getBackRef();
        if (backref != null && backref instanceof RubyMatchData) ((RubyMatchData)backref).use();

        if (a1 instanceof RubyString) a1 = str_to_c_strict(context, a1);
        if (a2 instanceof RubyString) a2 = str_to_c_strict(context, a2);
        
        frame.setBackRef(backref);

        if (a1 instanceof RubyComplex) {
            RubyComplex a1Complex = (RubyComplex)a1;
            if (!(a1Complex.image instanceof RubyFloat) && f_zero_p(context, a1Complex.image)) {
                a1 = a1Complex.real;
            }
        }
        
        if (a2 instanceof RubyComplex) {
            RubyComplex a2Complex = (RubyComplex)a2;
            if (!(a2Complex.image instanceof RubyFloat) && f_zero_p(context, a2Complex.image)) {
                a2 = a2Complex.real;
            }
        }
        
        if (a1 instanceof RubyComplex) {
            if (a2.isNil() || f_zero_p(context, a2)) return a1;
        }
        return a2.isNil() ? newInstance(context, recv, a1) : newInstance(context, recv, a1, a2);
    }
    
    /** nucomp_real
     * 
     */
    @JRubyMethod(name = "real")
    public IRubyObject real() {
        return real;
    }
    
    /** nucomp_image
     * 
     */
    @JRubyMethod(name = {"image", "imag"})
    public IRubyObject image() {
        return image;
    }
    
    /** nucomp_add
     * 
     */
    @JRubyMethod(name = "+")
    public IRubyObject op_add(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyComplex) {
            RubyComplex otherComplex = (RubyComplex)other;
            return newComplex(context, getMetaClass(), 
                              f_add(context, real, otherComplex.real),
                              f_add(context, image, otherComplex.image));
        } else if (other instanceof RubyNumeric && f_scalar_p(context, other).isTrue()) {
            return newComplex(context, getMetaClass(), f_add(context, real, other), image);
        }
        return coerceBin(context, "+", other);
    }

    /** nucomp_sub
     * 
     */
    @JRubyMethod(name = "-")
    public IRubyObject op_sub(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyComplex) {
            RubyComplex otherComplex = (RubyComplex)other;
            return newComplex(context, getMetaClass(), 
                              f_sub(context, real, otherComplex.real),
                              f_sub(context, image, otherComplex.image));
        } else if (other instanceof RubyNumeric && f_scalar_p(context, other).isTrue()) {
            return newComplex(context, getMetaClass(), f_sub(context, real, other), image);
        }
        return coerceBin(context, "-", other);
    }

    /** nucomp_mul
     * 
     */
    @JRubyMethod(name = "*")
    public IRubyObject op_mul(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyComplex) {
            RubyComplex otherComplex = (RubyComplex)other;
            IRubyObject realp = f_sub(context, 
                                f_mul(context, real, otherComplex.real),
                                f_mul(context, image, otherComplex.image));
            IRubyObject imagep = f_add(context,
                                f_mul(context, real, otherComplex.image),
                                f_mul(context, image, otherComplex.real));
            
            return newComplex(context, getMetaClass(), realp, imagep); 
        } else if (other instanceof RubyNumeric && f_scalar_p(context, other).isTrue()) {
            return newComplex(context, getMetaClass(),
                    f_mul(context, real, other),
                    f_mul(context, image, other));
        }
        return coerceBin(context, "*", other);
    }
    
    /** nucomp_div
     * 
     */
    @JRubyMethod(name = "/")
    public IRubyObject op_div(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyComplex) {
            RubyComplex otherComplex = (RubyComplex)other;
            if (real instanceof RubyFloat || image instanceof RubyFloat ||
                otherComplex.real instanceof RubyFloat || otherComplex.image instanceof RubyFloat) {
                IRubyObject magn = RubyMath.hypot(this, otherComplex.real, otherComplex.image);
                IRubyObject tmp = newComplexBang(context, getMetaClass(),
                                                 f_quo(context, otherComplex.real, magn),
                                                 f_quo(context, otherComplex.image, magn));
                return f_quo(context, f_mul(context, this, f_conjugate(context, tmp)), magn);
            }
            return f_quo(context, f_mul(context, this, f_conjugate(context, other)), f_abs2(context, other));
        } else if (other instanceof RubyNumeric && f_scalar_p(context, other).isTrue()) {
            return newComplex(context, getMetaClass(),
                    f_quo(context, real, other),
                    f_quo(context, image, other));
        }
        return coerceBin(context, "/", other);
    }

    /** nucomp_fdiv / nucomp_quo
     *
     */
    @JRubyMethod(name = {"fdiv", "quo"})
    public IRubyObject fdiv(ThreadContext context, IRubyObject other) {
        IRubyObject complex = newComplex(context, getMetaClass(),
                                         f_to_f(context, real),   
                                         f_to_f(context, image));

        return f_div(context, complex, other);
    }

    /** nucomp_expt
     * 
     */
    @JRubyMethod(name = "**")
    public IRubyObject op_expt(ThreadContext context, IRubyObject other) {
        if (f_zero_p(context, other)) {
            return newComplexBang(context, getMetaClass(), RubyFixnum.one(context.getRuntime()));
        } else if (other instanceof RubyRational && f_one_p(context, f_denominator(context, other))) {
            other = f_numerator(context, other); 
        } 

        if (other instanceof RubyComplex) {
            RubyArray a = f_polar(context, this).convertToArray();
            IRubyObject r = a.eltInternal(0);
            IRubyObject theta = a.eltInternal(1);
            RubyComplex otherComplex = (RubyComplex)other;
            IRubyObject nr = RubyMath.exp(this, f_sub(context, 
                                                      f_mul(context, otherComplex.real, RubyMath.log(this, r)),
                                                      f_mul(context, otherComplex.image, theta)));
            IRubyObject ntheta = f_add(context,
                                        f_mul(context, theta, otherComplex.real),
                                        f_mul(context, otherComplex.image, RubyMath.log(this, r)));
            return polar(context, getMetaClass(), nr, ntheta);
        } else if (other instanceof RubyInteger) {
            IRubyObject one = RubyFixnum.one(context.getRuntime());
            if (f_gt_p(context, other, RubyFixnum.zero(context.getRuntime())).isTrue()) {
                IRubyObject x = this;
                IRubyObject z = x;
                IRubyObject n = f_sub(context, other, one);
                
                IRubyObject two = RubyFixnum.two(context.getRuntime());
                
                while (!f_zero_p(context, n)) {
                    
                    RubyArray a = f_divmod(context, n, two).convertToArray();

                    while (f_zero_p(context, a.eltInternal(1))) {
                        RubyComplex xComplex = (RubyComplex)x;
                        x = newComplex(context, getMetaClass(),
                                       f_sub(context, f_mul(context, xComplex.real, xComplex.real),
                                                      f_mul(context, xComplex.image, xComplex.image)),
                                       f_mul(context, f_mul(context, two, xComplex.real), xComplex.image));
                        
                        n = a.eltInternal(0);
                        a = f_divmod(context, n, two).convertToArray();
                    }
                    z = f_mul(context, z, x);
                    n = f_sub(context, n, one);
                }
                return z;
            }
            return f_expt(context, f_div(context, f_to_r(context, one), this), f_negate(context, other));
        } else if (other instanceof RubyNumeric && f_scalar_p(context, other).isTrue()) {
            RubyArray a = f_polar(context, this).convertToArray();
            IRubyObject r = a.eltInternal(0);
            IRubyObject theta = a.eltInternal(1);
            return f_complex_polar(context, getMetaClass(), f_expt(context, r, other), f_mul(context, theta, other));
        }
        return coerceBin(context, "**", other);
    }

    /** nucomp_equal_p
     * 
     */
    @JRubyMethod(name = "==")
    public IRubyObject op_equal(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyComplex) {
            RubyComplex otherComplex = (RubyComplex)other;
            if (f_equal_p(context, real, otherComplex.real) && f_equal_p(context, image, otherComplex.image)) return context.getRuntime().getTrue();
            return context.getRuntime().getFalse();
        } else if (other instanceof RubyNumeric && f_scalar_p(context, other).isTrue()) {
            if (f_equal_p(context, real, other) && f_zero_p(context, image)) return context.getRuntime().getTrue();
            return context.getRuntime().getFalse();
        }
        return f_equal_p(context, other, this) ? context.getRuntime().getTrue() : context.getRuntime().getFalse();        
    }

    /** nucomp_coerce 
     * 
     */
    @JRubyMethod(name = "coerce")
    public IRubyObject coerce(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyNumeric && f_scalar_p(context, other).isTrue()) {
            return context.getRuntime().newArray(newComplexBang(context, getMetaClass(), other), this);
        }
        throw context.getRuntime().newTypeError(other.getMetaClass().getName() + " can't be coerced into " + getMetaClass().getName());
    }

    /** nucomp_abs 
     * 
     */
    @JRubyMethod(name = {"abs", "magnitude"})
    public IRubyObject abs(ThreadContext context) {
        return RubyMath.hypot(this, real, image);
    }

    /** nucomp_abs2 
     * 
     */
    @JRubyMethod(name = "abs2")
    public IRubyObject abs2(ThreadContext context) {
        return f_add(context,
                     f_mul(context, real, real),
                     f_mul(context, image, image));
    }

    /** nucomp_arg 
     * 
     */
    @JRubyMethod(name = {"arg", "angle"})
    public IRubyObject arg(ThreadContext context) {
        return RubyMath.atan2(this, image, real);
    }

    /** nucomp_polar 
     * 
     */
    @JRubyMethod(name = "polar")
    public IRubyObject polar(ThreadContext context) {
        return context.getRuntime().newArray(f_abs(context, this), f_arg(context, this));
    }

    /** nucomp_conjugate
     * 
     */
    @JRubyMethod(name = {"conjugate", "conj", "~"})
    public IRubyObject conjugate(ThreadContext context) {
        return newComplex(context, getMetaClass(), real, f_negate(context, image));
    }

    /** nucomp_real_p
     * 
     */
    //@JRubyMethod(name = "real?")
    public IRubyObject real_p(ThreadContext context) {
        return context.getRuntime().getFalse();
    }

    /** nucomp_complex_p
     * 
     */
    // @JRubyMethod(name = "complex?")
    public IRubyObject complex_p(ThreadContext context) {
        return context.getRuntime().getTrue();
    }

    /** nucomp_exact_p
     * 
     */
    // @JRubyMethod(name = "exact?")
    public IRubyObject exact_p(ThreadContext context) {
        return (f_exact_p(context, real).isTrue() && f_exact_p(context, image).isTrue()) ? context.getRuntime().getTrue() : context.getRuntime().getFalse();
    }

    /** nucomp_exact_p
     * 
     */
    // @JRubyMethod(name = "inexact?")
    public IRubyObject inexact_p(ThreadContext context) {
        return exact_p(context).isTrue() ? context.getRuntime().getFalse() : context.getRuntime().getTrue();
    }

    /** nucomp_denominator
     * 
     */
    @JRubyMethod(name = "denominator")
    public IRubyObject demoninator(ThreadContext context) {
        return f_lcm(context, f_denominator(context, real), f_denominator(context, image));
    }

    /** nucomp_numerator
     * 
     */
    @JRubyMethod(name = "numerator")
    public IRubyObject numerator(ThreadContext context) {
        IRubyObject cd = callMethod(context, "denominator");
        return newComplex(context, getMetaClass(),
                          f_mul(context, 
                                f_numerator(context, real),
                                f_div(context, cd, f_denominator(context, real))),
                          f_mul(context,
                                f_numerator(context, image),
                                f_div(context, cd, f_denominator(context, image))));
    }
    
    /** nucomp_hash
     * 
     */
    @JRubyMethod(name = "hash")
    public IRubyObject hash(ThreadContext context) {
        return f_xor(context, real, image);
    }

    /** f_signbit
     * 
     */
    private static boolean signbit(ThreadContext context, IRubyObject x) {
        if (x instanceof RubyFloat) {
            return Double.doubleToLongBits(((RubyFloat)x).getDoubleValue()) < 0;
        }
        return f_negative_p(context, x);
    }

    /** f_tpositive_p
     * 
     */
    private static boolean tpositive_p(ThreadContext context, IRubyObject x) {
        return !signbit(context, x);
    }

    /** nucomp_to_s
     * 
     */
    @JRubyMethod(name = "to_s")
    public IRubyObject to_s(ThreadContext context) {
        boolean impos = tpositive_p(context, image);

        RubyString str = f_to_s(context, real).convertToString();
        str.cat(impos ? (byte)'+' : (byte)'-');
        str.cat(f_to_s(context, f_abs(context, image)).convertToString().getByteList());
        str.cat((byte)'i');
        return str;
    }
    
    /** nucomp_inspect
     * 
     */
    @JRubyMethod(name = "inspect")
    public IRubyObject inspect(ThreadContext context) {
        boolean impos = tpositive_p(context, image);
        RubyString str = context.getRuntime().newString();
        str.cat((byte)'(');
        str.cat(f_inspect(context, real).convertToString().getByteList());
        str.cat(impos ? (byte)'+' : (byte)'-');
        str.cat(f_inspect(context, f_abs(context, image)).convertToString().getByteList());
        str.cat((byte)'i');
        str.cat((byte)')');
        return str;
    }

    /** nucomp_marshal_dump
     * 
     */
    @JRubyMethod(name = "marshal_dump")
    public IRubyObject marshal_dump(ThreadContext context) {
        return context.getRuntime().newArray(real, image);
    }

    /** nucomp_marshal_load
     * 
     */
    @JRubyMethod(name = "marshal_load")
    public IRubyObject marshal_load(ThreadContext context, IRubyObject arg) {
        RubyArray a = arg.convertToArray();
        real = a.size() > 0 ? a.eltInternal(0) : context.getRuntime().getNil();
        image = a.size() > 1 ? a.eltInternal(1) : context.getRuntime().getNil();
        return this;
    }

    /** nucomp_scalar_p
     * 
     */
    @JRubyMethod(name = "scalar?")
    public IRubyObject scalar_p(ThreadContext context) {
        return context.getRuntime().getFalse();
    }

    /** nucomp_to_i
     * 
     */
    @JRubyMethod(name = "to_i")
    public IRubyObject to_i(ThreadContext context) {
        if (image instanceof RubyFloat || !f_zero_p(context, image)) {
            throw context.getRuntime().newRangeError("can't convert " + f_to_s(context, this).convertToString() + " into Integer");
        }
        return f_to_i(context, real);
    }

    /** nucomp_to_f
     * 
     */
    @JRubyMethod(name = "to_f")
    public IRubyObject to_f(ThreadContext context) {
        if (image instanceof RubyFloat || !f_zero_p(context, image)) {
            throw context.getRuntime().newRangeError("can't convert " + f_to_s(context, this).convertToString() + " into Float");
        }
        return f_to_f(context, real);
    }

    /** nucomp_to_f
     * 
     */
    @JRubyMethod(name = "to_r")
    public IRubyObject to_r(ThreadContext context) {
        if (image instanceof RubyFloat || !f_zero_p(context, image)) {
            throw context.getRuntime().newRangeError("can't convert " + f_to_s(context, this).convertToString() + " into Rational");            
        }
        return f_to_r(context, real);
    }
    
    static RubyArray str_to_c_internal(ThreadContext context, IRubyObject recv) {
        RubyString s = recv.callMethod(context, "strip").convertToString();
        ByteList bytes = s.getByteList();

        Ruby runtime = context.getRuntime();
        if (bytes.realSize == 0) return runtime.newArray(runtime.getNil(), recv);

        IRubyObject sr, si, re;
        sr = si = re = runtime.getNil();
        boolean po = false;
        IRubyObject m = RubyRegexp.newRegexp(runtime, Numeric.ComplexPatterns.comp_pat0).callMethod(context, "match", s);

        if (!m.isNil()) {
            sr = m.callMethod(context, "[]", RubyFixnum.one(runtime));
            si = m.callMethod(context, "[]", RubyFixnum.two(runtime));
            re = m.callMethod(context, "post_match");
            po = true;
        }

        if (m.isNil()) {
            m = RubyRegexp.newRegexp(runtime, Numeric.ComplexPatterns.comp_pat1).callMethod(context, "match", s);

            if (!m.isNil()) {
                sr = runtime.getNil();
                si = m.callMethod(context, "[]", RubyFixnum.one(runtime));
                if (si.isNil()) si = runtime.newString();
                IRubyObject t = m.callMethod(context, "[]", RubyFixnum.two(runtime));
                if (t.isNil()) t = runtime.newString(new ByteList(new byte[]{'1'}));
                si.convertToString().cat(t.convertToString().getByteList());
                re = m.callMethod(context, "post_match");
                po = false;
            }
        }

        if (m.isNil()) {
            m = RubyRegexp.newRegexp(runtime, Numeric.ComplexPatterns.comp_pat2).callMethod(context, "match", s);
            if (m.isNil()) return runtime.newArray(runtime.getNil(), recv);
            sr = m.callMethod(context, "[]", RubyFixnum.one(runtime));
            if (m.callMethod(context, "[]", RubyFixnum.two(runtime)).isNil()) {
                si = runtime.getNil();
            } else {
                si = m.callMethod(context, "[]", RubyFixnum.three(runtime));
                IRubyObject t = m.callMethod(context, "[]", RubyFixnum.four(runtime));
                if (t.isNil()) t = runtime.newString(new ByteList(new byte[]{'1'}));
                si.convertToString().cat(t.convertToString().getByteList());
            }
            re = m.callMethod(context, "post_match");
            po = false;
        }

        IRubyObject r = RubyFixnum.zero(runtime);
        IRubyObject i = r;

        if (!sr.isNil()) {
            if (sr.callMethod(context, "include?", runtime.newString(new ByteList(new byte[]{'/'}))).isTrue()) {
                r = f_to_r(context, sr);
            } else if (f_gt_p(context, sr.callMethod(context, "count", runtime.newString(".eE")), RubyFixnum.zero(runtime)).isTrue()) {
                r = f_to_f(context, sr); 
            } else {
                r = f_to_i(context, sr);
            }
        }

        if (!si.isNil()) {
            if (si.callMethod(context, "include?", runtime.newString(new ByteList(new byte[]{'/'}))).isTrue()) {
                i = f_to_r(context, si);
            } else if (f_gt_p(context, si.callMethod(context, "count", runtime.newString(".eE")), RubyFixnum.zero(runtime)).isTrue()) {
                i = f_to_f(context, si);
            } else {
                i = f_to_i(context, si);
            }
        }
        return runtime.newArray(po ? newComplexPolar(context, r, i) : newComplexCanonicalize(context, r, i), re);
    }
    
    private static IRubyObject str_to_c_strict(ThreadContext context, IRubyObject recv) {
        RubyArray a = str_to_c_internal(context, recv);
        if (a.eltInternal(0).isNil() || a.eltInternal(1).convertToString().getByteList().length() > 0) {
            IRubyObject s = recv.callMethod(context, "inspect");
            throw context.getRuntime().newArgumentError("invalid value for Complex: " + s.convertToString());
        }
        return a.eltInternal(0);
    }    
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2007 Ola Bini <ola@ologix.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.runtime.Block;
import org.jruby.runtime.builtin.IRubyObject;

/**
 * Placeholder until/if we can support this
 *
 * @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
 */
@JRubyClass(name="Continuation")
public class RubyContinuation {
    public static void createContinuation(Ruby runtime) {
        RubyClass cContinuation = runtime.defineClass("Continuation",runtime.getObject(),runtime.getObject().getAllocator());
        cContinuation.defineAnnotatedMethods(RubyContinuation.class);
        runtime.setContinuation(cContinuation);
    }

    @JRubyMethod(name = {"call", "[]"}, rest = true, frame = true)
    public static IRubyObject call(IRubyObject recv, IRubyObject[] args, Block unusedBlock) {
        throw recv.getRuntime().newNotImplementedError("Continuations are not implemented in JRuby and will not work");
    }
}// RubyContinuation
/*
 ***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2006, 2007 Ola Bini <ola@ologix.com>
 * Copyright (C) 2007 Nick Sieger <nicksieger@gmail.com>
 * Copyright (C) 2008 Vladimir Sizikov <vsizikov@gmail.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.security.Provider;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;
import org.jruby.anno.JRubyClass;

import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.callback.Callback;
import org.jruby.util.ByteList;

/**
 * @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
 */
@JRubyModule(name="Digest")
public class RubyDigest {
    private static Provider provider = null;

    public static void createDigest(Ruby runtime) {
        try {
            provider = (Provider) Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider").newInstance();
        } catch(Exception e) {
            // provider is not available
        }

        RubyModule mDigest = runtime.defineModule("Digest");
        RubyClass cDigestBase = mDigest.defineClassUnder("Base",runtime.getObject(), Base.BASE_ALLOCATOR);

        cDigestBase.defineAnnotatedMethods(Base.class);
    }

    private static MessageDigest createMessageDigest(Ruby runtime, String providerName) throws NoSuchAlgorithmException {
        if(provider != null) {
            try {
                return MessageDigest.getInstance(providerName, provider);
            } catch(NoSuchAlgorithmException e) {
                // bouncy castle doesn't support algorithm
            }
        }
        // fall back to system JCA providers
        return MessageDigest.getInstance(providerName);
    }

    @JRubyClass(name="Digest::MD5", parent="Digest::Base")
    public static class MD5 {}
    @JRubyClass(name="Digest::RMD160", parent="Digest::Base")
    public static class RMD160 {}
    @JRubyClass(name="Digest::SHA1", parent="Digest::Base")
    public static class SHA1 {}
    @JRubyClass(name="Digest::SHA256", parent="Digest::Base")
    public static class SHA256 {}
    @JRubyClass(name="Digest::SHA384", parent="Digest::Base")
    public static class SHA384 {}
    @JRubyClass(name="Digest::SHA512", parent="Digest::Base")
    public static class SHA512 {}

    public static void createDigestMD5(Ruby runtime) {
        runtime.getLoadService().require("digest.so");
        RubyModule mDigest = runtime.fastGetModule("Digest");
        RubyClass cDigestBase = mDigest.fastGetClass("Base");
        RubyClass cDigest_MD5 = mDigest.defineClassUnder("MD5",cDigestBase,cDigestBase.getAllocator());
        cDigest_MD5.defineFastMethod("block_length", new Callback() {
            public Arity getArity() {
                return Arity.NO_ARGUMENTS;
            }
            public IRubyObject execute(IRubyObject recv, IRubyObject[] args, Block block) {
                return RubyFixnum.newFixnum(recv.getRuntime(), 64);
            }
        });
        cDigest_MD5.setInternalModuleVariable("metadata",runtime.newString("MD5"));
    }

    public static void createDigestRMD160(Ruby runtime) {
        runtime.getLoadService().require("digest.so");
        if(provider == null) {
            throw runtime.newLoadError("RMD160 not supported without BouncyCastle");
        }

        RubyModule mDigest = runtime.fastGetModule("Digest");
        RubyClass cDigestBase = mDigest.fastGetClass("Base");
        RubyClass cDigest_RMD160 = mDigest.defineClassUnder("RMD160",cDigestBase,cDigestBase.getAllocator());
        cDigest_RMD160.setInternalModuleVariable("metadata",runtime.newString("RIPEMD160"));
    }

    public static void createDigestSHA1(Ruby runtime) {
        runtime.getLoadService().require("digest.so");
        RubyModule mDigest = runtime.fastGetModule("Digest");
        RubyClass cDigestBase = mDigest.fastGetClass("Base");
        RubyClass cDigest_SHA1 = mDigest.defineClassUnder("SHA1",cDigestBase,cDigestBase.getAllocator());
        cDigest_SHA1.setInternalModuleVariable("metadata",runtime.newString("SHA1"));
    }

    public static void createDigestSHA2(Ruby runtime) {
        runtime.getLoadService().require("digest.so");
        try {
            createMessageDigest(runtime, "SHA-256");
        } catch(NoSuchAlgorithmException e) {
            throw runtime.newLoadError("SHA2 not supported");
        }

        RubyModule mDigest = runtime.fastGetModule("Digest");
        RubyClass cDigestBase = mDigest.fastGetClass("Base");
        RubyClass cDigest_SHA2_256 = mDigest.defineClassUnder("SHA256",cDigestBase,cDigestBase.getAllocator());
        cDigest_SHA2_256.setInternalModuleVariable("metadata",runtime.newString("SHA-256"));
        RubyClass cDigest_SHA2_384 = mDigest.defineClassUnder("SHA384",cDigestBase,cDigestBase.getAllocator());
        cDigest_SHA2_384.setInternalModuleVariable("metadata",runtime.newString("SHA-384"));
        RubyClass cDigest_SHA2_512 = mDigest.defineClassUnder("SHA512",cDigestBase,cDigestBase.getAllocator());
        cDigest_SHA2_512.setInternalModuleVariable("metadata",runtime.newString("SHA-512"));
    }

    @JRubyClass(name="Digest::Base")
    public static class Base extends RubyObject {
        protected static final ObjectAllocator BASE_ALLOCATOR = new ObjectAllocator() {
            public IRubyObject allocate(Ruby runtime, RubyClass klass) {
                return new Base(runtime, klass);
            }
        };
        
        @JRubyMethod(name = "digest", required = 1, meta = true)
        public static IRubyObject s_digest(IRubyObject recv, IRubyObject str) {
            Ruby runtime = recv.getRuntime();
            String name = ((RubyClass)recv).searchInternalModuleVariable("metadata").toString();
            try {
                MessageDigest md = createMessageDigest(runtime, name);
                return RubyString.newString(runtime, md.digest(str.convertToString().getBytes()));
            } catch(NoSuchAlgorithmException e) {
                throw recv.getRuntime().newNotImplementedError("Unsupported digest algorithm (" + name + ")");
            }
        }
        
        @JRubyMethod(name = "hexdigest", required = 1, meta = true)
        public static IRubyObject s_hexdigest(IRubyObject recv, IRubyObject str) {
            Ruby runtime = recv.getRuntime();
            String name = ((RubyClass)recv).searchInternalModuleVariable("metadata").toString();
            try {
                MessageDigest md = createMessageDigest(runtime, name);
                return RubyString.newString(runtime, ByteList.plain(toHex(md.digest(str.convertToString().getBytes()))));
            } catch(NoSuchAlgorithmException e) {
                throw recv.getRuntime().newNotImplementedError("Unsupported digest algorithm (" + name + ")");
            }
        }

        private MessageDigest algo;
        private StringBuffer data;

        public Base(Ruby runtime, RubyClass type) {
            super(runtime,type);
            data = new StringBuffer();

            if(type == runtime.fastGetModule("Digest").fastGetClass("Base")) {
                throw runtime.newNotImplementedError("Digest::Base is an abstract class");
            }
            if(!type.hasInternalModuleVariable("metadata")) {
                throw runtime.newNotImplementedError("the " + type + "() function is unimplemented on this machine");
            }
            try {
                setAlgorithm(type.searchInternalModuleVariable("metadata"));
            } catch(NoSuchAlgorithmException e) {
                throw runtime.newNotImplementedError("the " + type + "() function is unimplemented on this machine");
            }

        }
        
        @JRubyMethod(name = "initialize", optional = 1, frame = true)
        public IRubyObject initialize(IRubyObject[] args, Block unusedBlock) {
            if(args.length > 0 && !args[0].isNil()) {
                update(args[0]);
            }
            return this;
        }

        @JRubyMethod(name = "initialize_copy", required = 1)
        public IRubyObject initialize_copy(IRubyObject obj) {
            if(this == obj) {
                return this;
            }
            ((RubyObject)obj).checkFrozen();

            data = new StringBuffer(((Base)obj).data.toString());
            String name = ((Base)obj).algo.getAlgorithm();
            try {
                algo = createMessageDigest(getRuntime(), name);
            } catch(NoSuchAlgorithmException e) {
                throw getRuntime().newNotImplementedError("Unsupported digest algorithm (" + name + ")");
            }
            return this;
        }

        @JRubyMethod(name = {"update", "<<"}, required = 1)
        public IRubyObject update(IRubyObject obj) {
            data.append(obj);
            return this;
        }

        @JRubyMethod(name = "digest", optional = 1)
        public IRubyObject digest(IRubyObject[] args) {
            if (args.length == 1) {
                reset();
                data.append(args[0]);
            }

            IRubyObject digest = getDigest();

            if (args.length == 1) {
                reset();
            }
            return digest;
        }
        
        private IRubyObject getDigest() {
            algo.reset();
            return RubyString.newString(getRuntime(), algo.digest(ByteList.plain(data)));
        }
        
        @JRubyMethod(name = "digest!")
        public IRubyObject digest_bang() {
            algo.reset();            
            byte[] digest = algo.digest(ByteList.plain(data));
            reset();
            return RubyString.newString(getRuntime(), digest);
        }

        @JRubyMethod(name = {"hexdigest"}, optional = 1)
        public IRubyObject hexdigest(IRubyObject[] args) {
            algo.reset();
            if (args.length == 1) {
                reset();
                data.append(args[0]);
            }

            byte[] digest = ByteList.plain(toHex(algo.digest(ByteList.plain(data)))); 

            if (args.length == 1) {
                reset();
            }
            return RubyString.newString(getRuntime(), digest);
        }
        
        @JRubyMethod(name = {"to_s"})
        public IRubyObject to_s() {
            algo.reset();
            return RubyString.newString(getRuntime(), ByteList.plain(toHex(algo.digest(ByteList.plain(data)))));
        }

        @JRubyMethod(name = {"hexdigest!"})
        public IRubyObject hexdigest_bang() {
            algo.reset();
            byte[] digest = ByteList.plain(toHex(algo.digest(ByteList.plain(data))));
            reset();
            return RubyString.newString(getRuntime(), digest);
        }
        
        @JRubyMethod(name = "inspect")
        public IRubyObject inspect() {
            algo.reset();
            return RubyString.newString(getRuntime(), ByteList.plain("#<" + getMetaClass().getRealClass().getName() + ": " + toHex(algo.digest(ByteList.plain(data))) + ">"));
        }

        @JRubyMethod(name = "==", required = 1)
        public IRubyObject op_equal(IRubyObject oth) {
            boolean ret = this == oth;
            if(!ret) {
                if (oth instanceof Base) {
                    Base b = (Base)oth;
                    ret = this.algo.getAlgorithm().equals(b.algo.getAlgorithm()) &&
                            this.getDigest().equals(b.getDigest());
                } else {
                    IRubyObject str = oth.convertToString();
                    ret = this.to_s().equals(str);
                }
            }

            return ret ? getRuntime().getTrue() : getRuntime().getFalse();
        }
        
        @JRubyMethod(name = {"length", "size", "digest_length"})
        public IRubyObject length() {
            return RubyFixnum.newFixnum(getRuntime(), algo.getDigestLength());
        }

        @JRubyMethod(name = {"block_length"})
        public IRubyObject block_length() {
            throw getRuntime().newRuntimeError(
                    this.getMetaClass() + " doesn't implement block_length()");
        }

        @JRubyMethod(name = {"reset"})
        public IRubyObject reset() {
            algo.reset();
            data = new StringBuffer();
            return getRuntime().getNil();
        }

       private void setAlgorithm(IRubyObject algo) throws NoSuchAlgorithmException {
           this.algo = createMessageDigest(getRuntime(), algo.toString());
        }

        private static String toHex(byte[] val) {
            StringBuilder out = new StringBuilder();
            for(int i=0,j=val.length;i<j;i++) {
                String ve = Integer.toString((((int)((char)val[i])) & 0xFF),16);
                if(ve.length() == 1) {
                    ve = "0" + ve;
                }
                out.append(ve);
            }
            return out.toString();
        }
    }
}// RubyDigest
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2002-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004-2005 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.ext.posix.util.Platform;

import org.jruby.javasupport.JavaUtil;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.Dir;
import org.jruby.util.JRubyFile;
import org.jruby.util.ByteList;

/**
 * .The Ruby built-in class Dir.
 *
 * @author  jvoegele
 */
@JRubyClass(name="Dir", include="Enumerable")
public class RubyDir extends RubyObject {
	// What we passed to the constructor for method 'path'
    private RubyString    path;
    protected JRubyFile      dir;
    private   String[]  snapshot;   // snapshot of contents of directory
    private   int       pos;        // current position in directory
    private boolean isOpen = true;

    public RubyDir(Ruby runtime, RubyClass type) {
        super(runtime, type);
    }
    
    private static final ObjectAllocator DIR_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return new RubyDir(runtime, klass);
        }
    };

    public static RubyClass createDirClass(Ruby runtime) {
        RubyClass dirClass = runtime.defineClass("Dir", runtime.getObject(), DIR_ALLOCATOR);
        runtime.setDir(dirClass);

        dirClass.includeModule(runtime.getEnumerable());
        
        dirClass.defineAnnotatedMethods(RubyDir.class);

        return dirClass;
    }
    
    private final void checkDir() {
        if (!isTaint() && getRuntime().getSafeLevel() >= 4) throw getRuntime().newSecurityError("Insecure: operation on untainted Dir");
     
        testFrozen("");
        
        if (!isOpen) throw getRuntime().newIOError("closed directory");
    }    

    /**
     * Creates a new <code>Dir</code>.  This method takes a snapshot of the
     * contents of the directory at creation time, so changes to the contents
     * of the directory will not be reflected during the lifetime of the
     * <code>Dir</code> object returned, so a new <code>Dir</code> instance
     * must be created to reflect changes to the underlying file system.
     */
    @JRubyMethod(name = "initialize", required = 1, frame = true)
    public IRubyObject initialize(IRubyObject _newPath, Block unusedBlock) {
        RubyString newPath = _newPath.convertToString();
        getRuntime().checkSafeString(newPath);

        String adjustedPath = RubyFile.adjustRootPathOnWindows(getRuntime(), newPath.toString(), null);
        checkDirIsTwoSlashesOnWindows(getRuntime(), adjustedPath);

        dir = JRubyFile.create(getRuntime().getCurrentDirectory(), adjustedPath);
        if (!dir.isDirectory()) {
            dir = null;
            throw getRuntime().newErrnoENOENTError(newPath.toString() + " is not a directory");
        }
        path = newPath;
		List<String> snapshotList = new ArrayList<String>();
		snapshotList.add(".");
		snapshotList.add("..");
		snapshotList.addAll(getContents(dir));
		snapshot = (String[]) snapshotList.toArray(new String[snapshotList.size()]);
		pos = 0;

        return this;
    }

// ----- Ruby Class Methods ----------------------------------------------------
    
    private static List<ByteList> dirGlobs(String cwd, IRubyObject[] args, int flags) {
        List<ByteList> dirs = new ArrayList<ByteList>();
        
        for (int i = 0; i < args.length; i++) {
            ByteList globPattern = args[i].convertToString().getByteList();
            dirs.addAll(Dir.push_glob(cwd, globPattern, flags));
        }
        
        return dirs;
    }
    
    private static IRubyObject asRubyStringList(Ruby runtime, List<ByteList> dirs) {
        List<RubyString> allFiles = new ArrayList<RubyString>();

        for (ByteList dir: dirs) {
            allFiles.add(RubyString.newString(runtime, dir));
        }            

        IRubyObject[] tempFileList = new IRubyObject[allFiles.size()];
        allFiles.toArray(tempFileList);
         
        return runtime.newArrayNoCopy(tempFileList);
    }
    
    private static String getCWD(Ruby runtime) {
        try {
            return new org.jruby.util.NormalizedFile(runtime.getCurrentDirectory()).getCanonicalPath();
        } catch(Exception e) {
            return runtime.getCurrentDirectory();
        }
    }

    @JRubyMethod(name = "[]", required = 1, rest=true, meta = true)
    public static IRubyObject aref(IRubyObject recv, IRubyObject[] args) {
        List<ByteList> dirs;
        if (args.length == 1) {
            ByteList globPattern = args[0].convertToString().getByteList();
            dirs = Dir.push_glob(getCWD(recv.getRuntime()), globPattern, 0);
        } else {
            dirs = dirGlobs(getCWD(recv.getRuntime()), args, 0);
        }

        return asRubyStringList(recv.getRuntime(), dirs);
    }
    
    /**
     * Returns an array of filenames matching the specified wildcard pattern
     * <code>pat</code>. If a block is given, the array is iterated internally
     * with each filename is passed to the block in turn. In this case, Nil is
     * returned.  
     */
    @JRubyMethod(name = "glob", required = 1, optional = 1, frame = true, meta = true)
    public static IRubyObject glob(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        Ruby runtime = recv.getRuntime();
        int flags = args.length == 2 ?  RubyNumeric.num2int(args[1]) : 0;

        List<ByteList> dirs;
        IRubyObject tmp = args[0].checkArrayType();
        if (tmp.isNil()) {
            ByteList globPattern = args[0].convertToString().getByteList();
            dirs = Dir.push_glob(recv.getRuntime().getCurrentDirectory(), globPattern, flags);
        } else {
            dirs = dirGlobs(getCWD(runtime), ((RubyArray) tmp).toJavaArray(), flags);
        }
        
        if (block.isGiven()) {
            for (int i = 0; i < dirs.size(); i++) {
                block.yield(context, RubyString.newString(runtime, dirs.get(i)));
            }
        
            return recv.getRuntime().getNil();
        }

        return asRubyStringList(recv.getRuntime(), dirs);
    }

    /**
     * @return all entries for this Dir
     */
    @JRubyMethod(name = "entries")
    public RubyArray entries() {
        return getRuntime().newArrayNoCopy(JavaUtil.convertJavaArrayToRuby(getRuntime(), snapshot));
    }
    
    /**
     * Returns an array containing all of the filenames in the given directory.
     */
    @JRubyMethod(name = "entries", required = 1, meta = true)
    public static RubyArray entries(IRubyObject recv, IRubyObject path) {
        Ruby runtime = recv.getRuntime();

        String adjustedPath = RubyFile.adjustRootPathOnWindows(
                runtime, path.convertToString().toString(), null);
        checkDirIsTwoSlashesOnWindows(runtime, adjustedPath);

        final JRubyFile directory = JRubyFile.create(
                recv.getRuntime().getCurrentDirectory(), adjustedPath);

        if (!directory.isDirectory()) {
            throw recv.getRuntime().newErrnoENOENTError("No such directory");
        }
        List<String> fileList = getContents(directory);
		fileList.add(0, ".");
		fileList.add(1, "..");
        Object[] files = fileList.toArray();
        return recv.getRuntime().newArrayNoCopy(JavaUtil.convertJavaArrayToRuby(recv.getRuntime(), files));
    }
    
    // MRI behavior: just plain '//' or '\\\\' are considered illegal on Windows.
    private static void checkDirIsTwoSlashesOnWindows(Ruby runtime, String path) {
        if (Platform.IS_WINDOWS && ("//".equals(path) || "\\\\".equals(path))) {
            throw runtime.newErrnoEINVALError("Invalid argument - " + path);
        }
    }

    /** Changes the current directory to <code>path</code> */
    @JRubyMethod(name = "chdir", optional = 1, frame = true, meta = true)
    public static IRubyObject chdir(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        RubyString path = args.length == 1 ? 
            (RubyString) args[0].convertToString() : getHomeDirectoryPath(context);
        String adjustedPath = RubyFile.adjustRootPathOnWindows(
                recv.getRuntime(), path.toString(), null);
        checkDirIsTwoSlashesOnWindows(recv.getRuntime(), adjustedPath);
        JRubyFile dir = getDir(recv.getRuntime(), adjustedPath, true);
        String realPath = null;
        String oldCwd = recv.getRuntime().getCurrentDirectory();
        
        // We get canonical path to try and flatten the path out.
        // a dir '/subdir/..' should return as '/'
        // cnutter: Do we want to flatten path out?
        try {
            realPath = dir.getCanonicalPath();
        } catch (IOException e) {
            realPath = dir.getAbsolutePath();
        }
        
        IRubyObject result = null;
        if (block.isGiven()) {
        	// FIXME: Don't allow multiple threads to do this at once
            recv.getRuntime().setCurrentDirectory(realPath);
            try {
                result = block.yield(context, path);
            } finally {
                dir = getDir(recv.getRuntime(), oldCwd, true);
                recv.getRuntime().setCurrentDirectory(oldCwd);
            }
        } else {
        	recv.getRuntime().setCurrentDirectory(realPath);
        	result = recv.getRuntime().newFixnum(0);
        }
        
        return result;
    }

    /**
     * Changes the root directory (only allowed by super user).  Not available
     * on all platforms.
     */
    @JRubyMethod(name = "chroot", required = 1, meta = true)
    public static IRubyObject chroot(IRubyObject recv, IRubyObject path) {
        throw recv.getRuntime().newNotImplementedError("chroot not implemented: chroot is non-portable and is not supported.");
    }

    /**
     * Deletes the directory specified by <code>path</code>.  The directory must
     * be empty.
     */
    @JRubyMethod(name = {"rmdir", "unlink", "delete"}, required = 1, meta = true)
    public static IRubyObject rmdir(IRubyObject recv, IRubyObject path) {
        JRubyFile directory = getDir(recv.getRuntime(), path.convertToString().toString(), true);
        
        if (!directory.delete()) {
            throw recv.getRuntime().newSystemCallError("No such directory");
        }
        
        return recv.getRuntime().newFixnum(0);
    }

    /**
     * Executes the block once for each file in the directory specified by
     * <code>path</code>.
     */
    @JRubyMethod(name = "foreach", required = 1, frame = true, meta = true)
    public static IRubyObject foreach(ThreadContext context, IRubyObject recv, IRubyObject _path, Block block) {
        RubyString path = _path.convertToString();
        recv.getRuntime().checkSafeString(path);

        RubyClass dirClass = recv.getRuntime().getDir();
        RubyDir dir = (RubyDir) dirClass.newInstance(context, new IRubyObject[] { path }, block);
        
        dir.each(context, block);
        return recv.getRuntime().getNil();
    }

    /** Returns the current directory. */
    @JRubyMethod(name = {"getwd", "pwd"}, meta = true)
    public static RubyString getwd(IRubyObject recv) {
        Ruby ruby = recv.getRuntime();
        
        return RubyString.newUnicodeString(ruby, ruby.getCurrentDirectory());
    }

    /**
     * Creates the directory specified by <code>path</code>.  Note that the
     * <code>mode</code> parameter is provided only to support existing Ruby
     * code, and is ignored.
     */
    @JRubyMethod(name = "mkdir", required = 1, optional = 1, meta = true)
    public static IRubyObject mkdir(IRubyObject recv, IRubyObject[] args) {
        Ruby runtime = recv.getRuntime();
        runtime.checkSafeString(args[0]);
        String path = args[0].toString();

        File newDir = getDir(runtime, path, false);
        if (File.separatorChar == '\\') {
            newDir = new File(newDir.getPath());
        }
        
        int mode = args.length == 2 ? ((int) args[1].convertToInteger().getLongValue()) : 0777;

        if (runtime.getPosix().mkdir(newDir.getAbsolutePath(), mode) < 0) {
            // FIXME: This is a system error based on errno
            throw recv.getRuntime().newSystemCallError("mkdir failed");
        }
        
        return RubyFixnum.zero(recv.getRuntime());
    }

    /**
     * Returns a new directory object for <code>path</code>.  If a block is
     * provided, a new directory object is passed to the block, which closes the
     * directory object before terminating.
     */
    @JRubyMethod(name = "open", required = 1, frame = true, meta = true)
    public static IRubyObject open(ThreadContext context, IRubyObject recv, IRubyObject path, Block block) {
        RubyDir directory = 
            (RubyDir) recv.getRuntime().getDir().newInstance(context,
                    new IRubyObject[] { path }, Block.NULL_BLOCK);

        if (!block.isGiven()) return directory;
        
        try {
            return block.yield(context, directory);
        } finally {
            directory.close();
        }
    }

// ----- Ruby Instance Methods -------------------------------------------------

    /**
     * Closes the directory stream.
     */
    @JRubyMethod(name = "close")
    public IRubyObject close() {
        // Make sure any read()s after close fail.
        checkDir();
        
        isOpen = false;

        return getRuntime().getNil();
    }

    /**
     * Executes the block once for each entry in the directory.
     */
    @JRubyMethod(name = "each", frame = true)
    public IRubyObject each(ThreadContext context, Block block) {
        checkDir();
        
        String[] contents = snapshot;
        for (int i=0; i<contents.length; i++) {
            block.yield(context, getRuntime().newString(contents[i]));
        }
        return this;
    }

    /**
     * Returns the current position in the directory.
     */
    @JRubyMethod(name = {"tell", "pos"})
    public RubyInteger tell() {
        checkDir();
        return getRuntime().newFixnum(pos);
    }

    /**
     * Moves to a position <code>d</code>.  <code>pos</code> must be a value
     * returned by <code>tell</code> or 0.
     */
    @JRubyMethod(name = "seek", required = 1)
    public IRubyObject seek(IRubyObject newPos) {
        checkDir();
        
        set_pos(newPos);
        return this;
    }
    
    @JRubyMethod(name = "pos=", required = 1)
    public IRubyObject set_pos(IRubyObject newPos) {
        this.pos = RubyNumeric.fix2int(newPos);
        return newPos;
    }

    @JRubyMethod(name = "path")
    public IRubyObject path(ThreadContext context) {
        checkDir();
        
        return path.strDup(context.getRuntime());
    }

    /** Returns the next entry from this directory. */
    @JRubyMethod(name = "read")
    public IRubyObject read() {
        checkDir();

        if (pos >= snapshot.length) {
            return getRuntime().getNil();
        }
        RubyString result = getRuntime().newString(snapshot[pos]);
        pos++;
        return result;
    }

    /** Moves position in this directory to the first entry. */
    @JRubyMethod(name = "rewind")
    public IRubyObject rewind() {
        if (!isTaint() && getRuntime().getSafeLevel() >= 4) throw getRuntime().newSecurityError("Insecure: can't close");
        checkDir();

        pos = 0;
        return this;
    }

// ----- Helper Methods --------------------------------------------------------

    /** Returns a Java <code>File</code> object for the specified path.  If
     * <code>path</code> is not a directory, throws <code>IOError</code>.
     *
     * @param   path path for which to return the <code>File</code> object.
     * @param   mustExist is true the directory must exist.  If false it must not.
     * @throws  IOError if <code>path</code> is not a directory.
     */
    protected static JRubyFile getDir(final Ruby runtime, final String path, final boolean mustExist) {
        JRubyFile result = JRubyFile.create(runtime.getCurrentDirectory(),path);
        if (mustExist && !result.exists()) {
            throw runtime.newErrnoENOENTError("No such file or directory - " + path);
        }
        boolean isDirectory = result.isDirectory();
        
        if (mustExist && !isDirectory) {
            throw runtime.newErrnoENOTDIRError(path + " is not a directory");
        } else if (!mustExist && isDirectory) {
            throw runtime.newErrnoEEXISTError("File exists - " + path); 
        }

        return result;
    }

    /**
     * Returns the contents of the specified <code>directory</code> as an
     * <code>ArrayList</code> containing the names of the files as Java Strings.
     */
    protected static List<String> getContents(File directory) {
        String[] contents = directory.list();
        List<String> result = new ArrayList<String>();

        // If an IO exception occurs (something odd, but possible)
        // A directory may return null.
        if (contents != null) {
            for (int i=0; i<contents.length; i++) {
                result.add(contents[i]);
            }
        }
        return result;
    }

    /**
     * Returns the contents of the specified <code>directory</code> as an
     * <code>ArrayList</code> containing the names of the files as Ruby Strings.
     */
    protected static List<RubyString> getContents(File directory, Ruby runtime) {
        List<RubyString> result = new ArrayList<RubyString>();
        String[] contents = directory.list();
        
        for (int i = 0; i < contents.length; i++) {
            result.add(runtime.newString(contents[i]));
        }
        return result;
    }
	
    /**
     * Returns the home directory of the specified <code>user</code> on the
     * system. If the home directory of the specified user cannot be found,
     * an <code>ArgumentError it thrown</code>.
     */
    public static IRubyObject getHomeDirectoryPath(ThreadContext context, String user) {
        /*
         * TODO: This version is better than the hackish previous one. Windows
         *       behavior needs to be defined though. I suppose this version
         *       could be improved more too.
         * TODO: /etc/passwd is also inadequate for MacOSX since it does not
         *       use /etc/passwd for regular user accounts
         */

        String passwd = null;
        try {
            FileInputStream stream = new FileInputStream("/etc/passwd");
            int totalBytes = stream.available();
            byte[] bytes = new byte[totalBytes];
            stream.read(bytes);
            stream.close();
            passwd = new String(bytes);
        } catch (IOException e) {
            return context.getRuntime().getNil();
        }

        String[] rows = passwd.split("\n");
        int rowCount = rows.length;
        for (int i = 0; i < rowCount; i++) {
            String[] fields = rows[i].split(":");
            if (fields[0].equals(user)) {
                return context.getRuntime().newString(fields[5]);
            }
        }

        throw context.getRuntime().newArgumentError("user " + user + " doesn't exist");
    }

    public static RubyString getHomeDirectoryPath(ThreadContext context) {
        Ruby runtime = context.getRuntime();
        RubyHash systemHash = (RubyHash) runtime.getObject().fastGetConstant("ENV_JAVA");
        RubyHash envHash = (RubyHash) runtime.getObject().fastGetConstant("ENV");
        IRubyObject home = envHash.op_aref(context, runtime.newString("HOME"));

        if (home == null || home.isNil()) {
            home = systemHash.op_aref(context, runtime.newString("user.home"));
        }

        if (home == null || home.isNil()) {
            home = envHash.op_aref(context, runtime.newString("LOGDIR"));
        }

        if (home == null || home.isNil()) {
            throw runtime.newArgumentError("user.home/LOGDIR not set");
        }

        return (RubyString) home;
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2006 Ola Bini <ola@ologix.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.util.Comparator;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;

import org.jruby.exceptions.JumpException;
import org.jruby.javasupport.util.RuntimeHelpers;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.CallBlock;
import org.jruby.runtime.BlockCallback;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.TypeConverter;

/**
 * The implementation of Ruby's Enumerable module.
 */

@JRubyModule(name="Enumerable")
public class RubyEnumerable {

    public static RubyModule createEnumerableModule(Ruby runtime) {
        RubyModule enumModule = runtime.defineModule("Enumerable");
        runtime.setEnumerable(enumModule);
        
        enumModule.defineAnnotatedMethods(RubyEnumerable.class);

        return enumModule;
    }

    public static IRubyObject callEach(Ruby runtime, ThreadContext context, IRubyObject self,
            BlockCallback callback) {
        return RuntimeHelpers.invoke(context, self, "each", CallBlock.newCallClosure(self, runtime.getEnumerable(), 
                Arity.noArguments(), callback, context));
    }

    private static class ExitIteration extends RuntimeException {
        public Throwable fillInStackTrace() {
            return this;
        }
    }

    @JRubyMethod(name = "first")
    public static IRubyObject first_0(ThreadContext context, IRubyObject self) {
        Ruby runtime = self.getRuntime();
        final ThreadContext localContext = context;
        
        final IRubyObject[] holder = new IRubyObject[]{runtime.getNil()};

        try {
            callEach(runtime, context, self, new BlockCallback() {
                    public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                        if (localContext != ctx) {
                            throw ctx.getRuntime().newThreadError("Enumerable#first cannot be parallelized");
                        }
                        holder[0] = largs[0];
                        throw new ExitIteration();
                    }
                });
        } catch(ExitIteration ei) {}

        return holder[0];
    }

    @JRubyMethod(name = "first")
    public static IRubyObject first_1(ThreadContext context, IRubyObject self, final IRubyObject num) {
        final Ruby runtime = self.getRuntime();
        final RubyArray result = runtime.newArray();
        final ThreadContext localContext = context;

        if(RubyNumeric.fix2int(num) < 0) {
            throw runtime.newArgumentError("negative index");
        }

        try {
            callEach(runtime, context, self, new BlockCallback() {
                    private int iter = RubyNumeric.fix2int(num);
                    public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                        if (localContext != ctx) {
                            throw runtime.newThreadError("Enumerable#first cannot be parallelized");
                        }
                        if(iter-- == 0) {
                            throw new ExitIteration();
                        }
                        result.append(largs[0]);
                        return runtime.getNil();
                    }
                });
        } catch(ExitIteration ei) {}

        return result;
    }

    @JRubyMethod(name = {"to_a", "entries"})
    public static IRubyObject to_a(ThreadContext context, IRubyObject self) {
        Ruby runtime = self.getRuntime();
        RubyArray result = runtime.newArray();

        callEach(runtime, context, self, new AppendBlockCallback(runtime, result));

        return result;
    }

    @JRubyMethod(name = "sort", frame = true)
    public static IRubyObject sort(ThreadContext context, IRubyObject self, final Block block) {
        Ruby runtime = self.getRuntime();
        RubyArray result = runtime.newArray();

        callEach(runtime, context, self, new AppendBlockCallback(runtime, result));
        result.sort_bang(block);
        
        return result;
    }

    @JRubyMethod(name = "sort_by", frame = true)
    public static IRubyObject sort_by(ThreadContext context, IRubyObject self, final Block block) {
        final Ruby runtime = self.getRuntime();
        final ThreadContext localContext = context; // MUST NOT be used across threads

        if (self instanceof RubyArray) {
            RubyArray selfArray = (RubyArray) self;
            final IRubyObject[][] valuesAndCriteria = new IRubyObject[selfArray.size()][2];

            callEach(runtime, context, self, new BlockCallback() {
                AtomicInteger i = new AtomicInteger(0);

                public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                    IRubyObject[] myVandC = valuesAndCriteria[i.getAndIncrement()];
                    myVandC[0] = largs[0];
                    myVandC[1] = block.yield(ctx, largs[0]);
                    return runtime.getNil();
                }
            });
            
            Arrays.sort(valuesAndCriteria, new Comparator<IRubyObject[]>() {
                public int compare(IRubyObject[] o1, IRubyObject[] o2) {
                    return RubyFixnum.fix2int(o1[1].callMethod(localContext, MethodIndex.OP_SPACESHIP, "<=>", o2[1]));
                }
            });
            
            IRubyObject dstArray[] = new IRubyObject[selfArray.size()];
            for (int i = 0; i < dstArray.length; i++) {
                dstArray[i] = valuesAndCriteria[i][0];
            }

            return runtime.newArrayNoCopy(dstArray);
        } else {
            final RubyArray result = runtime.newArray();
            callEach(runtime, context, self, new AppendBlockCallback(runtime, result));
            
            final IRubyObject[][] valuesAndCriteria = new IRubyObject[result.size()][2];
            for (int i = 0; i < valuesAndCriteria.length; i++) {
                IRubyObject val = result.eltInternal(i);
                valuesAndCriteria[i][0] = val;
                valuesAndCriteria[i][1] = block.yield(context, val);
            }
            
            Arrays.sort(valuesAndCriteria, new Comparator<IRubyObject[]>() {
                public int compare(IRubyObject[] o1, IRubyObject[] o2) {
                    return RubyFixnum.fix2int(o1[1].callMethod(localContext, MethodIndex.OP_SPACESHIP, "<=>", o2[1]));
                }
            });
            
            for (int i = 0; i < valuesAndCriteria.length; i++) {
                result.eltInternalSet(i, valuesAndCriteria[i][0]);
            }

            return result;
        }
    }

    @JRubyMethod(name = "grep", required = 1, frame = true)
    public static IRubyObject grep(ThreadContext context, IRubyObject self, final IRubyObject pattern, final Block block) {
        final Ruby runtime = self.getRuntime();
        final RubyArray result = runtime.newArray();

        if (block.isGiven()) {
            callEach(runtime, context, self, new BlockCallback() {
                public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                    ctx.setRubyFrameDelta(ctx.getRubyFrameDelta()+2);
                    if (pattern.callMethod(ctx, MethodIndex.OP_EQQ, "===", largs[0]).isTrue()) {
                        IRubyObject value = block.yield(ctx, largs[0]);
                        synchronized (result) {
                            result.append(value);
                        }
                    }
                    ctx.setRubyFrameDelta(ctx.getRubyFrameDelta()-2);
                    return runtime.getNil();
                }
            });
        } else {
            callEach(runtime, context, self, new BlockCallback() {
                public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                    if (pattern.callMethod(ctx, MethodIndex.OP_EQQ, "===", largs[0]).isTrue()) {
                        synchronized (result) {
                            result.append(largs[0]);
                        }
                    }
                    return runtime.getNil();
                }
            });
        }
        
        return result;
    }

    @JRubyMethod(name = {"detect", "find"}, optional = 1, frame = true)
    public static IRubyObject detect(ThreadContext context, IRubyObject self, IRubyObject[] args, final Block block) {
        final Ruby runtime = self.getRuntime();
        final IRubyObject result[] = new IRubyObject[] { null };
        final ThreadContext localContext = context;
        IRubyObject ifnone = null;

        if (args.length == 1) ifnone = args[0];

        try {
            callEach(runtime, context, self, new BlockCallback() {
                public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                    if (localContext != ctx) {
                        throw runtime.newThreadError("Enumerable#detect/find cannot be parallelized");
                    }
                    if (block.yield(ctx, largs[0]).isTrue()) {
                        result[0] = largs[0];
                        throw JumpException.SPECIAL_JUMP;
                    }
                    return runtime.getNil();
                }
            });
        } catch (JumpException.SpecialJump sj) {
            return result[0];
        }

        return ifnone != null ? ifnone.callMethod(context, "call") : runtime.getNil();
    }

    @JRubyMethod(name = {"select", "find_all"}, frame = true)
    public static IRubyObject select(ThreadContext context, IRubyObject self, final Block block) {
        final Ruby runtime = self.getRuntime();
        final RubyArray result = runtime.newArray();

        callEach(runtime, context, self, new BlockCallback() {
            public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                if (block.yield(ctx, largs[0]).isTrue()) {
                    synchronized (result) {
                        result.append(largs[0]);
                    }
                }
                return runtime.getNil();
            }
        });

        return result;
    }

    @JRubyMethod(name = "reject", frame = true)
    public static IRubyObject reject(ThreadContext context, IRubyObject self, final Block block) {
        final Ruby runtime = self.getRuntime();
        final RubyArray result = runtime.newArray();

        callEach(runtime, context, self, new BlockCallback() {
            public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                if (!block.yield(ctx, largs[0]).isTrue()) {
                    synchronized (result) {
                        result.append(largs[0]);
                    }
                }
                return runtime.getNil();
            }
        });

        return result;
    }

    @JRubyMethod(name = {"collect", "map"}, frame = true)
    public static IRubyObject collect(ThreadContext context, IRubyObject self, final Block block) {
        final Ruby runtime = self.getRuntime();
        final RubyArray result = runtime.newArray();

        if (block.isGiven()) {
            callEach(runtime, context, self, new BlockCallback() {
                public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                    IRubyObject value = block.yield(ctx, largs[0]);
                    synchronized (result) {
                        result.append(value);
                    }
                    return runtime.getNil();
                }
            });
        } else {
            callEach(runtime, context, self, new AppendBlockCallback(runtime, result));
        }
        return result;
    }

    @JRubyMethod(name = "inject", optional = 1, frame = true)
    public static IRubyObject inject(ThreadContext context, IRubyObject self, IRubyObject[] args, final Block block) {
        final Ruby runtime = self.getRuntime();
        final IRubyObject result[] = new IRubyObject[] { null };
        final ThreadContext localContext = context;

        if (args.length == 1) result[0] = args[0];

        callEach(runtime, context, self, new BlockCallback() {
            public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                if (localContext != ctx) {
                    throw runtime.newThreadError("Enumerable#inject cannot be parallelized");
                }
                result[0] = result[0] == null ? 
                        largs[0] : block.yield(ctx, runtime.newArray(result[0], largs[0]), null, null, true);

                return runtime.getNil();
            }
        });

        return result[0] == null ? runtime.getNil() : result[0];
    }

    @JRubyMethod(name = "partition", frame = true)
    public static IRubyObject partition(ThreadContext context, IRubyObject self, final Block block) {
        final Ruby runtime = self.getRuntime();
        final RubyArray arr_true = runtime.newArray();
        final RubyArray arr_false = runtime.newArray();

        callEach(runtime, context, self, new BlockCallback() {
            public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                if (block.yield(ctx, largs[0]).isTrue()) {
                    synchronized (arr_true) {
                        arr_true.append(largs[0]);
                    }
                } else {
                    synchronized (arr_false) {
                        arr_false.append(largs[0]);
                    }
                }

                return runtime.getNil();
            }
        });

        return runtime.newArray(arr_true, arr_false);
    }

    private static class EachWithIndex implements BlockCallback {
        private int index = 0;
        private final Block block;
        private final Ruby runtime;

        public EachWithIndex(ThreadContext ctx, Block block) {
            this.block = block;
            this.runtime = ctx.getRuntime();
        }

        public IRubyObject call(ThreadContext context, IRubyObject[] iargs, Block block) {
            this.block.call(context, new IRubyObject[] { runtime.newArray(iargs[0], runtime.newFixnum(index++)) });
            return runtime.getNil();            
        }
    }

    @JRubyMethod(name = "each_with_index", frame = true)
    public static IRubyObject each_with_index(ThreadContext context, IRubyObject self, Block block) {
        RuntimeHelpers.invoke(context, self, "each", CallBlock.newCallClosure(self, self.getRuntime().getEnumerable(), 
                Arity.noArguments(), new EachWithIndex(context, block), context));
        
        return self;
    }

    @JRubyMethod(name = {"include?", "member?"}, required = 1, frame = true)
    public static IRubyObject include_p(ThreadContext context, IRubyObject self, final IRubyObject arg) {
        final Ruby runtime = context.getRuntime();
        final ThreadContext localContext = context;

        try {
            callEach(runtime, context, self, new BlockCallback() {
                public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                    if (localContext != ctx) {
                        throw runtime.newThreadError("Enumerable#include?/member? cannot be parallelized");
                    }
                    if (RubyObject.equalInternal(ctx, largs[0], arg)) {
                        throw JumpException.SPECIAL_JUMP;
                    }
                    return runtime.getNil();
                }
            });
        } catch (JumpException.SpecialJump sj) {
            return runtime.getTrue();
        }
        
        return runtime.getFalse();
    }

    @JRubyMethod(name = "max", frame = true)
    public static IRubyObject max(ThreadContext context, IRubyObject self, final Block block) {
        final Ruby runtime = self.getRuntime();
        final IRubyObject result[] = new IRubyObject[] { null };
        final ThreadContext localContext = context;

        if (block.isGiven()) {
            callEach(runtime, context, self, new BlockCallback() {
                public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                    if (localContext != ctx) {
                        throw runtime.newThreadError("Enumerable#max{} cannot be parallelized");
                    }
                    if (result[0] == null || RubyComparable.cmpint(ctx, block.yield(ctx, 
                            runtime.newArray(largs[0], result[0])), largs[0], result[0]) > 0) {
                        result[0] = largs[0];
                    }
                    return runtime.getNil();
                }
            });
        } else {
            callEach(runtime, context, self, new BlockCallback() {
                public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                    synchronized (result) {
                        if (result[0] == null || RubyComparable.cmpint(ctx, largs[0].callMethod(ctx,
                                MethodIndex.OP_SPACESHIP, "<=>", result[0]), largs[0], result[0]) > 0) {
                            result[0] = largs[0];
                        }
                    }
                    return runtime.getNil();
                }
            });
        }
        
        return result[0] == null ? runtime.getNil() : result[0];
    }

    @JRubyMethod(name = "min", frame = true)
    public static IRubyObject min(ThreadContext context, IRubyObject self, final Block block) {
        final Ruby runtime = self.getRuntime();
        final IRubyObject result[] = new IRubyObject[] { null };
        final ThreadContext localContext = context;

        if (block.isGiven()) {
            callEach(runtime, context, self, new BlockCallback() {
                public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                    if (localContext != ctx) {
                        throw runtime.newThreadError("Enumerable#min{} cannot be parallelized");
                    }
                    if (result[0] == null || RubyComparable.cmpint(ctx, block.yield(ctx, 
                            runtime.newArray(largs[0], result[0])), largs[0], result[0]) < 0) {
                        result[0] = largs[0];
                    }
                    return runtime.getNil();
                }
            });
        } else {
            callEach(runtime, context, self, new BlockCallback() {
                public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                    synchronized (result) {
                        if (result[0] == null || RubyComparable.cmpint(ctx, largs[0].callMethod(ctx,
                                MethodIndex.OP_SPACESHIP, "<=>", result[0]), largs[0], result[0]) < 0) {
                            result[0] = largs[0];
                        }
                    }
                    return runtime.getNil();
                }
            });
        }
        
        return result[0] == null ? runtime.getNil() : result[0];
    }

    @JRubyMethod(name = "all?", frame = true)
    public static IRubyObject all_p(ThreadContext context, IRubyObject self, final Block block) {
        final Ruby runtime = self.getRuntime();
        final ThreadContext localContext = context;

        try {
            if (block.isGiven()) {
                callEach(runtime, context, self, new BlockCallback() {
                    public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                        if (localContext != ctx) {
                            throw runtime.newThreadError("Enumerable#all? cannot be parallelized");
                        }
                        if (!block.yield(ctx, largs[0]).isTrue()) {
                            throw JumpException.SPECIAL_JUMP;
                        }
                        return runtime.getNil();
                    }
                });
            } else {
                callEach(runtime, context, self, new BlockCallback() {
                    public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                        if (localContext != ctx) {
                            throw runtime.newThreadError("Enumerable#all? cannot be parallelized");
                        }
                        if (!largs[0].isTrue()) {
                            throw JumpException.SPECIAL_JUMP;
                        }
                        return runtime.getNil();
                    }
                });
            }
        } catch (JumpException.SpecialJump sj) {
            return runtime.getFalse();
        }

        return runtime.getTrue();
    }

    @JRubyMethod(name = "any?", frame = true)
    public static IRubyObject any_p(ThreadContext context, IRubyObject self, final Block block) {
        final Ruby runtime = self.getRuntime();
        final ThreadContext localContext = context;

        try {
            if (block.isGiven()) {
                callEach(runtime, context, self, new BlockCallback() {
                    public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                        if (localContext != ctx) {
                            throw runtime.newThreadError("Enumerable#any? cannot be parallelized");
                        }
                        if (block.yield(ctx, largs[0]).isTrue()) {
                            throw JumpException.SPECIAL_JUMP;
                        }
                        return runtime.getNil();
                    }
                });
            } else {
                callEach(runtime, context, self, new BlockCallback() {
                    public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                        if (localContext != ctx) {
                            throw runtime.newThreadError("Enumerable#any? cannot be parallelized");
                        }
                        if (largs[0].isTrue()) {
                            throw JumpException.SPECIAL_JUMP;
                        }
                        return runtime.getNil();
                    }
                });
            }
        } catch (JumpException.SpecialJump sj) {
            return runtime.getTrue();
        }

        return runtime.getFalse();
    }

    @JRubyMethod(name = "zip", rest = true, frame = true)
    public static IRubyObject zip(ThreadContext context, IRubyObject self, final IRubyObject[] args, final Block block) {
        final Ruby runtime = self.getRuntime();

        for (int i = 0; i < args.length; i++) {
            args[i] = TypeConverter.convertToType(args[i], runtime.getArray(), MethodIndex.TO_A, "to_a");
        }
        
        final int aLen = args.length + 1;

        if (block.isGiven()) {
            callEach(runtime, context, self, new BlockCallback() {
                AtomicInteger ix = new AtomicInteger(0);

                public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                    RubyArray array = runtime.newArray(aLen);
                    int myIx = ix.getAndIncrement();
                    array.append(largs[0]);
                    for (int i = 0, j = args.length; i < j; i++) {
                        array.append(((RubyArray) args[i]).entry(myIx));
                    }
                    block.yield(ctx, array);
                    return runtime.getNil();
                }
            });
            return runtime.getNil();
        } else {
            final RubyArray zip = runtime.newArray();
            callEach(runtime, context, self, new BlockCallback() {
                AtomicInteger ix = new AtomicInteger(0);

                public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                    RubyArray array = runtime.newArray(aLen);
                    array.append(largs[0]);
                    int myIx = ix.getAndIncrement();
                    for (int i = 0, j = args.length; i < j; i++) {
                        array.append(((RubyArray) args[i]).entry(myIx));
                    }
                    synchronized (zip) {
                        zip.append(array);
                    }
                    return runtime.getNil();
                }
            });
            return zip;
        }
    }

    @JRubyMethod(name = "group_by", frame = true)
    public static IRubyObject group_by(ThreadContext context, IRubyObject self, final Block block) {
        final Ruby runtime = self.getRuntime();
        final RubyHash result = new RubyHash(runtime);

        callEach(runtime, context, self, new BlockCallback() {
            public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                IRubyObject key = block.yield(ctx, largs[0]);
                synchronized (result) {
                    IRubyObject curr = result.fastARef(key);

                    if (curr == null) {
                        curr = runtime.newArray();
                        result.fastASet(key, curr);
                    }
                    curr.callMethod(ctx, MethodIndex.OP_LSHIFT, "<<", largs[0]);
                }
                return runtime.getNil();
            }
        });

        return result;
    }
    
    public static final class AppendBlockCallback implements BlockCallback {
        private Ruby runtime;
        private RubyArray result;

        public AppendBlockCallback(Ruby runtime, RubyArray result) {
            this.runtime = runtime;
            this.result = result;
        }
        
        public IRubyObject call(ThreadContext context, IRubyObject[] largs, Block blk) {
            result.append(largs[0]);
            
            return runtime.getNil();
        }
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2006 Michael Studman <me@michaelstudman.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;
import org.jruby.javasupport.util.RuntimeHelpers;
import org.jruby.runtime.Block;
import org.jruby.runtime.BlockCallback;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;

/**
 * Implementation of Ruby's Enumerator module.
 */
@JRubyModule(name="Enumerable::Enumerator", include="Enumerable")
public class RubyEnumerator extends RubyObject {
    /** target for each operation */
    private IRubyObject object;
    
    /** method to invoke for each operation */
    private IRubyObject method;
    
    /** args to each method */
    private IRubyObject[] methodArgs;
    
    private static ObjectAllocator ENUMERATOR_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return new RubyEnumerator(runtime, klass);
        }
    };

    public static void defineEnumerator(Ruby runtime) {
        RubyModule kernel = runtime.getKernel();
        kernel.defineAnnotatedMethod(RubyEnumerator.class, "obj_to_enum");

        RubyModule enm = runtime.getClassFromPath("Enumerable");
        enm.defineAnnotatedMethod(RubyEnumerator.class, "each_with_index");
        enm.defineAnnotatedMethod(RubyEnumerator.class, "each_slice");
        enm.defineAnnotatedMethod(RubyEnumerator.class, "enum_slice");
        enm.defineAnnotatedMethod(RubyEnumerator.class, "each_cons");
        enm.defineAnnotatedMethod(RubyEnumerator.class, "enum_cons");

        RubyClass enmr = enm.defineClassUnder("Enumerator", runtime.getObject(), ENUMERATOR_ALLOCATOR);

        enmr.includeModule(enm);

        enmr.defineAnnotatedMethod(RubyEnumerator.class, "initialize");
        enmr.defineAnnotatedMethod(RubyEnumerator.class, "each");

        runtime.setEnumerator(enmr);
    }

    @JRubyMethod(name = {"to_enum", "enum_for"}, optional = 1, rest = true, frame = true)
    public static IRubyObject obj_to_enum(ThreadContext context, IRubyObject self, IRubyObject[] args, Block block) {
        IRubyObject[] newArgs = new IRubyObject[args.length + 1];
        newArgs[0] = self;
        System.arraycopy(args, 0, newArgs, 1, args.length);

        return self.getRuntime().getEnumerator().callMethod(context, "new", newArgs);
    }

    private RubyEnumerator(Ruby runtime, RubyClass type) {
        super(runtime, type);
        object = method = runtime.getNil();
    }

    @JRubyMethod(name = "initialize", required = 1, rest = true, visibility = Visibility.PRIVATE)
    public IRubyObject initialize(IRubyObject[] args) {
        object = args[0];
        method = args.length > 1 ? args[1] : getRuntime().fastNewSymbol("each");
        if (args.length > 2) {
            methodArgs = new IRubyObject[Math.max(0, args.length - 2)];
            System.arraycopy(args, 2, methodArgs, 0, args.length - 2);
        } else {
            methodArgs = new IRubyObject[0];
        }
        return this;
    }

    /**
     * Send current block and supplied args to method on target. According to MRI
     * Block may not be given and "each" should just ignore it and call on through to
     * underlying method.
     */
    @JRubyMethod(name = "each", frame = true)
    public IRubyObject each(ThreadContext context, Block block) {
        return object.callMethod(context, method.asJavaString(), methodArgs, block);
    }

    @JRubyMethod(name = "enum_with_index")
    public static IRubyObject each_with_index(ThreadContext context, IRubyObject self) {
        IRubyObject enumerator = self.getRuntime().getEnumerator();
        return RuntimeHelpers.invoke(context, enumerator, "new", self, self.getRuntime().fastNewSymbol("each_with_index"));
    }

    @JRubyMethod(name = "each_slice", required = 1, frame = true)
    public static IRubyObject each_slice(ThreadContext context, IRubyObject self, IRubyObject arg, final Block block) {
        final int size = (int)RubyNumeric.num2long(arg);

        if (size <= 0) throw self.getRuntime().newArgumentError("invalid slice size");

        final Ruby runtime = self.getRuntime();
        final RubyArray result[] = new RubyArray[]{runtime.newArray(size)};

        RubyEnumerable.callEach(runtime, context, self, new BlockCallback() {
            public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                result[0].append(largs[0]);
                if (result[0].size() == size) {
                    block.yield(ctx, result[0]);
                    result[0] = runtime.newArray(size);
                }
                return runtime.getNil();
            }
        });

        if (result[0].size() > 0) block.yield(context, result[0]);
        return self.getRuntime().getNil();
    }

    @JRubyMethod(name = "each_cons", required = 1, frame = true)
    public static IRubyObject each_cons(ThreadContext context, IRubyObject self, IRubyObject arg, final Block block) {
        final int size = (int)RubyNumeric.num2long(arg);

        if (size <= 0) throw self.getRuntime().newArgumentError("invalid size");

        final Ruby runtime = self.getRuntime();
        final RubyArray result = runtime.newArray(size);

        RubyEnumerable.callEach(runtime, context, self, new BlockCallback() {
            public IRubyObject call(ThreadContext ctx, IRubyObject[] largs, Block blk) {
                if (result.size() == size) result.shift();
                result.append(largs[0]);
                if (result.size() == size) block.yield(ctx, result.aryDup());
                return runtime.getNil();
            }
        });

        return runtime.getNil();        
    }

    @JRubyMethod(name = "enum_slice", required = 1)
    public static IRubyObject enum_slice(ThreadContext context, IRubyObject self, IRubyObject arg) {
        IRubyObject enumerator = self.getRuntime().getEnumerator();
        return RuntimeHelpers.invoke(context, enumerator, "new", self, self.getRuntime().fastNewSymbol("each_slice"), arg);
    }

    @JRubyMethod(name = "enum_cons", required = 1)
    public static IRubyObject enum_cons(ThreadContext context, IRubyObject self, IRubyObject arg) {
        IRubyObject enumerator = self.getRuntime().getEnumerator();
        return RuntimeHelpers.invoke(context, enumerator, "new", self, self.getRuntime().fastNewSymbol("each_cons"), arg);
    }
}
package org.jruby;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;
import org.jruby.ext.posix.Passwd;
import org.jruby.ext.posix.Group;
import org.jruby.ext.posix.POSIX;
import org.jruby.ext.posix.util.Platform;
import org.jruby.runtime.Block;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;

@JRubyModule(name="Etc")
public class RubyEtc {
    public static RubyModule createEtcModule(Ruby runtime) {
        RubyModule etcModule = runtime.defineModule("Etc");

        runtime.setEtc(etcModule);
        
        etcModule.defineAnnotatedMethods(RubyEtc.class);
        
        definePasswdStruct(runtime);
        defineGroupStruct(runtime);
        
        return etcModule;
    }
    
    private static void definePasswdStruct(Ruby runtime) {
        IRubyObject[] args = new IRubyObject[] {
                runtime.newString("Passwd"),
                runtime.newSymbol("name"),
                runtime.newSymbol("passwd"),
                runtime.newSymbol("uid"),
                runtime.newSymbol("gid"),
                runtime.newSymbol("gecos"),
                runtime.newSymbol("dir"),
                runtime.newSymbol("shell"),
                runtime.newSymbol("change"),
                runtime.newSymbol("uclass"),
                runtime.newSymbol("expire")
        };
        
        runtime.setPasswdStruct(RubyStruct.newInstance(runtime.getStructClass(), args, Block.NULL_BLOCK));
    }

    private static void defineGroupStruct(Ruby runtime) {
        IRubyObject[] args = new IRubyObject[] {
                runtime.newString("Group"),
                runtime.newSymbol("name"),
                runtime.newSymbol("passwd"),
                runtime.newSymbol("gid"),
                runtime.newSymbol("mem")
        };
        
        runtime.setGroupStruct(RubyStruct.newInstance(runtime.getStructClass(), args, Block.NULL_BLOCK));
    }
    
    private static IRubyObject setupPasswd(Ruby runtime, Passwd passwd) {
        IRubyObject[] args = new IRubyObject[] {
                runtime.newString(passwd.getLoginName()),
                runtime.newString(passwd.getPassword()),
                runtime.newFixnum(passwd.getUID()),
                runtime.newFixnum(passwd.getGID()),
                runtime.newString(passwd.getGECOS()),
                runtime.newString(passwd.getHome()),
                runtime.newString(passwd.getShell()),
                runtime.newFixnum(passwd.getPasswdChangeTime()),
                runtime.newString(passwd.getAccessClass()),
                runtime.newFixnum(passwd.getExpire())

        };
        
        return RubyStruct.newStruct(runtime.getPasswdStruct(), args, Block.NULL_BLOCK);
    }

    
    private static IRubyObject setupGroup(Ruby runtime, Group group) {
        IRubyObject[] args = new IRubyObject[] {
                runtime.newString(group.getName()),
                runtime.newString(group.getPassword()),
                runtime.newFixnum(group.getGID()),
                intoStringArray(runtime, group.getMembers())
        };
        
        return RubyStruct.newStruct(runtime.getGroupStruct(), args, Block.NULL_BLOCK);
    }

    private static IRubyObject intoStringArray(Ruby runtime, String[] members) {
        IRubyObject[] arr = new IRubyObject[members.length];
        for(int i = 0; i<arr.length; i++) {
            arr[i] = runtime.newString(members[i]);
        }
        return runtime.newArrayNoCopy(arr);
    }


    @JRubyMethod(name = "getpwuid", optional=1, module = true)
    public static IRubyObject getpwuid(IRubyObject recv, IRubyObject[] args) {
        Ruby runtime = recv.getRuntime();
        POSIX posix = runtime.getPosix();
        int uid = args.length == 0 ? posix.getuid() : RubyNumeric.fix2int(args[0]);
        Passwd pwd = posix.getpwuid(uid);
        if(pwd == null) {
            if (Platform.IS_WINDOWS) {  // MRI behavior
                return recv.getRuntime().getNil();
            }
            throw runtime.newArgumentError("can't find user for " + uid);
        }
        return setupPasswd(runtime, pwd);
    }

    @JRubyMethod(name = "getpwnam", required=1, module = true)
    public static IRubyObject getpwnam(IRubyObject recv, IRubyObject name) {
        String nam = name.convertToString().toString();
        Passwd pwd = recv.getRuntime().getPosix().getpwnam(nam);
        if(pwd == null) {
            if (Platform.IS_WINDOWS) {  // MRI behavior
                return recv.getRuntime().getNil();
            }
            throw recv.getRuntime().newArgumentError("can't find user for " + nam);
        }
        return setupPasswd(recv.getRuntime(), pwd);
    }

    @JRubyMethod(name = "passwd", module = true, frame=true)
    public static IRubyObject passwd(IRubyObject recv, Block block) {
        Ruby runtime = recv.getRuntime();
        POSIX posix = runtime.getPosix();
        if(block.isGiven()) {
            ThreadContext context = runtime.getCurrentContext();
            posix.setpwent();
            Passwd pw;
            while((pw = posix.getpwent()) != null) {
                block.yield(context, setupPasswd(runtime, pw));
            }
            posix.endpwent();
        }

        Passwd pw = posix.getpwent();
        if (pw != null) {
            return setupPasswd(runtime, pw);
        } else {
            return runtime.getNil();
        }
    }

    @JRubyMethod(name = "getlogin", module = true)
    public static IRubyObject getlogin(IRubyObject recv) {
        Ruby runtime = recv.getRuntime();
        
        String login = runtime.getPosix().getlogin();
        
        if (login != null) {
            return runtime.newString(login);
        } else {
            return runtime.getNil();
        }
    }

    @JRubyMethod(name = "endpwent", module = true)
    public static IRubyObject endpwent(IRubyObject recv) {
        Ruby runtime = recv.getRuntime();
        runtime.getPosix().endpwent();
        return runtime.getNil();
    }

    @JRubyMethod(name = "setpwent", module = true)
    public static IRubyObject setpwent(IRubyObject recv) {
        Ruby runtime = recv.getRuntime();
        runtime.getPosix().setpwent();
        return runtime.getNil();
    }

    @JRubyMethod(name = "getpwent", module = true)
    public static IRubyObject getpwent(IRubyObject recv) {
        Ruby runtime = recv.getRuntime();
        Passwd passwd = runtime.getPosix().getpwent();
        if (passwd != null) {
            return setupPasswd(recv.getRuntime(), passwd);
        } else {
            return runtime.getNil();
        }
    }

    @JRubyMethod(name = "getgrnam", required=1, module = true)
    public static IRubyObject getgrnam(IRubyObject recv, IRubyObject name) {
        String nam = name.convertToString().toString();
        Group grp = recv.getRuntime().getPosix().getgrnam(nam);
        if(grp == null) {
            if (Platform.IS_WINDOWS) {  // MRI behavior
                return recv.getRuntime().getNil();
            }
            throw recv.getRuntime().newArgumentError("can't find group for " + nam);
        }
        return setupGroup(recv.getRuntime(), grp);
    }

    @JRubyMethod(name = "getgrgid", optional=1, module = true)
    public static IRubyObject getgrgid(IRubyObject recv, IRubyObject[] args) {
        Ruby runtime = recv.getRuntime();
        POSIX posix = runtime.getPosix();
        int gid = args.length == 0 ? posix.getgid() : RubyNumeric.fix2int(args[0]);
        Group gr = posix.getgrgid(gid);
        if(gr == null) {
            if (Platform.IS_WINDOWS) {  // MRI behavior
                return runtime.getNil();
            }
            throw runtime.newArgumentError("can't find group for " + gid);
        }
        return setupGroup(runtime, gr);
    }

    @JRubyMethod(name = "endgrent", module = true)
    public static IRubyObject endgrent(IRubyObject recv) {
        Ruby runtime = recv.getRuntime();
        runtime.getPosix().endgrent();
        return runtime.getNil();
    }

    @JRubyMethod(name = "setgrent", module = true)
    public static IRubyObject setgrent(IRubyObject recv) {
        Ruby runtime = recv.getRuntime();
        runtime.getPosix().setgrent();
        return runtime.getNil();
    }

    @JRubyMethod(name = "group", module = true, frame=true)
    public static IRubyObject group(IRubyObject recv, Block block) {
        Ruby runtime = recv.getRuntime();
        POSIX posix = runtime.getPosix();
        if(block.isGiven()) {
            ThreadContext context = runtime.getCurrentContext();
            posix.setgrent();
            Group gr;
            while((gr = posix.getgrent()) != null) {
                block.yield(context, setupGroup(runtime, gr));
            }
            posix.endgrent();
        }

        Group gr = posix.getgrent();
        if (gr != null) {
            return setupGroup(runtime, gr);
        } else {
            return runtime.getNil();
        }
    }

    @JRubyMethod(name = "getgrent", module = true)
    public static IRubyObject getgrent(IRubyObject recv) {
        Ruby runtime = recv.getRuntime();
        Group gr = runtime.getPosix().getgrent();
        if (gr != null) {
            return setupGroup(recv.getRuntime(), gr);
        } else {
            return runtime.getNil();
        }        
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2002-2006 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Joey Gibson <joey@joeygibson.com>
 * Copyright (C) 2004-2005 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2005 David Corbin <dcorbin@users.sf.net>
 * Copyright (C) 2006 Michael Studman <codehaus@michaelstudman.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.io.IOException;
import java.io.PrintStream;
import java.util.List;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;

import org.jruby.runtime.Block;
import org.jruby.runtime.Frame;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ObjectMarshal;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.builtin.Variable;
import org.jruby.runtime.component.VariableEntry;
import org.jruby.runtime.marshal.MarshalStream;
import org.jruby.runtime.marshal.UnmarshalStream;
import org.jruby.util.SafePropertyAccessor;

/**
 *
 * @author  jpetersen
 */
@JRubyClass(name="Exception")
public class RubyException extends RubyObject {
    private StackTraceElement[] backtraceFrames;
    private StackTraceElement[] javaStackTrace;
    private IRubyObject backtrace;
    public IRubyObject message;
    public static final int TRACE_HEAD = 8;
    public static final int TRACE_TAIL = 4;
    public static final int TRACE_MAX = TRACE_HEAD + TRACE_TAIL + 6;

    protected RubyException(Ruby runtime, RubyClass rubyClass) {
        this(runtime, rubyClass, null);
    }

    public RubyException(Ruby runtime, RubyClass rubyClass, String message) {
        super(runtime, rubyClass);
        
        this.message = message == null ? runtime.getNil() : runtime.newString(message);
    }
    
    private static ObjectAllocator EXCEPTION_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            RubyException instance = new RubyException(runtime, klass);
            
            // for future compatibility as constructors move toward not accepting metaclass?
            instance.setMetaClass(klass);
            
            return instance;
        }
    };
    
    private static final ObjectMarshal EXCEPTION_MARSHAL = new ObjectMarshal() {
        public void marshalTo(Ruby runtime, Object obj, RubyClass type,
                              MarshalStream marshalStream) throws IOException {
            RubyException exc = (RubyException)obj;
            
            marshalStream.registerLinkTarget(exc);
            List<Variable<IRubyObject>> attrs = exc.getVariableList();
            attrs.add(new VariableEntry<IRubyObject>(
                    "mesg", exc.message == null ? runtime.getNil() : exc.message));
            attrs.add(new VariableEntry<IRubyObject>("bt", exc.getBacktrace()));
            marshalStream.dumpVariables(attrs);
        }

        public Object unmarshalFrom(Ruby runtime, RubyClass type,
                                    UnmarshalStream unmarshalStream) throws IOException {
            RubyException exc = (RubyException)type.allocate();
            
            unmarshalStream.registerLinkTarget(exc);
            unmarshalStream.defaultVariablesUnmarshal(exc);
            
            exc.message = exc.removeInternalVariable("mesg");
            exc.set_backtrace(exc.removeInternalVariable("bt"));
            
            return exc;
        }
    };

    public static RubyClass createExceptionClass(Ruby runtime) {
        RubyClass exceptionClass = runtime.defineClass("Exception", runtime.getObject(), EXCEPTION_ALLOCATOR);
        runtime.setException(exceptionClass);

        exceptionClass.setMarshal(EXCEPTION_MARSHAL);
        exceptionClass.defineAnnotatedMethods(RubyException.class);

        return exceptionClass;
    }

    public static RubyException newException(Ruby runtime, RubyClass excptnClass, String msg) {
        return new RubyException(runtime, excptnClass, msg);
    }
    
    public void setBacktraceFrames(StackTraceElement[] backtraceFrames) {
        this.backtraceFrames = backtraceFrames;
        if (TRACE_TYPE == RAW ||
                TRACE_TYPE == RAW_FILTERED ||
                TRACE_TYPE == RUBY_COMPILED ||
                TRACE_TYPE == RUBY_HYBRID) {
            javaStackTrace = Thread.currentThread().getStackTrace();
        }
    }
    
    public static final int RAW = 0;
    public static final int RAW_FILTERED = 1;
    public static final int RUBY_FRAMED = 2;
    public static final int RUBY_COMPILED = 3;
    public static final int RUBY_HYBRID = 4;
    
    public static final int TRACE_TYPE;
    
    static {
        String style = SafePropertyAccessor.getProperty("jruby.backtrace.style", "ruby_framed").toLowerCase();
        
        if (style.equals("raw")) TRACE_TYPE = RAW;
        else if (style.equals("raw_filtered")) TRACE_TYPE = RAW_FILTERED;
        else if (style.equals("ruby_framed")) TRACE_TYPE = RUBY_FRAMED;
        else if (style.equals("ruby_compiled")) TRACE_TYPE = RUBY_COMPILED;
        else if (style.equals("ruby_hybrid")) TRACE_TYPE = RUBY_HYBRID;
        else TRACE_TYPE = RUBY_FRAMED;
    }
    
    public IRubyObject getBacktrace() {
        if (backtrace == null) {
            initBacktrace();
        }
        return backtrace;
    }
    
    public void initBacktrace() {
        switch (TRACE_TYPE) {
        case RAW:
            backtrace = ThreadContext.createRawBacktrace(getRuntime(), javaStackTrace, false);
            break;
        case RAW_FILTERED:
            backtrace = ThreadContext.createRawBacktrace(getRuntime(), javaStackTrace, true);
            break;
        case RUBY_FRAMED:
            backtrace = backtraceFrames == null ? getRuntime().getNil() : ThreadContext.createBacktraceFromFrames(getRuntime(), backtraceFrames);
            break;
        case RUBY_COMPILED:
            backtrace = ThreadContext.createRubyCompiledBacktrace(getRuntime(), javaStackTrace);
            break;
        case RUBY_HYBRID:
            backtrace = ThreadContext.createRubyHybridBacktrace(getRuntime(), backtraceFrames, javaStackTrace, getRuntime().getDebug().isTrue());
            break;
        }
    }

    @JRubyMethod(optional = 2, frame = true, visibility = Visibility.PRIVATE)
    public IRubyObject initialize(IRubyObject[] args, Block block) {
        if (args.length == 1) message = args[0];
        return this;
    }

    @JRubyMethod
    public IRubyObject backtrace() {
        return getBacktrace(); 
    }

    @JRubyMethod(required = 1)
    public IRubyObject set_backtrace(IRubyObject obj) {
        if (obj.isNil()) {
            backtrace = null;
        } else if (!isArrayOfStrings(obj)) {
            throw getRuntime().newTypeError("backtrace must be Array of String");
        } else {
            backtrace = (RubyArray) obj;
        }
        return backtrace();
    }
    
    @JRubyMethod(name = "exception", optional = 1, rest = true, meta = true)
    public static IRubyObject exception(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        return ((RubyClass) recv).newInstance(context, args, block);
    }

    @JRubyMethod(optional = 1)
    public RubyException exception(IRubyObject[] args) {
        switch (args.length) {
            case 0 :
                return this;
            case 1 :
                if(args[0] == this) {
                    return this;
                }
                RubyException ret = (RubyException)rbClone();
                ret.initialize(args, Block.NULL_BLOCK); // This looks wrong, but it's the way MRI does it.
                return ret;
            default :
                throw getRuntime().newArgumentError("Wrong argument count");
        }
    }

    @JRubyMethod
    public IRubyObject to_s() {
        if (message.isNil()) return getRuntime().newString(getMetaClass().getName());
        message.setTaint(isTaint());
        return message;
    }

    @JRubyMethod(name = {"to_str", "message"})
    public IRubyObject to_str(ThreadContext context) {
        return callMethod(context, MethodIndex.TO_S, "to_s");
    }

    /** inspects an object and return a kind of debug information
     * 
     *@return A RubyString containing the debug information.
     */
    @JRubyMethod
    public IRubyObject inspect(ThreadContext context) {
        RubyModule rubyClass = getMetaClass();
        RubyString exception = RubyString.objAsString(context, this);

        if (exception.getByteList().realSize == 0) return getRuntime().newString(rubyClass.getName());
        StringBuilder sb = new StringBuilder("#<");
        sb.append(rubyClass.getName()).append(": ").append(exception.getByteList()).append(">");
        return getRuntime().newString(sb.toString());
    }

    public void printBacktrace(PrintStream errorStream) {
        IRubyObject backtrace = callMethod(getRuntime().getCurrentContext(), "backtrace");
        boolean debug = getRuntime().getDebug().isTrue();
        if (!backtrace.isNil() && backtrace instanceof RubyArray) {
            IRubyObject[] elements = backtrace.convertToArray().toJavaArray();

            for (int i = 1; i < elements.length; i++) {
                IRubyObject stackTraceLine = elements[i];
                    if (stackTraceLine instanceof RubyString) {
                    printStackTraceLine(errorStream, stackTraceLine);
                }

                if (!debug && i == RubyException.TRACE_HEAD && elements.length > RubyException.TRACE_MAX) {
                    int hiddenLevels = elements.length - RubyException.TRACE_HEAD - RubyException.TRACE_TAIL;
                            errorStream.print("\t ... " + hiddenLevels + " levels...\n");
                    i = elements.length - RubyException.TRACE_TAIL;
                }
            }
        }
    }

    private void printStackTraceLine(PrintStream errorStream, IRubyObject stackTraceLine) {
            errorStream.print("\tfrom " + stackTraceLine + '\n');
    }
	
    private boolean isArrayOfStrings(IRubyObject backtrace) {
        if (!(backtrace instanceof RubyArray)) return false; 
            
        IRubyObject[] elements = ((RubyArray) backtrace).toJavaArray();
        
        for (int i = 0 ; i < elements.length ; i++) {
            if (!(elements[i] instanceof RubyString)) return false;
        }
            
        return true;
    }
}
/*
 ***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2002-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2003 Joey Gibson <joey@joeygibson.com>
 * Copyright (C) 2004-2007 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004-2007 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2006 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import org.jruby.util.io.OpenFile;
import org.jruby.util.io.ChannelDescriptor;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;

import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;
import org.jruby.ext.posix.util.Platform;
import org.jruby.runtime.Block;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;
import org.jruby.util.io.DirectoryAsFileException;
import org.jruby.util.io.Stream;
import org.jruby.util.io.ChannelStream;
import org.jruby.util.io.ModeFlags;
import org.jruby.util.JRubyFile;
import org.jruby.util.TypeConverter;
import org.jruby.util.io.BadDescriptorException;
import org.jruby.util.io.FileExistsException;
import org.jruby.util.io.InvalidValueException;
import org.jruby.util.io.PipeException;

/**
 * Ruby File class equivalent in java.
 **/
@JRubyClass(name="File", parent="IO", include="FileTest")
public class RubyFile extends RubyIO {
    private static final long serialVersionUID = 1L;
    
    public static final int LOCK_SH = 1;
    public static final int LOCK_EX = 2;
    public static final int LOCK_NB = 4;
    public static final int LOCK_UN = 8;

    private static final int FNM_NOESCAPE = 1;
    private static final int FNM_PATHNAME = 2;
    private static final int FNM_DOTMATCH = 4;
    private static final int FNM_CASEFOLD = 8;
    private static final int FNM_SYSCASE;

    static {
        if (Platform.IS_WINDOWS) {
            FNM_SYSCASE = FNM_CASEFOLD;
        } else {
            FNM_SYSCASE = 0;
        }
    }

    private static boolean startsWithDriveLetterOnWindows(String path) {
        return (path != null)
            && Platform.IS_WINDOWS && 
            ((path.length()>1 && path.charAt(0) == '/') ? 
             (path.length() > 2
              && isWindowsDriveLetter(path.charAt(1))
              && path.charAt(2) == ':') : 
             (path.length() > 1
              && isWindowsDriveLetter(path.charAt(0))
              && path.charAt(1) == ':'));
    }
    // adjusts paths started with '/' or '\\', on windows.
    static String adjustRootPathOnWindows(Ruby runtime, String path, String dir) {
        if (path == null) return path;
        if (Platform.IS_WINDOWS) {
            // MRI behavior on Windows: it treats '/' as a root of
            // a current drive (but only if SINGLE slash is present!):
            // E.g., if current work directory is
            // 'D:/home/directory', then '/' means 'D:/'.
            //
            // Basically, '/path' is treated as a *RELATIVE* path,
            // relative to the current drive. '//path' is treated
            // as absolute one.
            if ((path.startsWith("/") && !(path.length()>2 && path.charAt(2) == ':')) || path.startsWith("\\")) {
                if (path.length() > 1 && (path.charAt(1) == '/' || path.charAt(1) == '\\')) {
                    return path;
                }
                
                // First try to use drive letter from supplied dir value,
                // then try current work dir.
                if (!startsWithDriveLetterOnWindows(dir)) {
                    dir = runtime.getCurrentDirectory();
                }
                if (dir.length() >= 2) {
                    path = dir.substring(0, 2) + path;
                }
            } else if (startsWithDriveLetterOnWindows(path) && path.length() == 2) {
               // compensate for missing slash after drive letter on windows
                path += "/";
            }
        }
        return path;
    }

    protected String path;
    private FileLock currentLock;
    
    public RubyFile(Ruby runtime, RubyClass type) {
        super(runtime, type);
    }
    
    // XXX This constructor is a hack to implement the __END__ syntax.
    //     Converting a reader back into an InputStream doesn't generally work.
    public RubyFile(Ruby runtime, String path, final Reader reader) {
        this(runtime, path, new InputStream() {
            public int read() throws IOException {
                return reader.read();
            }
        });
    }
    
    public RubyFile(Ruby runtime, String path, InputStream in) {
        super(runtime, runtime.getFile());
        this.path = path;
        try {
            this.openFile.setMainStream(new ChannelStream(runtime, new ChannelDescriptor(Channels.newChannel(in), getNewFileno(), new FileDescriptor())));
        } catch (InvalidValueException ex) {
            throw runtime.newErrnoEINVALError();
        }
        this.openFile.setMode(openFile.getMainStream().getModes().getOpenFileFlags());
        registerDescriptor(openFile.getMainStream().getDescriptor());
    }

    private static ObjectAllocator FILE_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            RubyFile instance = new RubyFile(runtime, klass);
            
            instance.setMetaClass(klass);
            
            return instance;
        }
    };

    @JRubyModule(name="File::Constants")
    public static class Constants {}
    
    public static RubyClass createFileClass(Ruby runtime) {
        RubyClass fileClass = runtime.defineClass("File", runtime.getIO(), FILE_ALLOCATOR);
        runtime.setFile(fileClass);
        RubyString separator = runtime.newString("/");
        ThreadContext context = runtime.getCurrentContext();
        
        fileClass.kindOf = new RubyModule.KindOf() {
            @Override
            public boolean isKindOf(IRubyObject obj, RubyModule type) {
                return obj instanceof RubyFile;
            }
        };

        separator.freeze(context);
        fileClass.defineConstant("SEPARATOR", separator);
        fileClass.defineConstant("Separator", separator);
        
        if (File.separatorChar == '\\') {
            RubyString altSeparator = runtime.newString("\\");
            altSeparator.freeze(context);
            fileClass.defineConstant("ALT_SEPARATOR", altSeparator);
        } else {
            fileClass.defineConstant("ALT_SEPARATOR", runtime.getNil());
        }
        
        RubyString pathSeparator = runtime.newString(File.pathSeparator);
        pathSeparator.freeze(context);
        fileClass.defineConstant("PATH_SEPARATOR", pathSeparator);

        // TODO: why are we duplicating the constants here, and then in
        // File::Constants below? File::Constants is included in IO.

        // TODO: These were missing, so we're not handling them elsewhere?
        // FIXME: The old value, 32786, didn't match what IOModes expected, so I reference
        // the constant here. THIS MAY NOT BE THE CORRECT VALUE.
        fileClass.fastSetConstant("BINARY", runtime.newFixnum(ModeFlags.BINARY));
        fileClass.fastSetConstant("FNM_NOESCAPE", runtime.newFixnum(FNM_NOESCAPE));
        fileClass.fastSetConstant("FNM_CASEFOLD", runtime.newFixnum(FNM_CASEFOLD));
        fileClass.fastSetConstant("FNM_SYSCASE", runtime.newFixnum(FNM_SYSCASE));
        fileClass.fastSetConstant("FNM_DOTMATCH", runtime.newFixnum(FNM_DOTMATCH));
        fileClass.fastSetConstant("FNM_PATHNAME", runtime.newFixnum(FNM_PATHNAME));
        
        // Create constants for open flags
        fileClass.fastSetConstant("RDONLY", runtime.newFixnum(ModeFlags.RDONLY));
        fileClass.fastSetConstant("WRONLY", runtime.newFixnum(ModeFlags.WRONLY));
        fileClass.fastSetConstant("RDWR", runtime.newFixnum(ModeFlags.RDWR));
        fileClass.fastSetConstant("CREAT", runtime.newFixnum(ModeFlags.CREAT));
        fileClass.fastSetConstant("EXCL", runtime.newFixnum(ModeFlags.EXCL));
        fileClass.fastSetConstant("NOCTTY", runtime.newFixnum(ModeFlags.NOCTTY));
        fileClass.fastSetConstant("TRUNC", runtime.newFixnum(ModeFlags.TRUNC));
        fileClass.fastSetConstant("APPEND", runtime.newFixnum(ModeFlags.APPEND));
        fileClass.fastSetConstant("NONBLOCK", runtime.newFixnum(ModeFlags.NONBLOCK));
        
        // Create constants for flock
        fileClass.fastSetConstant("LOCK_SH", runtime.newFixnum(RubyFile.LOCK_SH));
        fileClass.fastSetConstant("LOCK_EX", runtime.newFixnum(RubyFile.LOCK_EX));
        fileClass.fastSetConstant("LOCK_NB", runtime.newFixnum(RubyFile.LOCK_NB));
        fileClass.fastSetConstant("LOCK_UN", runtime.newFixnum(RubyFile.LOCK_UN));
        
        // Create Constants class
        RubyModule constants = fileClass.defineModuleUnder("Constants");
        
        // TODO: These were missing, so we're not handling them elsewhere?
        constants.fastSetConstant("BINARY", runtime.newFixnum(ModeFlags.BINARY));
        constants.fastSetConstant("SYNC", runtime.newFixnum(0x1000));
        constants.fastSetConstant("FNM_NOESCAPE", runtime.newFixnum(FNM_NOESCAPE));
        constants.fastSetConstant("FNM_CASEFOLD", runtime.newFixnum(FNM_CASEFOLD));
        constants.fastSetConstant("FNM_SYSCASE", runtime.newFixnum(FNM_SYSCASE));
        constants.fastSetConstant("FNM_DOTMATCH", runtime.newFixnum(FNM_DOTMATCH));
        constants.fastSetConstant("FNM_PATHNAME", runtime.newFixnum(FNM_PATHNAME));
        
        // Create constants for open flags
        constants.fastSetConstant("RDONLY", runtime.newFixnum(ModeFlags.RDONLY));
        constants.fastSetConstant("WRONLY", runtime.newFixnum(ModeFlags.WRONLY));
        constants.fastSetConstant("RDWR", runtime.newFixnum(ModeFlags.RDWR));
        constants.fastSetConstant("CREAT", runtime.newFixnum(ModeFlags.CREAT));
        constants.fastSetConstant("EXCL", runtime.newFixnum(ModeFlags.EXCL));
        constants.fastSetConstant("NOCTTY", runtime.newFixnum(ModeFlags.NOCTTY));
        constants.fastSetConstant("TRUNC", runtime.newFixnum(ModeFlags.TRUNC));
        constants.fastSetConstant("APPEND", runtime.newFixnum(ModeFlags.APPEND));
        constants.fastSetConstant("NONBLOCK", runtime.newFixnum(ModeFlags.NONBLOCK));
        
        // Create constants for flock
        constants.fastSetConstant("LOCK_SH", runtime.newFixnum(RubyFile.LOCK_SH));
        constants.fastSetConstant("LOCK_EX", runtime.newFixnum(RubyFile.LOCK_EX));
        constants.fastSetConstant("LOCK_NB", runtime.newFixnum(RubyFile.LOCK_NB));
        constants.fastSetConstant("LOCK_UN", runtime.newFixnum(RubyFile.LOCK_UN));
        
        // File::Constants module is included in IO.
        runtime.getIO().includeModule(constants);

        runtime.getFileTest().extend_object(fileClass);
        
        fileClass.defineAnnotatedMethods(RubyFile.class);
        
        return fileClass;
    }
    
    @JRubyMethod
    @Override
    public IRubyObject close() {
        // Make sure any existing lock is released before we try and close the file
        if (currentLock != null) {
            try {
                currentLock.release();
            } catch (IOException e) {
                throw getRuntime().newIOError(e.getMessage());
            }
        }
        return super.close();
    }

    @JRubyMethod(required = 1)
    public IRubyObject flock(ThreadContext context, IRubyObject lockingConstant) {
        // TODO: port exact behavior from MRI, and move most locking logic into ChannelDescriptor
        // TODO: for all LOCK_NB cases, return false if they would block
        ChannelDescriptor descriptor = openFile.getMainStream().getDescriptor();
        
        // null channel always succeeds for all locking operations
        if (descriptor.isNull()) return RubyFixnum.zero(context.getRuntime());
        
        FileChannel fileChannel = (FileChannel)descriptor.getChannel();
        int lockMode = RubyNumeric.num2int(lockingConstant);

        // Exclusive locks in Java require the channel to be writable, otherwise
        // an exception is thrown (terminating JRuby execution).
        // But flock behavior of MRI is that it allows
        // exclusive locks even on non-writable file. So we convert exclusive
        // lock to shared lock if the channel is not writable, to better match
        // the MRI behavior.
        if (!openFile.isWritable() && (lockMode & LOCK_EX) > 0) {
            lockMode = (lockMode ^ LOCK_EX) | LOCK_SH;
        }

        try {
            switch (lockMode) {
                case LOCK_UN:
                case LOCK_UN | LOCK_NB:
                    if (currentLock != null) {
                        currentLock.release();
                        currentLock = null;

                        return RubyFixnum.zero(context.getRuntime());
                    }
                    break;
                case LOCK_EX:
                    if (currentLock != null) {
                        currentLock.release();
                        currentLock = null;
                    }
                    currentLock = fileChannel.lock();
                    if (currentLock != null) {
                        return RubyFixnum.zero(context.getRuntime());
                    }

                    break;
                case LOCK_EX | LOCK_NB:
                    if (currentLock != null) {
                        currentLock.release();
                        currentLock = null;
                    }
                    currentLock = fileChannel.tryLock();
                    if (currentLock != null) {
                        return RubyFixnum.zero(context.getRuntime());
                    }

                    break;
                case LOCK_SH:
                    if (currentLock != null) {
                        currentLock.release();
                        currentLock = null;
                    }

                    currentLock = fileChannel.lock(0L, Long.MAX_VALUE, true);
                    if (currentLock != null) {
                        return RubyFixnum.zero(context.getRuntime());
                    }

                    break;
                case LOCK_SH | LOCK_NB:
                    if (currentLock != null) {
                        currentLock.release();
                        currentLock = null;
                    }

                    currentLock = fileChannel.tryLock(0L, Long.MAX_VALUE, true);
                    if (currentLock != null) {
                        return RubyFixnum.zero(context.getRuntime());
                    }

                    break;
                default:
            }
        } catch (IOException ioe) {
            if (context.getRuntime().getDebug().isTrue()) {
                ioe.printStackTrace(System.err);
            }
            // Return false here
        } catch (java.nio.channels.OverlappingFileLockException ioe) {
            if (context.getRuntime().getDebug().isTrue()) {
                ioe.printStackTrace(System.err);
            }
            // Return false here
        }

        return context.getRuntime().getFalse();
    }

    @JRubyMethod(required = 1, optional = 2, frame = true, visibility = Visibility.PRIVATE)
    @Override
    public IRubyObject initialize(IRubyObject[] args, Block block) {
        if (openFile == null) {
            throw getRuntime().newRuntimeError("reinitializing File");
        }
        
        if (args.length > 0 && args.length < 3) {
            IRubyObject fd = TypeConverter.convertToTypeWithCheck(args[0], getRuntime().getFixnum(), MethodIndex.TO_INT, "to_int");
            if (!fd.isNil()) {
                args[0] = fd;
                return super.initialize(args, block);
            }
        }

        return openFile(args);
    }
    
    private IRubyObject openFile(IRubyObject args[]) {
        IRubyObject filename = args[0].convertToString();
        getRuntime().checkSafeString(filename);
        
        path = filename.convertToString().getUnicodeValue();
        
        String modeString;
        ModeFlags modes;
        int perm;
        
        try {
            if ((args.length > 1 && args[1] instanceof RubyFixnum) || (args.length > 2 && !args[2].isNil())) {
                if (args[1] instanceof RubyFixnum) {
                    modes = new ModeFlags(RubyNumeric.num2int(args[1]));
                } else {
                    modeString = args[1].convertToString().toString();
                    modes = getIOModes(getRuntime(), modeString);
                }
                if (args.length > 2 && !args[2].isNil()) {
                    perm = RubyNumeric.num2int(args[2]);
                } else {
                    perm = 438; // 0666
                }

                sysopenInternal(path, modes, perm);
            } else {
                modeString = "r";
                if (args.length > 1) {
                    if (!args[1].isNil()) {
                        modeString = args[1].convertToString().toString();
                    }
                }
                openInternal(path, modeString);
            }
        } catch (InvalidValueException ex) {
            throw getRuntime().newErrnoEINVALError();
        } finally {}
        
        return this;
    }
    
    private void sysopenInternal(String path, ModeFlags modes, int perm) throws InvalidValueException {
        openFile = new OpenFile();
        
        openFile.setPath(path);
        openFile.setMode(modes.getOpenFileFlags());
        
        ChannelDescriptor descriptor = sysopen(path, modes, perm);
        openFile.setMainStream(fdopen(descriptor, modes));
        
        registerDescriptor(descriptor);
    }
    
    private void openInternal(String path, String modeString) throws InvalidValueException {
        openFile = new OpenFile();

        openFile.setMode(getIOModes(getRuntime(), modeString).getOpenFileFlags());
        openFile.setPath(path);
        openFile.setMainStream(fopen(path, modeString));
        
        registerDescriptor(openFile.getMainStream().getDescriptor());
    }
    
    private ChannelDescriptor sysopen(String path, ModeFlags modes, int perm) throws InvalidValueException {
        try {
            ChannelDescriptor descriptor = ChannelDescriptor.open(
                    getRuntime().getCurrentDirectory(),
                    path,
                    modes,
                    perm,
                    getRuntime().getPosix());

            // TODO: check if too many open files, GC and try again

            return descriptor;
        } catch (FileNotFoundException fnfe) {
            throw getRuntime().newErrnoENOENTError();
        } catch (DirectoryAsFileException dafe) {
            throw getRuntime().newErrnoEISDirError();
        } catch (FileExistsException fee) {
            throw getRuntime().newErrnoEEXISTError("file exists: " + path);
        } catch (IOException ioe) {
            throw getRuntime().newIOErrorFromException(ioe);
        }
    }
    
    private Stream fopen(String path, String modeString) {
        try {
            Stream stream = ChannelStream.fopen(
                    getRuntime(),
                    path,
                    getIOModes(getRuntime(), modeString));
            
            if (stream == null) {
                // TODO
    //            if (errno == EMFILE || errno == ENFILE) {
    //                rb_gc();
    //                file = fopen(fname, mode);
    //            }
    //            if (!file) {
    //                rb_sys_fail(fname);
    //            }
            }

            // Do we need to be in SETVBUF mode for buffering to make sense? This comes up elsewhere.
    //    #ifdef USE_SETVBUF
    //        if (setvbuf(file, NULL, _IOFBF, 0) != 0)
    //            rb_warn("setvbuf() can't be honoured for %s", fname);
    //    #endif
    //    #ifdef __human68k__
    //        fmode(file, _IOTEXT);
    //    #endif
            return stream;
        } catch (BadDescriptorException e) {
            throw getRuntime().newErrnoEBADFError();
        } catch (FileNotFoundException ex) {
            // FNFException can be thrown in both cases, when the file
            // is not found, or when permission is denied.
            if (Ruby.isSecurityRestricted() || new File(path).exists()) {
                throw getRuntime().newErrnoEACCESError(
                        "Permission denied - " + path);
            }
            throw getRuntime().newErrnoENOENTError(
                    "File not found - " + path);
        } catch (DirectoryAsFileException ex) {
            throw getRuntime().newErrnoEISDirError();
        } catch (FileExistsException ex) {
            throw getRuntime().newErrnoEEXISTError(path);
        } catch (IOException ex) {
            throw getRuntime().newIOErrorFromException(ex);
        } catch (InvalidValueException ex) {
            throw getRuntime().newErrnoEINVALError();
        } catch (PipeException ex) {
            throw getRuntime().newErrnoEPIPEError();
        }
    }

    @JRubyMethod(required = 1)
    public IRubyObject chmod(ThreadContext context, IRubyObject arg) {
        int mode = (int) arg.convertToInteger().getLongValue();

        if (!new File(path).exists()) {
            throw context.getRuntime().newErrnoENOENTError("No such file or directory - " + path);
        }

        return context.getRuntime().newFixnum(context.getRuntime().getPosix().chmod(path, mode));
    }

    @JRubyMethod(required = 2)
    public IRubyObject chown(ThreadContext context, IRubyObject arg1, IRubyObject arg2) {
        int owner = -1;
        if (!arg1.isNil()) {
            owner = RubyNumeric.num2int(arg1);
        }

        int group = -1;
        if (!arg2.isNil()) {
            group = RubyNumeric.num2int(arg2);
        }

        if (!new File(path).exists()) {
            throw context.getRuntime().newErrnoENOENTError("No such file or directory - " + path);
        }

        return context.getRuntime().newFixnum(context.getRuntime().getPosix().chown(path, owner, group));
    }

    @JRubyMethod
    public IRubyObject atime(ThreadContext context) {
        return context.getRuntime().newFileStat(path, false).atime();
    }

    @JRubyMethod
    public IRubyObject ctime(ThreadContext context) {
        return context.getRuntime().newFileStat(path, false).ctime();
    }

    @JRubyMethod(required = 1)
    public IRubyObject lchmod(ThreadContext context, IRubyObject arg) {
        int mode = (int) arg.convertToInteger().getLongValue();

        if (!new File(path).exists()) {
            throw context.getRuntime().newErrnoENOENTError("No such file or directory - " + path);
        }

        return context.getRuntime().newFixnum(context.getRuntime().getPosix().lchmod(path, mode));
    }

    // TODO: this method is not present in MRI!
    @JRubyMethod(required = 2)
    public IRubyObject lchown(ThreadContext context, IRubyObject arg1, IRubyObject arg2) {
        int owner = -1;
        if (!arg1.isNil()) {
            owner = RubyNumeric.num2int(arg1);
        }

        int group = -1;
        if (!arg2.isNil()) {
            group = RubyNumeric.num2int(arg2);
        }

        if (!new File(path).exists()) {
            throw context.getRuntime().newErrnoENOENTError("No such file or directory - " + path);
        }

        return context.getRuntime().newFixnum(context.getRuntime().getPosix().lchown(path, owner, group));
    }

    @JRubyMethod
    public IRubyObject lstat(ThreadContext context) {
        return context.getRuntime().newFileStat(path, true);
    }
    
    @JRubyMethod
    public IRubyObject mtime(ThreadContext context) {
        return getLastModified(context.getRuntime(), path);
    }

    @JRubyMethod
    public RubyString path(ThreadContext context) {
        return context.getRuntime().newString(path);
    }

    @JRubyMethod
    @Override
    public IRubyObject stat(ThreadContext context) {
        openFile.checkClosed(context.getRuntime());
        return context.getRuntime().newFileStat(path, false);
    }

    @JRubyMethod(required = 1)
    public IRubyObject truncate(ThreadContext context, IRubyObject arg) {
        RubyInteger newLength = arg.convertToInteger();
        if (newLength.getLongValue() < 0) {
            throw context.getRuntime().newErrnoEINVALError("invalid argument: " + path);
        }
        try {
            openFile.checkWritable(context.getRuntime());
            openFile.getMainStream().ftruncate(newLength.getLongValue());
        } catch (BadDescriptorException e) {
            throw context.getRuntime().newErrnoEBADFError();
        } catch (PipeException e) {
            throw context.getRuntime().newErrnoESPIPEError();
        } catch (InvalidValueException ex) {
            throw context.getRuntime().newErrnoEINVALError();
        } catch (IOException e) {
            // Should we do anything?
        }

        return RubyFixnum.zero(context.getRuntime());
    }

    @Override
    public String toString() {
        return "RubyFile(" + path + ", " + openFile.getMode() + ", " + openFile.getMainStream().getDescriptor().getFileno() + ")";
    }

    // TODO: This is also defined in the MetaClass too...Consolidate somewhere.
    private static ModeFlags getModes(Ruby runtime, IRubyObject object) throws InvalidValueException {
        if (object instanceof RubyString) {
            return getIOModes(runtime, ((RubyString) object).toString());
        } else if (object instanceof RubyFixnum) {
            return new ModeFlags(((RubyFixnum) object).getLongValue());
        }

        throw runtime.newTypeError("Invalid type for modes");
    }

    @JRubyMethod
    @Override
    public IRubyObject inspect() {
        StringBuilder val = new StringBuilder();
        val.append("#<File:").append(path);
        if(!openFile.isOpen()) {
            val.append(" (closed)");
        }
        val.append(">");
        return getRuntime().newString(val.toString());
    }
    
    /* File class methods */
    
    @JRubyMethod(required = 1, optional = 1, meta = true)
    public static IRubyObject basename(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        String name = RubyString.stringValue(args[0]).toString();

        // MRI-compatible basename handling for windows drive letter paths
        if (Platform.IS_WINDOWS) {
            if (name.length() > 1 && name.charAt(1) == ':' && Character.isLetter(name.charAt(0))) {
                switch (name.length()) {
                case 2:
                    return RubyString.newEmptyString(context.getRuntime()).infectBy(args[0]);
                case 3:
                    return context.getRuntime().newString(name.substring(2)).infectBy(args[0]);
                default:
                    switch (name.charAt(2)) {
                    case '/':
                    case '\\':
                        break;
                    default:
                        // strip c: away from relative-pathed name
                        name = name.substring(2);
                        break;
                    }
                    break;
                }
            }
        }

        while (name.length() > 1 && name.charAt(name.length() - 1) == '/') {
            name = name.substring(0, name.length() - 1);
        }
        
        // Paths which end in "/" or "\\" must be stripped off.
        int slashCount = 0;
        int length = name.length();
        for (int i = length - 1; i >= 0; i--) {
            char c = name.charAt(i);
            if (c != '/' && c != '\\') {
                break;
            }
            slashCount++;
        }
        if (slashCount > 0 && length > 1) {
            name = name.substring(0, name.length() - slashCount);
        }
        
        int index = name.lastIndexOf('/');
        if (index == -1) {
            // XXX actually only on windows...
            index = name.lastIndexOf('\\');
        }
        
        if (!name.equals("/") && index != -1) {
            name = name.substring(index + 1);
        }
        
        if (args.length == 2) {
            String ext = RubyString.stringValue(args[1]).toString();
            if (".*".equals(ext)) {
                index = name.lastIndexOf('.');
                if (index > 0) {  // -1 no match; 0 it is dot file not extension
                    name = name.substring(0, index);
                }
            } else if (name.endsWith(ext)) {
                name = name.substring(0, name.length() - ext.length());
            }
        }
        return context.getRuntime().newString(name).infectBy(args[0]);
    }

    @JRubyMethod(required = 2, rest = true, meta = true)
    public static IRubyObject chmod(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        Ruby runtime = context.getRuntime();
        
        int count = 0;
        RubyInteger mode = args[0].convertToInteger();
        for (int i = 1; i < args.length; i++) {
            IRubyObject filename = args[i];
            
            if (!RubyFileTest.exist_p(filename, filename.convertToString()).isTrue()) {
                throw runtime.newErrnoENOENTError("No such file or directory - " + filename);
            }
            
            boolean result = 0 == runtime.getPosix().chmod(filename.toString(), (int)mode.getLongValue());
            if (result) {
                count++;
            }
        }
        
        return runtime.newFixnum(count);
    }
    
    @JRubyMethod(required = 3, rest = true, meta = true)
    public static IRubyObject chown(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        Ruby runtime = context.getRuntime();

        int count = 0;
        int owner = -1;
        if (!args[0].isNil()) {
            owner = RubyNumeric.num2int(args[0]);
        }

        int group = -1;
        if (!args[1].isNil()) {
            group = RubyNumeric.num2int(args[1]);
        }
        for (int i = 2; i < args.length; i++) {
            IRubyObject filename = args[i];
            
            if (!RubyFileTest.exist_p(filename, filename.convertToString()).isTrue()) {
                throw runtime.newErrnoENOENTError("No such file or directory - " + filename);
            }
            
            boolean result = 0 == runtime.getPosix().chown(filename.toString(), owner, group);
            if (result) {
                count++;
            }
        }
        
        return runtime.newFixnum(count);
    }
    
    @JRubyMethod(required = 1, meta = true)
    public static IRubyObject dirname(ThreadContext context, IRubyObject recv, IRubyObject arg) {
        RubyString filename = RubyString.stringValue(arg);
        String jfilename = filename.toString();
        String name = jfilename.replace('\\', '/');
        int minPathLength = 1;
        boolean trimmedSlashes = false;

        boolean startsWithDriveLetterOnWindows = startsWithDriveLetterOnWindows(name);

        if (startsWithDriveLetterOnWindows) {
            minPathLength = 3;
        }

        while (name.length() > minPathLength && name.charAt(name.length() - 1) == '/') {
            trimmedSlashes = true;
            name = name.substring(0, name.length() - 1);
        }

        String result;
        if (startsWithDriveLetterOnWindows && name.length() == 2) {
            if (trimmedSlashes) {
                // C:\ is returned unchanged
                result = jfilename.substring(0, 3);
            } else {
                result = jfilename.substring(0, 2) + '.';
            }
        } else {
            //TODO deal with UNC names
            int index = name.lastIndexOf('/');
            if (index == -1) {
                if (startsWithDriveLetterOnWindows) {
                    return context.getRuntime().newString(jfilename.substring(0, 2) + ".");
                } else {
                    return context.getRuntime().newString(".");
                }
            }
            if (index == 0) return context.getRuntime().newString("/");

            if (startsWithDriveLetterOnWindows && index == 2) {
                // Include additional path separator
                // (so that dirname of "C:\file.txt" is  "C:\", not "C:")
                index++;
            }

            result = jfilename.substring(0, index);
        }

        char endChar;
        // trim trailing slashes
        while (result.length() > minPathLength) {
            endChar = result.charAt(result.length() - 1);
            if (endChar == '/' || endChar == '\\') {
                result = result.substring(0, result.length() - 1);
            } else {
                break;
            }
        }

        return context.getRuntime().newString(result).infectBy(filename);
    }

    private static boolean isWindowsDriveLetter(char c) {
           return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
    }


    /**
     * Returns the extension name of the file. An empty string is returned if 
     * the filename (not the entire path) starts or ends with a dot.
     * @param recv
     * @param arg Path to get extension name of
     * @return Extension, including the dot, or an empty string
     */
    @JRubyMethod(required = 1, meta = true)
    public static IRubyObject extname(ThreadContext context, IRubyObject recv, IRubyObject arg) {
        IRubyObject baseFilename = basename(context, recv, new IRubyObject[]{arg});
        String filename = RubyString.stringValue(baseFilename).toString();
        String result = "";

        int dotIndex = filename.lastIndexOf(".");
        if (dotIndex > 0 && dotIndex != (filename.length() - 1)) {
            // Dot is not at beginning and not at end of filename. 
            result = filename.substring(dotIndex);
        }

        return context.getRuntime().newString(result);
    }

    /**
     * Converts a pathname to an absolute pathname. Relative paths are 
     * referenced from the current working directory of the process unless 
     * a second argument is given, in which case it will be used as the 
     * starting point. If the second argument is also relative, it will 
     * first be converted to an absolute pathname.
     * @param recv
     * @param args 
     * @return Resulting absolute path as a String
     */
    @JRubyMethod(required = 1, optional = 1, meta = true)
    public static IRubyObject expand_path(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        Ruby runtime = context.getRuntime();
        
        String relativePath = RubyString.stringValue(args[0]).toString();

        boolean isAbsoluteWithFilePrefix = relativePath.startsWith("file:");

        String cwd = null;
        
        // Handle ~user paths 
        relativePath = expandUserPath(context, relativePath);
        
        // If there's a second argument, it's the path to which the first 
        // argument is relative.
        if (args.length == 2 && !args[1].isNil()) {
            
            String cwdArg = RubyString.stringValue(args[1]).toString();
            
            // Handle ~user paths.
            cwd = expandUserPath(context, cwdArg);

            cwd = adjustRootPathOnWindows(runtime, cwd, null);

            boolean startsWithSlashNotOnWindows = (cwd != null)
                    && !Platform.IS_WINDOWS && cwd.length() > 0
                    && cwd.charAt(0) == '/';

            // TODO: better detection when path is absolute or not.
            // If the path isn't absolute, then prepend the current working
            // directory to the path.
            if (!startsWithSlashNotOnWindows && !startsWithDriveLetterOnWindows(cwd)) {
                cwd = new File(runtime.getCurrentDirectory(), cwd).getAbsolutePath();
            }
        } else {
            // If there's no second argument, simply use the working directory 
            // of the runtime.
            cwd = runtime.getCurrentDirectory();
        }
        
        // Something wrong we don't know the cwd...
        // TODO: Is this behavior really desirable? /mov
        if (cwd == null) return runtime.getNil();
        
        /* The counting of slashes that follows is simply a way to adhere to 
         * Ruby's UNC (or something) compatibility. When Ruby's expand_path is 
         * called with "//foo//bar" it will return "//foo/bar". JRuby uses 
         * java.io.File, and hence returns "/foo/bar". In order to retain 
         * java.io.File in the lower layers and provide full Ruby 
         * compatibility, the number of extra slashes must be counted and 
         * prepended to the result.
         */ 

        // TODO: special handling on windows for some corner cases
//        if (IS_WINDOWS) {
//            if (relativePath.startsWith("//")) {
//                if (relativePath.length() > 2 && relativePath.charAt(2) != '/') {
//                    int nextSlash = relativePath.indexOf('/', 3);
//                    if (nextSlash != -1) {
//                        return runtime.newString(
//                                relativePath.substring(0, nextSlash)
//                                + canonicalize(relativePath.substring(nextSlash)));
//                    } else {
//                        return runtime.newString(relativePath);
//                    }
//                }
//            }
//        }

        // Find out which string to check.
        String padSlashes = "";
        if (!Platform.IS_WINDOWS) {
            if (relativePath.length() > 0 && relativePath.charAt(0) == '/') {
                padSlashes = countSlashes(relativePath);
            } else if (cwd.length() > 0 && cwd.charAt(0) == '/') {
                padSlashes = countSlashes(cwd);
            }
        }
        
        JRubyFile path;
        
        if (relativePath.length() == 0) {
            path = JRubyFile.create(relativePath, cwd);
        } else {
            relativePath = adjustRootPathOnWindows(runtime, relativePath, cwd);
            path = JRubyFile.create(cwd, relativePath);
        }
        
        String tempResult = padSlashes + canonicalize(path.getAbsolutePath());

        if(isAbsoluteWithFilePrefix) {
            tempResult = tempResult.substring(tempResult.indexOf("file:"));
        }

        return runtime.newString(tempResult);
    }
    
    /**
     * This method checks a path, and if it starts with ~, then it expands 
     * the path to the absolute path of the user's home directory. If the 
     * string does not begin with ~, then the string is simply returned.
     * unaltered.
     * @param recv
     * @param path Path to check
     * @return Expanded path
     */
    public static String expandUserPath(ThreadContext context, String path) {
        
        int pathLength = path.length();

        if (pathLength >= 1 && path.charAt(0) == '~') {
            // Enebo : Should ~frogger\\foo work (it doesnt in linux ruby)?
            int userEnd = path.indexOf('/');
            
            if (userEnd == -1) {
                if (pathLength == 1) {
                    // Single '~' as whole path to expand
                    path = RubyDir.getHomeDirectoryPath(context).toString();
                } else {
                    // No directory delimeter.  Rest of string is username
                    userEnd = pathLength;
                }
            }
            
            if (userEnd == 1) {
                // '~/...' as path to expand
                path = RubyDir.getHomeDirectoryPath(context).toString() +
                        path.substring(1);
            } else if (userEnd > 1){
                // '~user/...' as path to expand
                String user = path.substring(1, userEnd);
                IRubyObject dir = RubyDir.getHomeDirectoryPath(context, user);
                
                if (dir.isNil()) {
                    throw context.getRuntime().newArgumentError("user " + user + " does not exist");
                }
                
                path = "" + dir + (pathLength == userEnd ? "" : path.substring(userEnd));
            }
        }
        return path;
    }
    
    /**
     * Returns a string consisting of <code>n-1</code> slashes, where 
     * <code>n</code> is the number of slashes at the beginning of the input 
     * string.
     * @param stringToCheck
     * @return
     */
    private static String countSlashes( String stringToCheck ) {
        
        // Count number of extra slashes in the beginning of the string.
        int slashCount = 0;
        for (int i = 0; i < stringToCheck.length(); i++) {
            if (stringToCheck.charAt(i) == '/') {
                slashCount++;
            } else {
                break;
            }
        }

        // If there are N slashes, then we want N-1.
        if (slashCount > 0) {
            slashCount--;
        }
        
        // Prepare a string with the same number of redundant slashes so that 
        // we easily can prepend it to the result.
        byte[] slashes = new byte[slashCount];
        for (int i = 0; i < slashCount; i++) {
            slashes[i] = '/';
        }
        return new String(slashes); 
        
    }

    private static String canonicalize(String path) {
        return canonicalize(null, path);
    }

    private static String canonicalize(String canonicalPath, String remaining) {
        if (remaining == null) {
            if ("".equals(canonicalPath)) {
                return "/";
            } else {
                // compensate for missing slash after drive letter on windows
                if (startsWithDriveLetterOnWindows(canonicalPath)
                        && canonicalPath.length() == 2) {
                    canonicalPath += "/";
                }
            }
            return canonicalPath;
        }

        String child;
        int slash = remaining.indexOf('/');
        if (slash == -1) {
            child = remaining;
            remaining = null;
        } else {
            child = remaining.substring(0, slash);
            remaining = remaining.substring(slash + 1);
        }

        if (child.equals(".")) {
            // skip it
            if (canonicalPath != null && canonicalPath.length() == 0 ) canonicalPath += "/";
        } else if (child.equals("..")) {
            if (canonicalPath == null) throw new IllegalArgumentException("Cannot have .. at the start of an absolute path");
            int lastDir = canonicalPath.lastIndexOf('/');
            if (lastDir == -1) {
                if (startsWithDriveLetterOnWindows(canonicalPath)) {
                   // do nothing, we should not delete the drive letter
                } else {
                    canonicalPath = "";
                }
            } else {
                canonicalPath = canonicalPath.substring(0, lastDir);
            }
        } else if (canonicalPath == null) {
            canonicalPath = child;
        } else {
            canonicalPath += "/" + child;
        }

        return canonicalize(canonicalPath, remaining);
    }

    /**
     * Returns true if path matches against pattern The pattern is not a regular expression;
     * instead it follows rules similar to shell filename globbing. It may contain the following
     * metacharacters:
     *   *:  Glob - match any sequence chars (re: .*).  If like begins with '.' then it doesn't.
     *   ?:  Matches a single char (re: .).
     *   [set]:  Matches a single char in a set (re: [...]).
     *
     */
    @JRubyMethod(name = {"fnmatch", "fnmatch?"}, required = 2, optional = 1, meta = true)
    public static IRubyObject fnmatch(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        int flags = args.length == 3 ? RubyNumeric.num2int(args[2]) : 0;

        ByteList pattern = args[0].convertToString().getByteList();
        ByteList path = args[1].convertToString().getByteList();

        if (org.jruby.util.Dir.fnmatch(pattern.bytes, pattern.begin, pattern.begin+pattern.realSize, 
                                       path.bytes, path.begin, path.begin+path.realSize, flags) == 0) {
            return context.getRuntime().getTrue();
        }
        return context.getRuntime().getFalse();
    }
    
    @JRubyMethod(name = "ftype", required = 1, meta = true)
    public static IRubyObject ftype(ThreadContext context, IRubyObject recv, IRubyObject filename) {
        return context.getRuntime().newFileStat(filename.convertToString().toString(), true).ftype();
    }

    private static String inspectJoin(ThreadContext context, IRubyObject recv, RubyArray parent, RubyArray array) {
        Ruby runtime = context.getRuntime();
        
        // If already inspecting, there is no need to register/unregister again.
        if (runtime.isInspecting(parent)) return join(context, recv, array).toString();

        try {
            runtime.registerInspecting(parent);
            return join(context, recv, array).toString();
        } finally {
            runtime.unregisterInspecting(parent);
        }
    }

    private static RubyString join(ThreadContext context, IRubyObject recv, RubyArray ary) {
        IRubyObject[] args = ary.toJavaArray();
        boolean isTainted = false;
        StringBuilder buffer = new StringBuilder();
        Ruby runtime = context.getRuntime();
        
        for (int i = 0; i < args.length; i++) {
            if (args[i].isTaint()) {
                isTainted = true;
            }
            String element;
            if (args[i] instanceof RubyString) {
                element = args[i].toString();
            } else if (args[i] instanceof RubyArray) {
                if (runtime.isInspecting(args[i])) {
                    element = "[...]";
                } else {
                    element = inspectJoin(context, recv, ary, ((RubyArray)args[i]));
                }
            } else {
                element = args[i].convertToString().toString();
            }
            
            chomp(buffer);
            if (i > 0 && !element.startsWith("/") && !element.startsWith("\\")) {
                buffer.append("/");
            }
            buffer.append(element);
        }
        
        RubyString fixedStr = RubyString.newString(runtime, buffer.toString());
        fixedStr.setTaint(isTainted);
        return fixedStr;
    }
    
    /*
     * Fixme:  This does not have exact same semantics as RubyArray.join, but they
     * probably could be consolidated (perhaps as join(args[], sep, doChomp)).
     */
    @JRubyMethod(rest = true, meta = true)
    public static RubyString join(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        return join(context, recv, RubyArray.newArrayNoCopyLight(context.getRuntime(), args));
    }

    private static void chomp(StringBuilder buffer) {
        int lastIndex = buffer.length() - 1;
        
        while (lastIndex >= 0 && (buffer.lastIndexOf("/") == lastIndex || buffer.lastIndexOf("\\") == lastIndex)) {
            buffer.setLength(lastIndex);
            lastIndex--;
        }
    }
    
    @JRubyMethod(name = "lstat", required = 1, meta = true)
    public static IRubyObject lstat(ThreadContext context, IRubyObject recv, IRubyObject filename) {
        String f = filename.convertToString().toString();
        if(f.startsWith("file:") && f.indexOf('!') != -1) {
            f = f.substring(5, f.indexOf("!"));
        }
        return context.getRuntime().newFileStat(f, true);
    }

    @JRubyMethod(name = "stat", required = 1, meta = true)
    public static IRubyObject stat(ThreadContext context, IRubyObject recv, IRubyObject filename) {
        String f = filename.convertToString().toString();
        if(f.startsWith("file:") && f.indexOf('!') != -1) {
            f = f.substring(5, f.indexOf("!"));
        }
        return context.getRuntime().newFileStat(f, false);
    }

    @JRubyMethod(name = "atime", required = 1, meta = true)
    public static IRubyObject atime(ThreadContext context, IRubyObject recv, IRubyObject filename) {
        String f = filename.convertToString().toString();
        if(f.startsWith("file:") && f.indexOf('!') != -1) {
            f = f.substring(5, f.indexOf("!"));
        }
        return context.getRuntime().newFileStat(f, false).atime();
    }

    @JRubyMethod(name = "ctime", required = 1, meta = true)
    public static IRubyObject ctime(ThreadContext context, IRubyObject recv, IRubyObject filename) {
        String f = filename.convertToString().toString();
        if(f.startsWith("file:") && f.indexOf('!') != -1) {
            f = f.substring(5, f.indexOf("!"));
        }
        return context.getRuntime().newFileStat(f, false).ctime();
    }

    @JRubyMethod(required = 2, rest = true, meta = true)
    public static IRubyObject lchmod(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        Ruby runtime = context.getRuntime();
        
        int count = 0;
        RubyInteger mode = args[0].convertToInteger();
        for (int i = 1; i < args.length; i++) {
            IRubyObject filename = args[i];
            
            if (!RubyFileTest.exist_p(filename, filename.convertToString()).isTrue()) {
                throw runtime.newErrnoENOENTError("No such file or directory - " + filename);
            }
            
            boolean result = 0 == runtime.getPosix().lchmod(filename.toString(), (int)mode.getLongValue());
            if (result) {
                count++;
            }
        }
        
        return runtime.newFixnum(count);
    }
    
    @JRubyMethod(required = 3, rest = true, meta = true)
    public static IRubyObject lchown(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        Ruby runtime = context.getRuntime();
        int owner = !args[0].isNil() ? RubyNumeric.num2int(args[0]) : -1;
        int group = !args[1].isNil() ? RubyNumeric.num2int(args[1]) : -1;
        int count = 0;
        
        for (int i = 2; i < args.length; i++) {
            IRubyObject filename = args[i];
            
            if (!RubyFileTest.exist_p(filename, filename.convertToString()).isTrue()) {
                throw runtime.newErrnoENOENTError("No such file or directory - " + filename);
            }
            
            boolean result = 0 == runtime.getPosix().lchown(filename.toString(), owner, group);
            if (result) {
                count++;
            }
        }
        
        return runtime.newFixnum(count);
    }

    @JRubyMethod(required = 2, meta = true)
    public static IRubyObject link(ThreadContext context, IRubyObject recv, IRubyObject from, IRubyObject to) {
        Ruby runtime = context.getRuntime();
        RubyString fromStr = RubyString.stringValue(from);
        RubyString toStr = RubyString.stringValue(to);
        try {
            if (runtime.getPosix().link(
                    fromStr.toString(),toStr.toString()) == -1) {
                // FIXME: When we get JNA3 we need to properly write this to errno.
                throw runtime.newErrnoEEXISTError("File exists - " 
                               + fromStr + " or " + toStr);
            }
        } catch (java.lang.UnsatisfiedLinkError ule) {
            throw runtime.newNotImplementedError("link() function is unimplemented on this machine");
        }
        
        return runtime.newFixnum(0);
    }

    @JRubyMethod(name = "mtime", required = 1, meta = true)
    public static IRubyObject mtime(ThreadContext context, IRubyObject recv, IRubyObject filename) {
        return getLastModified(context.getRuntime(), filename.convertToString().toString());
    }
    
    @JRubyMethod(required = 2, meta = true)
    public static IRubyObject rename(ThreadContext context, IRubyObject recv, IRubyObject oldName, IRubyObject newName) {
        Ruby runtime = context.getRuntime();
        RubyString oldNameString = RubyString.stringValue(oldName);
        RubyString newNameString = RubyString.stringValue(newName);
        runtime.checkSafeString(oldNameString);
        runtime.checkSafeString(newNameString);
        JRubyFile oldFile = JRubyFile.create(runtime.getCurrentDirectory(), oldNameString.toString());
        JRubyFile newFile = JRubyFile.create(runtime.getCurrentDirectory(), newNameString.toString());
        
        if (!oldFile.exists() || !newFile.getParentFile().exists()) {
            throw runtime.newErrnoENOENTError("No such file or directory - " + oldNameString + 
                    " or " + newNameString);
        }

        JRubyFile dest = JRubyFile.create(runtime.getCurrentDirectory(), newNameString.toString());

        if (oldFile.renameTo(dest)) {  // rename is successful
            return RubyFixnum.zero(runtime);
        }

        // rename via Java API call wasn't successful, let's try some tricks, similar to MRI 

        if (newFile.exists()) {
            runtime.getPosix().chmod(newNameString.toString(), 0666);
            newFile.delete();
        }

        if (oldFile.renameTo(dest)) { // try to rename one more time
            return RubyFixnum.zero(runtime);
        }

        throw runtime.newErrnoEACCESError("Permission denied - " + oldNameString + " or " + 
                newNameString);
    }
    
    @JRubyMethod(required = 1, meta = true)
    public static RubyArray split(ThreadContext context, IRubyObject recv, IRubyObject arg) {
        RubyString filename = RubyString.stringValue(arg);
        
        return context.getRuntime().newArray(dirname(context, recv, filename),
                basename(context, recv, new IRubyObject[] { filename }));
    }
    
    @JRubyMethod(required = 2, meta = true)
    public static IRubyObject symlink(ThreadContext context, IRubyObject recv, IRubyObject from, IRubyObject to) {
        Ruby runtime = context.getRuntime();
        RubyString fromStr = RubyString.stringValue(from);
        RubyString toStr = RubyString.stringValue(to);
        try {
            if (runtime.getPosix().symlink(
                    fromStr.toString(), toStr.toString()) == -1) {
                // FIXME: When we get JNA3 we need to properly write this to errno.
                throw runtime.newErrnoEEXISTError("File exists - " 
                               + fromStr + " or " + toStr);
            }
        } catch (java.lang.UnsatisfiedLinkError ule) {
            throw runtime.newNotImplementedError("symlink() function is unimplemented on this machine");
        }
        
        return runtime.newFixnum(0);
    }
    
    @JRubyMethod(required = 1, meta = true)
    public static IRubyObject readlink(ThreadContext context, IRubyObject recv, IRubyObject path) {
        Ruby runtime = context.getRuntime();
        
        try {
            String realPath = runtime.getPosix().readlink(path.toString());
        
            if (!RubyFileTest.exist_p(recv, path).isTrue()) {
                throw runtime.newErrnoENOENTError("No such file or directory - " + path);
            }
        
            if (!RubyFileTest.symlink_p(recv, path).isTrue()) {
                throw runtime.newErrnoEINVALError("invalid argument - " + path);
            }
        
            if (realPath == null) {
                //FIXME: When we get JNA3 we need to properly write this to errno.
            }

            return runtime.newString(realPath);
        } catch (IOException e) {
            throw runtime.newIOError(e.getMessage());
        }
    }

    // Can we produce IOError which bypasses a close?
    @JRubyMethod(required = 2, meta = true)
    public static IRubyObject truncate(ThreadContext context, IRubyObject recv, IRubyObject arg1, IRubyObject arg2) {
        Ruby runtime = context.getRuntime();
        RubyString filename = arg1.convertToString(); // TODO: SafeStringValue here
        RubyInteger newLength = arg2.convertToInteger(); 
        
        if (!new File(runtime.getCurrentDirectory(), filename.getByteList().toString()).exists()) {
            throw runtime.newErrnoENOENTError(
                    "No such file or directory - " + filename.getByteList().toString());
        }

        if (newLength.getLongValue() < 0) {
            throw runtime.newErrnoEINVALError("invalid argument: " + filename);
        }
        
        IRubyObject[] args = new IRubyObject[] { filename, runtime.newString("r+") };
        RubyFile file = (RubyFile) open(context, recv, args, Block.NULL_BLOCK);
        file.truncate(context, newLength);
        file.close();
        
        return RubyFixnum.zero(runtime);
    }

    @JRubyMethod(meta = true, optional = 1)
    public static IRubyObject umask(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        Ruby runtime = context.getRuntime();
        int oldMask = 0;
        if (args.length == 0) {
            oldMask = runtime.getPosix().umask(0);
            runtime.getPosix().umask(oldMask);
        } else if (args.length == 1) {
            oldMask = runtime.getPosix().umask((int) args[0].convertToInteger().getLongValue()); 
        } else {
            runtime.newArgumentError("wrong number of arguments");
        }
        
        return runtime.newFixnum(oldMask);
    }

    /**
     * This method does NOT set atime, only mtime, since Java doesn't support anything else.
     */
    @JRubyMethod(required = 2, rest = true, meta = true)
    public static IRubyObject utime(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        Ruby runtime = context.getRuntime();
        
        // Ignore access_time argument since Java does not support it.
        
        long mtime;
        if (args[1] instanceof RubyTime) {
            mtime = ((RubyTime) args[1]).getJavaDate().getTime();
        } else if (args[1] instanceof RubyNumeric) {
            mtime = RubyNumeric.num2long(args[1]);
        } else if (args[1] == runtime.getNil()) {
            mtime = System.currentTimeMillis();
        } else {
            RubyTime time = (RubyTime) TypeConverter.convertToType(args[1], runtime.getTime(), MethodIndex.NO_INDEX,"to_time", true);
            mtime = time.getJavaDate().getTime();
        }
        
        for (int i = 2, j = args.length; i < j; i++) {
            RubyString filename = RubyString.stringValue(args[i]);
            runtime.checkSafeString(filename);
            JRubyFile fileToTouch = JRubyFile.create(runtime.getCurrentDirectory(),filename.toString());
            
            if (!fileToTouch.exists()) {
                throw runtime.newErrnoENOENTError(" No such file or directory - \"" + filename + "\"");
            }
            
            fileToTouch.setLastModified(mtime);
        }
        
        return runtime.newFixnum(args.length - 2);
    }
    
    @JRubyMethod(name = {"unlink", "delete"}, rest = true, meta = true)
    public static IRubyObject unlink(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        Ruby runtime = context.getRuntime();
        
        for (int i = 0; i < args.length; i++) {
            RubyString filename = RubyString.stringValue(args[i]);
            runtime.checkSafeString(filename);
            JRubyFile lToDelete = JRubyFile.create(runtime.getCurrentDirectory(),filename.toString());
            
            boolean isSymlink = RubyFileTest.symlink_p(recv, filename).isTrue();
            // Broken symlinks considered by exists() as non-existing,
            // so we need to check for symlinks explicitly.
            if (!lToDelete.exists() && !isSymlink) {
                throw runtime.newErrnoENOENTError(" No such file or directory - \"" + filename + "\"");
            }

            if (!lToDelete.delete()) {
                throw runtime.newErrnoEACCESError("Permission denied - \"" + filename + "\"");
            }
        }
        
        return runtime.newFixnum(args.length);
    }

    // Fast path since JNA stat is about 10x slower than this
    private static IRubyObject getLastModified(Ruby runtime, String path) {
        JRubyFile file = JRubyFile.create(runtime.getCurrentDirectory(), path);
        
        if (!file.exists()) {
            throw runtime.newErrnoENOENTError("No such file or directory - " + path);
        }
        
        return runtime.newTime(file.lastModified());
    }
}
/*
 ***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2002-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Joey Gibson <joey@joeygibson.com>
 * Copyright (C) 2004 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.io.FileDescriptor;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.ext.posix.FileStat;
import org.jruby.ext.posix.util.Platform;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.JRubyFile;

/**
 * Implements File::Stat
 */
@JRubyClass(name="File::Stat", include="Comparable")
public class RubyFileStat extends RubyObject {
    private static final long serialVersionUID = 1L;
    
    private JRubyFile file;
    private FileStat stat;

    private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return new RubyFileStat(runtime, klass);
        }
    };

    public static RubyClass createFileStatClass(Ruby runtime) {
        // TODO: NOT_ALLOCATABLE_ALLOCATOR is probably ok here. Confirm. JRUBY-415
        final RubyClass fileStatClass = runtime.getFile().defineClassUnder("Stat",runtime.getObject(), ALLOCATOR);
        runtime.setFileStat(fileStatClass);

        fileStatClass.includeModule(runtime.fastGetModule("Comparable"));
        fileStatClass.defineAnnotatedMethods(RubyFileStat.class);

        return fileStatClass;
    }

    protected RubyFileStat(Ruby runtime, RubyClass clazz) {
        super(runtime, clazz);

    }
    
    public static RubyFileStat newFileStat(Ruby runtime, String filename, boolean lstat) {
        RubyFileStat stat = new RubyFileStat(runtime, runtime.getFileStat());
        
        stat.setup(filename, lstat);
        
        return stat;
    }

    public static RubyFileStat newFileStat(Ruby runtime, FileDescriptor descriptor) {
        RubyFileStat stat = new RubyFileStat(runtime, runtime.getFileStat());
        
        stat.setup(descriptor);
        
        return stat;
    }

    private void setup(FileDescriptor descriptor) {
        stat = getRuntime().getPosix().fstat(descriptor);
    }
    
    private void setup(String filename, boolean lstat) {
        if (Platform.IS_WINDOWS && filename.length() == 2
                && filename.charAt(1) == ':' && Character.isLetter(filename.charAt(0))) {
            filename += "/";
        }
            
        file = JRubyFile.create(getRuntime().getCurrentDirectory(), filename);

        if (lstat) {
            stat = getRuntime().getPosix().lstat(file.getAbsolutePath());
        } else {
            stat = getRuntime().getPosix().stat(file.getAbsolutePath());
        }
    }

    @JRubyMethod(name = "initialize", required = 1, visibility = Visibility.PRIVATE)
    public IRubyObject initialize(IRubyObject fname, Block unusedBlock) {
        setup(fname.convertToString().toString(), false);

        return this;
    }
    
    @JRubyMethod(name = "atime")
    public IRubyObject atime() {
        return getRuntime().newTime(stat.atime() * 1000);
    }
    
    @JRubyMethod(name = "blksize")
    public RubyFixnum blksize() {
        return getRuntime().newFixnum(stat.blockSize());
    }

    @JRubyMethod(name = "blockdev?")
    public IRubyObject blockdev_p() {
        return getRuntime().newBoolean(stat.isBlockDev());
    }

    @JRubyMethod(name = "blocks")
    public IRubyObject blocks() {
        return getRuntime().newFixnum(stat.blocks());
    }

    @JRubyMethod(name = "chardev?")
    public IRubyObject chardev_p() {
        return getRuntime().newBoolean(stat.isCharDev());
    }

    @JRubyMethod(name = "<=>", required = 1)
    public IRubyObject cmp(IRubyObject other) {
        if (!(other instanceof RubyFileStat)) getRuntime().getNil();
        
        long time1 = stat.mtime();
        long time2 = ((RubyFileStat) other).stat.mtime();
        
        if (time1 == time2) {
            return getRuntime().newFixnum(0);
        } else if (time1 < time2) {
            return getRuntime().newFixnum(-1);
        } 

        return getRuntime().newFixnum(1);
    }

    @JRubyMethod(name = "ctime")
    public IRubyObject ctime() {
        return getRuntime().newTime(stat.ctime() * 1000);
    }

    @JRubyMethod(name = "dev")
    public IRubyObject dev() {
        return getRuntime().newFixnum(stat.dev());
    }
    
    @JRubyMethod(name = "dev_major")
    public IRubyObject devMajor() {
        return getRuntime().newFixnum(stat.major(stat.dev()));
    }

    @JRubyMethod(name = "dev_minor")
    public IRubyObject devMinor() {
        return getRuntime().newFixnum(stat.minor(stat.dev()));
    }

    @JRubyMethod(name = "directory?")
    public RubyBoolean directory_p() {
        return getRuntime().newBoolean(stat.isDirectory());
    }

    @JRubyMethod(name = "executable?")
    public IRubyObject executable_p() {
        return getRuntime().newBoolean(stat.isExecutable());
    }

    @JRubyMethod(name = "executable_real?")
    public IRubyObject executableReal_p() {
        return getRuntime().newBoolean(stat.isExecutableReal());
    }

    @JRubyMethod(name = "file?")
    public RubyBoolean file_p() {
        return getRuntime().newBoolean(stat.isFile());
    }

    @JRubyMethod(name = "ftype")
    public RubyString ftype() {
        return getRuntime().newString(stat.ftype());
    }

    @JRubyMethod(name = "gid")
    public IRubyObject gid() {
        return getRuntime().newFixnum(stat.gid());
    }
    
    @JRubyMethod(name = "grpowned?")
    public IRubyObject group_owned_p() {
        return getRuntime().newBoolean(stat.isGroupOwned());
    }
    
    @JRubyMethod(name = "initialize_copy", required = 1)
    public IRubyObject initialize_copy(IRubyObject original) {
        if (!(original instanceof RubyFileStat)) {
            throw getRuntime().newTypeError("wrong argument class");
        }
        
        RubyFileStat originalFileStat = (RubyFileStat) original;
        
        file = originalFileStat.file;
        stat = originalFileStat.stat;
        
        return this;
    }
    
    @JRubyMethod(name = "ino")
    public IRubyObject ino() {
        return getRuntime().newFixnum(stat.ino());
    }

    @JRubyMethod(name = "inspect")
    public IRubyObject inspect() {
        StringBuilder buf = new StringBuilder("#<");
        buf.append(getMetaClass().getRealClass().getName());
        buf.append(" ");
        // FIXME: Obvious issue that not all platforms can display all attributes.  Ugly hacks.
        // Using generic posix library makes pushing inspect behavior into specific system impls
        // rather painful.
        try { buf.append("dev=0x").append(Long.toHexString(stat.dev())).append(", "); } catch (Exception e) {}
        try { buf.append("ino=").append(stat.ino()).append(", "); } catch (Exception e) {}
        buf.append("mode=0").append(Integer.toOctalString(stat.mode())).append(", "); 
        try { buf.append("nlink=").append(stat.nlink()).append(", "); } catch (Exception e) {}
        try { buf.append("uid=").append(stat.uid()).append(", "); } catch (Exception e) {}
        try { buf.append("gid=").append(stat.gid()).append(", "); } catch (Exception e) {}
        try { buf.append("rdev=0x").append(Long.toHexString(stat.rdev())).append(", "); } catch (Exception e) {}
        buf.append("size=").append(stat.st_size()).append(", ");
        try { buf.append("blksize=").append(stat.blockSize()).append(", "); } catch (Exception e) {}
        try { buf.append("blocks=").append(stat.blocks()).append(", "); } catch (Exception e) {}
        
        buf.append("atime=").append(atime()).append(", ");
        buf.append("mtime=").append(mtime()).append(", ");
        buf.append("ctime=").append(ctime());
        buf.append(">");
        
        return getRuntime().newString(buf.toString());
    }

    @JRubyMethod(name = "uid")
    public IRubyObject uid() {
        return getRuntime().newFixnum(stat.uid());
    }
    
    @JRubyMethod(name = "mode")
    public IRubyObject mode() {
        return getRuntime().newFixnum(stat.mode());
    }

    @JRubyMethod(name = "mtime")
    public IRubyObject mtime() {
        return getRuntime().newTime(stat.mtime() * 1000);
    }
    
    public IRubyObject mtimeEquals(IRubyObject other) {
        return getRuntime().newBoolean(stat.mtime() == newFileStat(getRuntime(), other.convertToString().toString(), false).stat.mtime()); 
    }

    public IRubyObject mtimeGreaterThan(IRubyObject other) {
        return getRuntime().newBoolean(stat.mtime() > newFileStat(getRuntime(), other.convertToString().toString(), false).stat.mtime()); 
    }

    public IRubyObject mtimeLessThan(IRubyObject other) {
        return getRuntime().newBoolean(stat.mtime() < newFileStat(getRuntime(), other.convertToString().toString(), false).stat.mtime()); 
    }

    @JRubyMethod(name = "nlink")
    public IRubyObject nlink() {
        return getRuntime().newFixnum(stat.nlink());
    }

    @JRubyMethod(name = "owned?")
    public IRubyObject owned_p() {
        return getRuntime().newBoolean(stat.isOwned());
    }

    @JRubyMethod(name = "pipe?")
    public IRubyObject pipe_p() {
        return getRuntime().newBoolean(stat.isNamedPipe());
    }

    @JRubyMethod(name = "rdev")
    public IRubyObject rdev() {
        return getRuntime().newFixnum(stat.rdev());
    }
    
    @JRubyMethod(name = "rdev_major")
    public IRubyObject rdevMajor() {
        return getRuntime().newFixnum(stat.major(stat.rdev()));
    }

    @JRubyMethod(name = "rdev_minor")
    public IRubyObject rdevMinor() {
        return getRuntime().newFixnum(stat.minor(stat.rdev()));
    }

    @JRubyMethod(name = "readable?")
    public IRubyObject readable_p() {
        return getRuntime().newBoolean(stat.isReadable());
    }

    @JRubyMethod(name = "readable_real?")
    public IRubyObject readableReal_p() {
        return getRuntime().newBoolean(stat.isReadableReal());
    }

    @JRubyMethod(name = "setgid?")
    public IRubyObject setgid_p() {
        return getRuntime().newBoolean(stat.isSetgid());
    }

    @JRubyMethod(name = "setuid?")
    public IRubyObject setuid_p() {
        return getRuntime().newBoolean(stat.isSetuid());
    }

    @JRubyMethod(name = "size")
    public IRubyObject size() {
        return getRuntime().newFixnum(stat.st_size());
    }
    
    @JRubyMethod(name = "size?")
    public IRubyObject size_p() {
        long size = stat.st_size();
        
        if (size == 0) return getRuntime().getNil();
        
        return getRuntime().newFixnum(size);
    }

    @JRubyMethod(name = "socket?")
    public IRubyObject socket_p() {
        return getRuntime().newBoolean(stat.isSocket());
    }
    
    @JRubyMethod(name = "sticky?")
    public IRubyObject sticky_p() {
        return getRuntime().newBoolean(stat.isSticky());
    }

    @JRubyMethod(name = "symlink?")
    public IRubyObject symlink_p() {
        return getRuntime().newBoolean(stat.isSymlink());
    }

    @JRubyMethod(name = "writable?")
    public IRubyObject writable_p() {
    	return getRuntime().newBoolean(stat.isWritable());
    }
    
    @JRubyMethod(name = "writable_real?")
    public IRubyObject writableReal_p() {
        return getRuntime().newBoolean(stat.isWritableReal());
    }
    
    @JRubyMethod(name = "zero?")
    public IRubyObject zero_p() {
        return getRuntime().newBoolean(stat.isEmpty());
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2004-2005 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;
import org.jruby.exceptions.RaiseException;

import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.JRubyFile;

@JRubyModule(name="FileTest")
public class RubyFileTest {
    public static RubyModule createFileTestModule(Ruby runtime) {
        RubyModule fileTestModule = runtime.defineModule("FileTest");
        runtime.setFileTest(fileTestModule);
        
        fileTestModule.defineAnnotatedMethods(RubyFileTest.class);
        
        return fileTestModule;
    }

    @JRubyMethod(name = "blockdev?", required = 1, module = true)
    public static IRubyObject blockdev_p(IRubyObject recv, IRubyObject filename) {
        Ruby runtime = recv.getRuntime();
        JRubyFile file = file(filename);
        
        return runtime.newBoolean(file.exists() && runtime.getPosix().stat(file.getAbsolutePath()).isBlockDev());
    }
    
    @JRubyMethod(name = "chardev?", required = 1, module = true)
    public static IRubyObject chardev_p(IRubyObject recv, IRubyObject filename) {
        Ruby runtime = recv.getRuntime();
        JRubyFile file = file(filename);
        
        return runtime.newBoolean(file.exists() && runtime.getPosix().stat(file.getAbsolutePath()).isCharDev());
    }

    @JRubyMethod(name = "directory?", required = 1, module = true)
    public static IRubyObject directory_p(IRubyObject recv, IRubyObject filename) {
        Ruby runtime = recv.getRuntime();
        JRubyFile file = file(filename);
        
        return runtime.newBoolean(file.exists() && runtime.getPosix().stat(file.getAbsolutePath()).isDirectory());
    }
    
    @JRubyMethod(name = "executable?", required = 1, module = true)
    public static IRubyObject executable_p(IRubyObject recv, IRubyObject filename) {
        Ruby runtime = recv.getRuntime();
        JRubyFile file = file(filename);
        
        return runtime.newBoolean(file.exists() && runtime.getPosix().stat(file.getAbsolutePath()).isExecutable());
    }
    
    @JRubyMethod(name = "executable_real?", required = 1, module = true)
    public static IRubyObject executable_real_p(IRubyObject recv, IRubyObject filename) {
        Ruby runtime = recv.getRuntime();
        JRubyFile file = file(filename);
        
        return runtime.newBoolean(file.exists() && runtime.getPosix().stat(file.getAbsolutePath()).isExecutableReal());
    }
    
    @JRubyMethod(name = {"exist?", "exists?"}, required = 1, module = true)
    public static IRubyObject exist_p(IRubyObject recv, IRubyObject filename) {
        if (Ruby.isSecurityRestricted()) {
            return recv.getRuntime().getFalse();
        }

        if(filename.convertToString().toString().startsWith("file:")) {
            String file = filename.convertToString().toString().substring(5);
            int bang = file.indexOf('!');
            if (bang == -1 || bang == file.length() - 1) {
                return recv.getRuntime().getFalse();
            }
            String jar = file.substring(0, bang);
            String after = file.substring(bang + 2);
            try {
                java.util.jar.JarFile jf = new java.util.jar.JarFile(jar);
                if(jf.getJarEntry(after) != null) {
                    return recv.getRuntime().getTrue();
                } else {
                    return recv.getRuntime().getFalse();
                }
            } catch(Exception e) {
                return recv.getRuntime().getFalse();
            }
        }

        return recv.getRuntime().newBoolean(file(filename).exists());
    }

    @JRubyMethod(name = "file?", required = 1, module = true)
    public static RubyBoolean file_p(IRubyObject recv, IRubyObject filename) {
        JRubyFile file = file(filename);
        
        return filename.getRuntime().newBoolean(file.exists() && file.isFile());
    }

    @JRubyMethod(name = "grpowned?", required = 1, module = true)
    public static IRubyObject grpowned_p(IRubyObject recv, IRubyObject filename) {
        Ruby runtime = recv.getRuntime();
        JRubyFile file = file(filename);
        
        return runtime.newBoolean(file.exists() && runtime.getPosix().stat(file.getAbsolutePath()).isGroupOwned());
    }
    
    @JRubyMethod(name = "identical?", required = 2, module = true)
    public static IRubyObject identical_p(IRubyObject recv, IRubyObject filename1, IRubyObject filename2) {
        Ruby runtime = recv.getRuntime();
        JRubyFile file1 = file(filename1);
        JRubyFile file2 = file(filename2);
        
        return runtime.newBoolean(file1.exists() && file2.exists() &&
                runtime.getPosix().stat(file1.getAbsolutePath()).isIdentical(runtime.getPosix().stat(file2.getAbsolutePath())));
    }

    @JRubyMethod(name = "owned?", required = 1, module = true)
    public static IRubyObject owned_p(IRubyObject recv, IRubyObject filename) {
        Ruby runtime = recv.getRuntime();
        JRubyFile file = file(filename);
        
        return runtime.newBoolean(file.exists() && runtime.getPosix().stat(file.getAbsolutePath()).isOwned());
    }
    
    @JRubyMethod(name = "pipe?", required = 1, module = true)
    public static IRubyObject pipe_p(IRubyObject recv, IRubyObject filename) {
        Ruby runtime = recv.getRuntime();
        JRubyFile file = file(filename);
        
        return runtime.newBoolean(file.exists() && runtime.getPosix().stat(file.getAbsolutePath()).isNamedPipe());
    }

    // We use file test since it is faster than a stat; also euid == uid in Java always
    @JRubyMethod(name = {"readable?", "readable_real?"}, required = 1, module = true)
    public static IRubyObject readable_p(IRubyObject recv, IRubyObject filename) {
        JRubyFile file = file(filename);

        return recv.getRuntime().newBoolean(file.exists() && file.canRead());
    }

    // Not exposed by filetest, but so similiar in nature that it is stored here
    public static IRubyObject rowned_p(IRubyObject recv, IRubyObject filename) {
        Ruby runtime = recv.getRuntime();
        JRubyFile file = file(filename);
        
        return runtime.newBoolean(file.exists() && runtime.getPosix().stat(file.getAbsolutePath()).isROwned());
    }
    
    @JRubyMethod(name = "setgid?", required = 1, module = true)
    public static IRubyObject setgid_p(IRubyObject recv, IRubyObject filename) {
        Ruby runtime = recv.getRuntime();
        JRubyFile file = file(filename);
        
        return runtime.newBoolean(file.exists() && runtime.getPosix().stat(file.getAbsolutePath()).isSetgid());
    }
    
    @JRubyMethod(name = "setuid?", required = 1, module = true)
    public static IRubyObject setuid_p(IRubyObject recv, IRubyObject filename) {
        Ruby runtime = recv.getRuntime();
        JRubyFile file = file(filename);
        
        return runtime.newBoolean(file.exists() && runtime.getPosix().stat(file.getAbsolutePath()).isSetuid());
    }
    
    @JRubyMethod(name = "size", required = 1, module = true)
    public static IRubyObject size(IRubyObject recv, IRubyObject filename) {
        JRubyFile file = file(filename);

        if (!file.exists()) noFileError(filename);
        
        return recv.getRuntime().newFixnum(file.length());
    }
    
    @JRubyMethod(name = "size?", required = 1, module = true)
    public static IRubyObject size_p(IRubyObject recv, IRubyObject filename) {
        JRubyFile file = file(filename);

        if (!file.exists()) {
            return recv.getRuntime().getNil();
        }

        long length =  file.length();
        if (length > 0) {
            return recv.getRuntime().newFixnum(length);
        } else {
            return recv.getRuntime().getNil();
        }
    }
    
    @JRubyMethod(name = "socket?", required = 1, module = true)
    public static IRubyObject socket_p(IRubyObject recv, IRubyObject filename) {
        Ruby runtime = recv.getRuntime();
        JRubyFile file = file(filename);
        
        return runtime.newBoolean(file.exists() && runtime.getPosix().stat(file.getAbsolutePath()).isSocket());
    }
    
    @JRubyMethod(name = "sticky?", required = 1, module = true)
    public static IRubyObject sticky_p(IRubyObject recv, IRubyObject filename) {
        Ruby runtime = recv.getRuntime();
        JRubyFile file = file(filename);
        
        return runtime.newBoolean(file.exists() && runtime.getPosix().stat(file.getAbsolutePath()).isSticky());
    }
        
    @JRubyMethod(name = "symlink?", required = 1, module = true)
    public static RubyBoolean symlink_p(IRubyObject recv, IRubyObject filename) {
        Ruby runtime = recv.getRuntime();
        JRubyFile file = file(filename);

        try {
            // Note: We can't use file.exists() to check whether the symlink
            // exists or not, because that method returns false for existing
            // but broken symlink. So, we try without the existence check,
            // but in the try-catch block.
            // MRI behavior: symlink? on broken symlink should return true.
            return runtime.newBoolean(runtime.getPosix().lstat(file.getAbsolutePath()).isSymlink());
        } catch (RaiseException re) {
            return runtime.getFalse();
        }
    }

    // We do both writable and writable_real through the same method because
    // in our java process effective and real userid will always be the same.
    @JRubyMethod(name = {"writable?", "writable_real?"}, required = 1, module = true)
    public static RubyBoolean writable_p(IRubyObject recv, IRubyObject filename) {
        return filename.getRuntime().newBoolean(file(filename).canWrite());
    }
    
    @JRubyMethod(name = "zero?", required = 1, module = true)
    public static RubyBoolean zero_p(IRubyObject recv, IRubyObject filename) {
        JRubyFile file = file(filename);
        
        return filename.getRuntime().newBoolean(file.exists() && file.length() == 0L);
    }

    private static JRubyFile file(IRubyObject path) {
        String filename = path.convertToString().toString();
        
        return JRubyFile.create(path.getRuntime().getCurrentDirectory(), filename);
    }
    
    private static void noFileError(IRubyObject filename) {
        throw filename.getRuntime().newErrnoENOENTError("No such file or directory - " + 
                filename.convertToString());
    }
}
/*
 ***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2002-2006 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2005 David Corbin <dcorbin@users.sourceforge.net>
 * Copyright (C) 2006 Antti Karanta <antti.karanta@napa.fi>
 * Copyright (C) 2007 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.common.IRubyWarnings.ID;
import org.jruby.java.MiniJava;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.marshal.UnmarshalStream;
import org.jruby.util.Convert;
import org.jruby.util.Numeric;
import org.jruby.util.TypeCoercer;

/** 
 * Implementation of the Fixnum class.
 */
@JRubyClass(name="Fixnum", parent="Integer", include="Precision")
public class RubyFixnum extends RubyInteger {
    
    public static RubyClass createFixnumClass(Ruby runtime) {
        RubyClass fixnum = runtime.defineClass("Fixnum", runtime.getInteger(),
                ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
        runtime.setFixnum(fixnum);
        fixnum.index = ClassIndex.FIXNUM;
        fixnum.kindOf = new RubyModule.KindOf() {
                @Override
                public boolean isKindOf(IRubyObject obj, RubyModule type) {
                    return obj instanceof RubyFixnum;
                }
            };

        fixnum.includeModule(runtime.getPrecision());
        
        fixnum.defineAnnotatedMethods(RubyFixnum.class);
        
        for (int i = 0; i < runtime.fixnumCache.length; i++) {
            runtime.fixnumCache[i] = new RubyFixnum(runtime, fixnum, i - 128);
        }

        return fixnum;
    }    
    
    private final long value;
    private static final int BIT_SIZE = 64;
    public static final long SIGN_BIT = (1L << (BIT_SIZE - 1));
    public static final long MAX = (1L<<(BIT_SIZE - 1)) - 1;
    public static final long MIN = -1 * MAX - 1;
    public static final long MAX_MARSHAL_FIXNUM = (1L << 30) - 1; // 0x3fff_ffff
    public static final long MIN_MARSHAL_FIXNUM = - (1L << 30);   // -0x4000_0000

    private static IRubyObject fixCoerce(IRubyObject x) {
        do {
            x = x.convertToInteger();
        } while (!(x instanceof RubyFixnum) && !(x instanceof RubyBignum));
        return x;
    }
    
    public RubyFixnum(Ruby runtime) {
        this(runtime, 0);
    }

    public RubyFixnum(Ruby runtime, long value) {
        super(runtime, runtime.getFixnum(), false);
        this.value = value;
    }
    
    private RubyFixnum(Ruby runtime, RubyClass klazz, long value) {
        super(runtime, klazz, false);
        this.value = value;
    }
    
    @Override
    public int getNativeTypeIndex() {
        return ClassIndex.FIXNUM;
    }
    
    /** 
     * short circuit for Fixnum key comparison
     */
    @Override
    public final boolean eql(IRubyObject other) {
        return other instanceof RubyFixnum && value == ((RubyFixnum)other).value;
    }
    
    @Override
    public boolean isImmediate() {
    	return true;
    }
    
    @Override
    public RubyClass getSingletonClass() {
        throw getRuntime().newTypeError("can't define singleton");
    }

    @Override
    public Class<?> getJavaClass() {
        // this precision-guessing needs to be thought out more, since in the
        // case of coercing to Object we generally want to get the same type
        // always
//        if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
//            return byte.class;
//        } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
//            return short.class;
//        } else if (value >= Character.MIN_VALUE && value <= Character.MAX_VALUE) {
//            return char.class;
//        } else if (value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE) {
//            return int.class;
//        }
        return long.class;
    }

    @Override
    public double getDoubleValue() {
        return value;
    }

    @Override
    public long getLongValue() {
        return value;
    }

    private static final int CACHE_OFFSET = 128;
    
    public static RubyFixnum newFixnum(Ruby runtime, long value) {
        if (isInCacheRange(value)) {
            return runtime.fixnumCache[(int) value + CACHE_OFFSET];
        }
        return new RubyFixnum(runtime, value);
    }
    
    private static boolean isInCacheRange(long value) {
        return value <= 127 && value >= -128;
    }

    public RubyFixnum newFixnum(long newValue) {
        return newFixnum(getRuntime(), newValue);
    }

    public static RubyFixnum zero(Ruby runtime) {
        return runtime.fixnumCache[CACHE_OFFSET];
    }

    public static RubyFixnum one(Ruby runtime) {
        return runtime.fixnumCache[CACHE_OFFSET + 1];
    }
    
    public static RubyFixnum two(Ruby runtime) {
        return runtime.fixnumCache[CACHE_OFFSET + 2];
    }
    
    public static RubyFixnum three(Ruby runtime) {
        return runtime.fixnumCache[CACHE_OFFSET + 3];
    }
    
    public static RubyFixnum four(Ruby runtime) {
        return runtime.fixnumCache[CACHE_OFFSET + 4];
    }
    
    public static RubyFixnum five(Ruby runtime) {
        return runtime.fixnumCache[CACHE_OFFSET + 5];
    }

    public static RubyFixnum minus_one(Ruby runtime) {
        return runtime.fixnumCache[CACHE_OFFSET - 1];
    }

    @Override
    public RubyFixnum hash() {
        return newFixnum(hashCode());
    }

    @Override
    public final int hashCode() {
        return (int)(value ^ value >>> 32);
    }

    @Override
    public boolean equals(Object other) {
        if (other == this) {
            return true;
        }
        
        if (other instanceof RubyFixnum) { 
            RubyFixnum num = (RubyFixnum)other;
            
            if (num.value == value) {
                return true;
            }
        }
        
        return false;
    }

    /*  ================
     *  Instance Methods
     *  ================ 
     */

    /** fix_to_s
     * 
     */
    public RubyString to_s(IRubyObject[] args) {
        switch (args.length) {
        case 0: return to_s();
        case 1: return to_s(args[0]);
        default: throw getRuntime().newArgumentError(args.length, 1);
        }
    }
    
    @JRubyMethod
    @Override
    public RubyString to_s() {
        int base = 10;
        return getRuntime().newString(Convert.longToByteList(value, base));
    }
    
    @JRubyMethod
    public RubyString to_s(IRubyObject arg0) {
        int base = num2int(arg0);
        if (base < 2 || base > 36) {
            throw getRuntime().newArgumentError("illegal radix " + base);
        }
        return getRuntime().newString(Convert.longToByteList(value, base));
    }

    /** fix_id2name
     * 
     */
    @JRubyMethod
    public IRubyObject id2name() {
        RubySymbol symbol = RubySymbol.getSymbolLong(getRuntime(), value);
        
        if (symbol != null) return getRuntime().newString(symbol.asJavaString());

        return getRuntime().getNil();
    }

    /** fix_to_sym
     * 
     */
    @JRubyMethod
    public IRubyObject to_sym() {
        RubySymbol symbol = RubySymbol.getSymbolLong(getRuntime(), value);
        
        return symbol != null ? symbol : getRuntime().getNil(); 
    }

    /** fix_uminus
     * 
     */
    @JRubyMethod(name = "-@")
    public IRubyObject op_uminus() {
        if (value == MIN) { // a gotcha
            return RubyBignum.newBignum(getRuntime(), BigInteger.valueOf(value).negate());
        }
        return RubyFixnum.newFixnum(getRuntime(), -value);
        }

    /** fix_plus
     * 
     */
    @JRubyMethod(name = "+")
    public IRubyObject op_plus(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyFixnum) {
            return addFixnum(context, (RubyFixnum)other);
        }
        return addOther(context, other);
    }
    
    private IRubyObject addFixnum(ThreadContext context, RubyFixnum other) {
        long otherValue = other.value;
        long result = value + otherValue;
        if (additionOverflowed(value, otherValue, result)) {
            return addAsBignum(context, other);
        }
        return newFixnum(context.getRuntime(), result);
    }
    
    private static boolean additionOverflowed(long original, long other, long result) {
        return (~(original ^ other) & (original ^ result) & SIGN_BIT) != 0;
    }
    
    private static boolean subtractionOverflowed(long original, long other, long result) {
        return (~(original ^ ~other) & (original ^ result) & SIGN_BIT) != 0;
    }
    
    private IRubyObject addAsBignum(ThreadContext context, RubyFixnum other) {
        return RubyBignum.newBignum(context.getRuntime(), value).op_plus(context, other);
    }
    
    private IRubyObject addOther(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyBignum) {
            return ((RubyBignum) other).op_plus(context, this);
        }
        if (other instanceof RubyFloat) {
            return context.getRuntime().newFloat((double) value + ((RubyFloat) other).getDoubleValue());
        }
        return coerceBin(context, "+", other);
    }

    /** fix_minus
     * 
     */
    @JRubyMethod(name = "-")
    public IRubyObject op_minus(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyFixnum) {
            return subtractFixnum(context, (RubyFixnum)other);
        }
        return subtractOther(context, other);
    }
    
    private IRubyObject subtractFixnum(ThreadContext context, RubyFixnum other) {
        long otherValue = other.value;
        long result = value - otherValue;
        if (subtractionOverflowed(value, otherValue, result)) {
            return subtractAsBignum(context, other);
        }
        return newFixnum(context.getRuntime(), result);
    }
    
    private IRubyObject subtractAsBignum(ThreadContext context, RubyFixnum other) {
        return RubyBignum.newBignum(context.getRuntime(), value).op_minus(context, other);
    }
    
    private IRubyObject subtractOther(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyBignum) {
            return RubyBignum.newBignum(context.getRuntime(), value).op_minus(context, other);
        } else if (other instanceof RubyFloat) {
            return context.getRuntime().newFloat((double) value - ((RubyFloat) other).getDoubleValue());
        }
        return coerceBin(context, "-", other);
    }

    /** fix_mul
     * 
     */
    @JRubyMethod(name = "*")
    public IRubyObject op_mul(ThreadContext context, IRubyObject other) {
        Ruby runtime = context.getRuntime();
        if (other instanceof RubyFixnum) {
            long otherValue = ((RubyFixnum) other).value;
            if (value == 0) {
                return RubyFixnum.zero(runtime);
            }
            long result = value * otherValue;
            if (result / value != otherValue) {
                return RubyBignum.newBignum(runtime, value).op_mul(context, other);
            }
            return newFixnum(runtime, result);
        } else if (other instanceof RubyBignum) {
            return ((RubyBignum) other).op_mul(context, this);
        } else if (other instanceof RubyFloat) {
            return runtime.newFloat((double) value * ((RubyFloat) other).getDoubleValue());
        }
        return coerceBin(context, "*", other);
    }
    
    /** fix_div
     * here is terrible MRI gotcha:
     * 1.div 3.0 -> 0
     * 1 / 3.0   -> 0.3333333333333333
     * 
     * MRI is also able to do it in one place by looking at current frame in rb_num_coerce_bin:
     * rb_funcall(x, ruby_frame->orig_func, 1, y);
     * 
     * also note that RubyFloat doesn't override Numeric.div
     */
    @JRubyMethod(name = "div")
    public IRubyObject div_div(ThreadContext context, IRubyObject other) {
        return idiv(context, other, "div");
    }
    	
    @JRubyMethod(name = "/")
    public IRubyObject op_div(ThreadContext context, IRubyObject other) {
        return idiv(context, other, "/");
    }

    @JRubyMethod(name = {"odd?"})
    public RubyBoolean odd_p() {
        if(value%2 != 0) {
            return getRuntime().getTrue();
        }
        return getRuntime().getFalse();
    }

    @JRubyMethod(name = {"even?"})
    public RubyBoolean even_p() {
        if(value%2 == 0) {
            return getRuntime().getTrue();
        }
        return getRuntime().getFalse();
    }

    @JRubyMethod
    public IRubyObject pred() {
        return getRuntime().newFixnum(value-1);
    }

    public IRubyObject idiv(ThreadContext context, IRubyObject other, String method) {
        if (other instanceof RubyFixnum) {
            long x = value;
            long y = ((RubyFixnum) other).value;
            
            if (y == 0) {
                throw context.getRuntime().newZeroDivisionError();
            }
            
            long div = x / y;
            long mod = x % y;

            if (mod < 0 && y > 0 || mod > 0 && y < 0) {
                div -= 1;
            }

            return context.getRuntime().newFixnum(div);
        } 
        return coerceBin(context, method, other);
    }
        
    /** fix_mod
     * 
     */
    @JRubyMethod(name = {"%", "modulo"})
    public IRubyObject op_mod(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyFixnum) {
            // Java / and % are not the same as ruby
            long x = value;
            long y = ((RubyFixnum) other).value;

            if (y == 0) {
                throw context.getRuntime().newZeroDivisionError();
            }

            long mod = x % y;

            if (mod < 0 && y > 0 || mod > 0 && y < 0) {
                mod += y;
            }

            return context.getRuntime().newFixnum(mod);
        }
        return coerceBin(context, "%", other);
    }
                
    /** fix_divmod
     * 
     */
    @JRubyMethod
    @Override
    public IRubyObject divmod(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyFixnum) {
            long x = value;
            long y = ((RubyFixnum) other).value;
            final Ruby runtime = context.getRuntime();

            if (y == 0) {
                throw runtime.newZeroDivisionError();
            }

            long div = x / y;
            long mod = x % y;

            if (mod < 0 && y > 0 || mod > 0 && y < 0) {
                div -= 1;
                mod += y;
            }

            IRubyObject fixDiv = RubyFixnum.newFixnum(runtime, div);
            IRubyObject fixMod = RubyFixnum.newFixnum(runtime, mod);

            return RubyArray.newArray(runtime, fixDiv, fixMod);

        }
        return coerceBin(context, "divmod", other);
    }
    	
    /** fix_quo
     * 
     */
    @JRubyMethod(name = "quo", compat = CompatVersion.RUBY1_8)
    public IRubyObject quo(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyFixnum) {
            return RubyFloat.newFloat(context.getRuntime(), (double) value / (double) ((RubyFixnum) other).value);
        }
        return coerceBin(context, "quo", other);
    }

    /** fix_pow 
     * 
     */
    @JRubyMethod(name = "**")
    public IRubyObject op_pow(ThreadContext context, IRubyObject other) {
        if(other instanceof RubyFixnum) {
            long b = ((RubyFixnum) other).value;

            if (b == 0) {
                return RubyFixnum.one(context.getRuntime());
            }
            if (b == 1) {
                return this;
            }
            if (b > 0) {
                return RubyBignum.newBignum(context.getRuntime(), value).op_pow(context, other);
            }
            return RubyFloat.newFloat(context.getRuntime(), Math.pow(value, b));
        } else if (other instanceof RubyFloat) {
            return RubyFloat.newFloat(context.getRuntime(), Math.pow(value, ((RubyFloat) other)
                    .getDoubleValue()));
        }
        return coerceBin(context, "**", other);
    }
    
    /** fix_pow 
     * 
     */
    @JRubyMethod(name = "**", compat = CompatVersion.RUBY1_9)
    public IRubyObject op_pow_19(ThreadContext context, IRubyObject other) {
        Ruby runtime = context.getRuntime();
        long a = value;
        if (other instanceof RubyFixnum) {
            long b = ((RubyFixnum) other).value;

            if (b < 0) {
                return RubyRational.newRationalRaw(context.getRuntime(), this).callMethod(context, "**", other);
            }

            if (b == 0) return RubyFixnum.one(runtime);
            if (b == 1) return this;
             
            if (a == 0) {
                return b > 0 ? RubyFixnum.zero(runtime) : RubyNumeric.dbl2num(runtime, 1.0 / 0.0);
            }
            if (a == 1) return RubyFixnum.one(runtime);
            if (a == -1) {
                return b % 2 == 0 ? RubyFixnum.one(runtime) : RubyFixnum.minus_one(runtime);
            }
            return Numeric.int_pow(context, a, b);
        } else if (other instanceof RubyBignum) {
            if (other.callMethod(context, "<", RubyFixnum.zero(runtime)).isTrue()) {
                return RubyRational.newRationalRaw(runtime, this).callMethod(context, "**", other);
            }
            if (a == 0) return RubyFixnum.zero(runtime);
            if (a == 1) return RubyFixnum.one(runtime);
            if (a == -1) {
                return RubyInteger.even_p(context, other).isTrue() ? RubyFixnum.one(runtime) : RubyFixnum.minus_one(runtime);
            }
            RubyBignum.newBignum(runtime, RubyBignum.fix2big(this)).op_pow(context, other);
        } else if (other instanceof RubyFloat) {
            return RubyFloat.newFloat(context.getRuntime(), Math.pow(a, ((RubyFloat) other).getDoubleValue()));
        }
        return coerceBin(context, "**", other);
    }
    
            
    /** fix_abs
     * 
     */
    @JRubyMethod
    public IRubyObject abs() {
        if (value < 0) {
            // A gotcha for Long.MIN_VALUE: value = -value
            if (value == Long.MIN_VALUE) {
                return RubyBignum.newBignum(
                        getRuntime(), BigInteger.valueOf(value).negate());
            }
            return RubyFixnum.newFixnum(getRuntime(), -value);
        }
        return this;
    }
            
    /** fix_equal
     * 
     */
    @JRubyMethod(name = "==")
    @Override
    public IRubyObject op_equal(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyFixnum) {
            return RubyBoolean.newBoolean(context.getRuntime(), value == ((RubyFixnum) other).value);
        }
        return super.op_num_equal(context, other);
    }

    /** fix_cmp
     * 
     */
    @JRubyMethod(name = "<=>")
    public IRubyObject op_cmp(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyFixnum) {
            return compareFixnum(context, (RubyFixnum)other);
        }
        return coerceCmp(context, "<=>", other);
    }
    
    private IRubyObject compareFixnum(ThreadContext context, RubyFixnum other) {
        long otherValue = ((RubyFixnum) other).value;
        if (value == otherValue) {
            return RubyFixnum.zero(context.getRuntime());
        }
        if (value > otherValue) {
            return RubyFixnum.one(context.getRuntime());
        }
        return RubyFixnum.minus_one(context.getRuntime());
    }

    /** fix_gt
     * 
     */
    @JRubyMethod(name = ">")
    public IRubyObject op_gt(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyFixnum) {
            return RubyBoolean.newBoolean(context.getRuntime(), value > ((RubyFixnum) other).value);
    }
        return coerceRelOp(context, ">", other);
    }

    /** fix_ge
     * 
     */
    @JRubyMethod(name = ">=")
    public IRubyObject op_ge(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyFixnum) {
            return RubyBoolean.newBoolean(context.getRuntime(), value >= ((RubyFixnum) other).value);
            }
        return coerceRelOp(context, ">=", other);
    }

    /** fix_lt
     * 
     */
    @JRubyMethod(name = "<")
    public IRubyObject op_lt(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyFixnum) {
            return RubyBoolean.newBoolean(context.getRuntime(), value < ((RubyFixnum) other).value);
        }
        
        return coerceRelOp(context, "<", other);
    }
        
    /** fix_le
     * 
     */
    @JRubyMethod(name = "<=")
    public IRubyObject op_le(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyFixnum) {
            return RubyBoolean.newBoolean(context.getRuntime(), value <= ((RubyFixnum) other).value);
        }
        
        return coerceRelOp(context, "<=", other);
    }

    /** fix_rev
     * 
     */
    @JRubyMethod(name = "~")
    public IRubyObject op_neg() {
        return newFixnum(~value);
    }
    	
    /** fix_and
     * 
     */
    @JRubyMethod(name = "&")
    public IRubyObject op_and(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyFixnum || (other = fixCoerce(other)) instanceof RubyFixnum) {
            return newFixnum(context.getRuntime(), value & ((RubyFixnum) other).value);
        }
        return ((RubyBignum) other).op_and(context, this);
    }

    /** fix_or 
     * 
     */
    @JRubyMethod(name = "|")
    public IRubyObject op_or(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyFixnum || (other = fixCoerce(other)) instanceof RubyFixnum) {
            return newFixnum(context.getRuntime(), value | ((RubyFixnum) other).value);
        }
        return ((RubyBignum) other).op_or(context, this);
    }

    /** fix_xor 
     * 
     */
    @JRubyMethod(name = "^")
    public IRubyObject op_xor(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyFixnum || (other = fixCoerce(other)) instanceof RubyFixnum) {
            return newFixnum(context.getRuntime(), value ^ ((RubyFixnum) other).value);
        }
        return ((RubyBignum) other).op_xor(context, this); 
    }

    /** fix_aref 
     * 
     */
    @JRubyMethod(name = "[]")
    public IRubyObject op_aref(IRubyObject other) {
        if(!(other instanceof RubyFixnum) && !((other = fixCoerce(other)) instanceof RubyFixnum)) {
            RubyBignum big = (RubyBignum) other;
            RubyObject tryFix = RubyBignum.bignorm(getRuntime(), big.getValue());
            if (!(tryFix instanceof RubyFixnum)) {
                return big.getValue().signum() == 0 || value >= 0 ? RubyFixnum.zero(getRuntime()) : RubyFixnum.one(getRuntime());
            }
        }

        long otherValue = fix2long(other);

        if (otherValue < 0) return RubyFixnum.zero(getRuntime());

        if (BIT_SIZE - 1 < otherValue) {
            return value < 0 ? RubyFixnum.one(getRuntime()) : RubyFixnum.zero(getRuntime());
        }

        return (value & (1L << otherValue)) == 0 ? RubyFixnum.zero(getRuntime()) : RubyFixnum.one(getRuntime());
    }

    /** fix_lshift 
     * 
     */
    @JRubyMethod(name = "<<")
    public IRubyObject op_lshift(IRubyObject other) {
        if (!(other instanceof RubyFixnum)) return RubyBignum.newBignum(getRuntime(), value).op_lshift(other);

        long width = ((RubyFixnum)other).getLongValue();

        return width < 0 ? rshift(-width) : lshift(width); 
    }
    
    private IRubyObject lshift(long width) {
        if (width > BIT_SIZE - 1 || ((~0L << BIT_SIZE - width - 1) & value) != 0) {
            return RubyBignum.newBignum(getRuntime(), value).op_lshift(RubyFixnum.newFixnum(getRuntime(), width));
        }
        return RubyFixnum.newFixnum(getRuntime(), value << width);
    }

    /** fix_rshift 
     * 
     */
    @JRubyMethod(name = ">>")
    public IRubyObject op_rshift(IRubyObject other) {
        if (!(other instanceof RubyFixnum)) return RubyBignum.newBignum(getRuntime(), value).op_rshift(other);

        long width = ((RubyFixnum)other).getLongValue();

        if (width == 0) return this;

        return width < 0 ? lshift(-width) : rshift(width);  
    }
    
    private IRubyObject rshift(long width) { 
        if (width >= BIT_SIZE - 1) {
            return value < 0 ? RubyFixnum.minus_one(getRuntime()) : RubyFixnum.zero(getRuntime()); 
        }
        return RubyFixnum.newFixnum(getRuntime(), value >> width);
    }

    /** fix_to_f 
     * 
     */
    @JRubyMethod
    public IRubyObject to_f() {
        return RubyFloat.newFloat(getRuntime(), (double) value);
    }

    /** fix_size 
     * 
     */
    @JRubyMethod
    public IRubyObject size() {
        return newFixnum((long) ((BIT_SIZE + 7) / 8));
    }

    /** fix_zero_p 
     * 
     */
    @JRubyMethod(name = "zero?")
    public IRubyObject zero_p() {
        return RubyBoolean.newBoolean(getRuntime(), value == 0);
    }

    @JRubyMethod
    @Override
    public IRubyObject id() {
        if (value <= Long.MAX_VALUE / 2 && value >= Long.MIN_VALUE / 2) {
            return newFixnum(2 * value + 1);
        }
        
        return super.id();
    }

    @Override
    public IRubyObject taint(ThreadContext context) {
        return this;
    }

    @Override
    public IRubyObject freeze(ThreadContext context) {
        return this;
    }
    
    // Piece of mri rb_to_id
    @Override
    public String asJavaString() {
        getRuntime().getWarnings().warn(ID.FIXNUMS_NOT_SYMBOLS, "do not use Fixnums as Symbols");
        
        // FIXME: I think this chunk is equivalent to MRI id2name (and not our public method 
        // id2name).  Make into method if used more than once.  
        RubySymbol symbol = RubySymbol.getSymbolLong(getRuntime(), value);
        
        if (symbol == null) {
            throw getRuntime().newArgumentError("" + value + " is not a symbol");
        }
        
        return symbol.asJavaString();
    }

    public static RubyFixnum unmarshalFrom(UnmarshalStream input) throws java.io.IOException {
        return input.getRuntime().newFixnum(input.unmarshalInt());
    }

    /*  ================
     *  Singleton Methods
     *  ================ 
     */

    /** rb_fix_induced_from
     * 
     */
    @JRubyMethod(meta = true)
    public static IRubyObject induced_from(IRubyObject recv, IRubyObject other) {
        return RubyNumeric.num2fix(other);
    }

    @Override
    public IRubyObject to_java() {
        return MiniJava.javaToRuby(getRuntime(), Long.valueOf(value));
    }

    @Override
    public IRubyObject as(Class javaClass) {
        return MiniJava.javaToRuby(getRuntime(), coerceToJavaType(getRuntime(), this, javaClass));
    }
    
    private static Object coerceToJavaType(Ruby ruby, RubyFixnum self, Class javaClass) {
        if (!Number.class.isAssignableFrom(javaClass)) {
            throw ruby.newTypeError(javaClass.getCanonicalName() + " is not a numeric type");
        }
        
        TypeCoercer coercer = JAVA_COERCERS.get(javaClass);
        
        if (coercer == null) {
            throw ruby.newTypeError("Cannot coerce Fixnum to " + javaClass.getCanonicalName());
        }
        
        return coercer.coerce(self);
    }
    
    private static final Map<Class, TypeCoercer> JAVA_COERCERS = new HashMap<Class, TypeCoercer>();
    
    static {
        TypeCoercer intCoercer = new TypeCoercer() {
            public Object coerce(IRubyObject self) {
                RubyFixnum fixnum = (RubyFixnum)self;
                
                if (fixnum.value > Integer.MAX_VALUE) {
                    throw self.getRuntime().newRangeError("Fixnum " + fixnum.value + " is too large for Java int");
                }
                
                return Integer.valueOf((int)fixnum.value);
            }
        };
        JAVA_COERCERS.put(int.class, intCoercer);
        JAVA_COERCERS.put(Integer.class, intCoercer);
    }
}
/*
 ***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002 Don Schwartz <schwardo@users.sourceforge.net>
 * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2002-2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2004 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2006 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import static org.jruby.util.Numeric.f_expt;
import static org.jruby.util.Numeric.f_mul;
import static org.jruby.util.Numeric.f_to_i;
import static org.jruby.util.Numeric.frexp;
import static org.jruby.util.Numeric.ldexp;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;

import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.marshal.MarshalStream;
import org.jruby.runtime.marshal.UnmarshalStream;

/**
  * A representation of a float object
 */
@JRubyClass(name="Float", parent="Numeric", include="Precision")
public class RubyFloat extends RubyNumeric {

    public static RubyClass createFloatClass(Ruby runtime) {
        RubyClass floatc = runtime.defineClass("Float", runtime.getNumeric(), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
        runtime.setFloat(floatc);
        floatc.index = ClassIndex.FLOAT;
        floatc.kindOf = new RubyModule.KindOf() {
            public boolean isKindOf(IRubyObject obj, RubyModule type) {
                return obj instanceof RubyFloat;
            }
        };        
        
        floatc.getSingletonClass().undefineMethod("new");
        floatc.includeModule(runtime.getPrecision());

        // Java Doubles are 64 bit long:            
        floatc.defineConstant("ROUNDS", RubyFixnum.newFixnum(runtime, 1));
        floatc.defineConstant("RADIX", RubyFixnum.newFixnum(runtime, 2));
        floatc.defineConstant("MANT_DIG", RubyFixnum.newFixnum(runtime, 53));
        floatc.defineConstant("DIG", RubyFixnum.newFixnum(runtime, 15));
        // Double.MAX_EXPONENT since Java 1.6
        floatc.defineConstant("MIN_EXP", RubyFixnum.newFixnum(runtime, -1021));
        // Double.MAX_EXPONENT since Java 1.6            
        floatc.defineConstant("MAX_EXP", RubyFixnum.newFixnum(runtime, 1024));
        floatc.defineConstant("MIN_10_EXP", RubyFixnum.newFixnum(runtime, -307));
        floatc.defineConstant("MAX_10_EXP", RubyFixnum.newFixnum(runtime, 308));
        floatc.defineConstant("MIN", RubyFloat.newFloat(runtime, Double.MIN_VALUE));
        floatc.defineConstant("MAX", RubyFloat.newFloat(runtime, Double.MAX_VALUE));
        floatc.defineConstant("EPSILON", RubyFloat.newFloat(runtime, 2.2204460492503131e-16));
        
        floatc.defineAnnotatedMethods(RubyFloat.class);

        return floatc;
    }

    private final double value;
    
    public int getNativeTypeIndex() {
        return ClassIndex.FLOAT;
    }

    public RubyFloat(Ruby runtime) {
        this(runtime, 0.0);
    }

    public RubyFloat(Ruby runtime, double value) {
        super(runtime, runtime.getFloat());
        this.value = value;
    }

    public Class<?> getJavaClass() {
        // this needs to be thought out more along with the changes in RubyFixnum
        // since "to Object" coercion will generally want to produce the same
        // type every time
//        if (value >= Float.MIN_VALUE && value <= Float.MAX_VALUE) {
//            return float.class;
//        }
        return double.class;
    }

    /** Getter for property value.
     * @return Value of property value.
     */
    public double getValue() {
        return this.value;
    }

    public double getDoubleValue() {
        return value;
    }

    public long getLongValue() {
        return (long) value;
    }
    
    public RubyFloat convertToFloat() {
    	return this;
    }

    protected int compareValue(RubyNumeric other) {
        double otherVal = other.getDoubleValue();
        return getValue() > otherVal ? 1 : getValue() < otherVal ? -1 : 0;
    }

    public static RubyFloat newFloat(Ruby runtime, double value) {
        return new RubyFloat(runtime, value);
    }

    /*  ================
     *  Instance Methods
     *  ================ 
     */

    /** rb_flo_induced_from
     * 
     */
    @JRubyMethod(required = 1, meta = true)
    public static IRubyObject induced_from(ThreadContext context, IRubyObject recv, IRubyObject number) {
        if (number instanceof RubyFixnum || number instanceof RubyBignum || number instanceof RubyRational) {
            return number.callMethod(context, MethodIndex.TO_F, "to_f");
        } else if (number instanceof RubyFloat) {
            return number;
        }
        throw recv.getRuntime().newTypeError(
                "failed to convert " + number.getMetaClass() + " into Float");
    }

    private final static DecimalFormat FORMAT = new DecimalFormat("##############0.0##############",
            new DecimalFormatSymbols(Locale.ENGLISH));

    /** flo_to_s
     * 
     */
    @JRubyMethod(name = "to_s")
    public IRubyObject to_s() {
        if (Double.isInfinite(value)) {
            return RubyString.newString(getRuntime(), value < 0 ? "-Infinity" : "Infinity");
        }

        if (Double.isNaN(value)) {
            return RubyString.newString(getRuntime(), "NaN");
        }

        String val = ""+value;

        if(val.indexOf('E') != -1) {
            String v2 = FORMAT.format(value);
            int ix = v2.length()-1;
            while(v2.charAt(ix) == '0' && v2.charAt(ix-1) != '.') {
                ix--;
            }
            if(ix > 15 || "0.0".equals(v2.substring(0,ix+1))) {
                val = val.replaceFirst("E(\\d)","e+$1").replaceFirst("E-","e-");
            } else {
                val = v2.substring(0,ix+1);
            }
        }

        return RubyString.newString(getRuntime(), val);
    }

    /** flo_coerce
     * 
     */
    @JRubyMethod(name = "coerce", required = 1)
    public IRubyObject coerce(IRubyObject other) {
        return getRuntime().newArray(RubyKernel.new_float(this, other), this);
    }

    /** flo_uminus
     * 
     */
    @JRubyMethod(name = "-@")
    public IRubyObject op_uminus() {
        return RubyFloat.newFloat(getRuntime(), -value);
    }

    /** flo_plus
     * 
     */
    @JRubyMethod(name = "+", required = 1)
    public IRubyObject op_plus(ThreadContext context, IRubyObject other) {
        switch (other.getMetaClass().index) {
        case ClassIndex.FIXNUM:
        case ClassIndex.BIGNUM:
        case ClassIndex.FLOAT:
            return RubyFloat.newFloat(getRuntime(), value + ((RubyNumeric) other).getDoubleValue());
        default:
            return coerceBin(context, "+", other);
        }
    }

    /** flo_minus
     * 
     */
    @JRubyMethod(name = "-", required = 1)
    public IRubyObject op_minus(ThreadContext context, IRubyObject other) {
        switch (other.getMetaClass().index) {
        case ClassIndex.FIXNUM:
        case ClassIndex.BIGNUM:
        case ClassIndex.FLOAT:
            return RubyFloat.newFloat(getRuntime(), value - ((RubyNumeric) other).getDoubleValue());
        default:
            return coerceBin(context, "-", other);
        }
    }

    /** flo_mul
     * 
     */
    @JRubyMethod(name = "*", required = 1)
    public IRubyObject op_mul(ThreadContext context, IRubyObject other) {
        switch (other.getMetaClass().index) {
        case ClassIndex.FIXNUM:
        case ClassIndex.BIGNUM:
        case ClassIndex.FLOAT:
            return RubyFloat.newFloat(
                    getRuntime(), value * ((RubyNumeric) other).getDoubleValue());
        default:
            return coerceBin(context, "*", other);
        }
    }
    
    /** flo_div
     * 
     */
    @JRubyMethod(name = "/", required = 1)
    public IRubyObject op_fdiv(ThreadContext context, IRubyObject other) { // don't override Numeric#div !
        switch (other.getMetaClass().index) {
        case ClassIndex.FIXNUM:
        case ClassIndex.BIGNUM:
        case ClassIndex.FLOAT:
            return RubyFloat.newFloat(getRuntime(), value / ((RubyNumeric) other).getDoubleValue());
        default:
            return coerceBin(context, "/", other);
        }
    }

    /** flo_mod
     * 
     */
    @JRubyMethod(name = {"%", "modulo"}, required = 1)
    public IRubyObject op_mod(ThreadContext context, IRubyObject other) {
        switch (other.getMetaClass().index) {
        case ClassIndex.FIXNUM:
        case ClassIndex.BIGNUM:
        case ClassIndex.FLOAT:
            double y = ((RubyNumeric) other).getDoubleValue();
            // Modelled after c ruby implementation (java /,% not same as ruby)
            double x = value;

            double mod = Math.IEEEremainder(x, y);
            if (y * mod < 0) {
                mod += y;
            }

            return RubyFloat.newFloat(getRuntime(), mod);
        default:
            return coerceBin(context, "%", other);
        }
    }

    /** flo_divmod
     * 
     */
    @JRubyMethod(name = "divmod", required = 1)
    public IRubyObject divmod(ThreadContext context, IRubyObject other) {
        switch (other.getMetaClass().index) {
        case ClassIndex.FIXNUM:
        case ClassIndex.BIGNUM:
        case ClassIndex.FLOAT:
            double y = ((RubyNumeric) other).getDoubleValue();
            double x = value;

            double mod = Math.IEEEremainder(x, y);
            // MRI behavior:
            if (Double.isNaN(mod)) {
                throw getRuntime().newFloatDomainError("NaN");
            }
            double div = Math.floor(x / y);

            if (y * mod < 0) {
                mod += y;
            }
            final Ruby runtime = getRuntime();
            IRubyObject car = dbl2num(runtime, div);
            RubyFloat cdr = RubyFloat.newFloat(runtime, mod);
            return RubyArray.newArray(runtime, car, cdr);
        default:
            return coerceBin(context, "divmod", other);
        }
    }
    	
    /** flo_pow
     * 
     */
    @JRubyMethod(name = "**", required = 1)
    public IRubyObject op_pow(ThreadContext context, IRubyObject other) {
        switch (other.getMetaClass().index) {
        case ClassIndex.FIXNUM:
        case ClassIndex.BIGNUM:
        case ClassIndex.FLOAT:
            return RubyFloat.newFloat(getRuntime(), Math.pow(value, ((RubyNumeric) other)
                    .getDoubleValue()));
        default:
            return coerceBin(context, "**", other);
        }
    }

    /** flo_eq
     * 
     */
    @JRubyMethod(name = "==", required = 1)
    public IRubyObject op_equal(ThreadContext context, IRubyObject other) {
        if (Double.isNaN(value)) {
            return getRuntime().getFalse();
        }
        switch (other.getMetaClass().index) {
        case ClassIndex.FIXNUM:
        case ClassIndex.BIGNUM:
        case ClassIndex.FLOAT:
            return RubyBoolean.newBoolean(getRuntime(), value == ((RubyNumeric) other)
                    .getDoubleValue());
        default:
            // Numeric.equal            
            return super.op_num_equal(context, other);
        }
    }

    /** flo_cmp
     * 
     */
    @JRubyMethod(name = "<=>", required = 1)
    public IRubyObject op_cmp(ThreadContext context, IRubyObject other) {
        switch (other.getMetaClass().index) {
        case ClassIndex.FIXNUM:
        case ClassIndex.BIGNUM:
        case ClassIndex.FLOAT:
            double b = ((RubyNumeric) other).getDoubleValue();
            return dbl_cmp(getRuntime(), value, b);
        default:
            return coerceCmp(context, "<=>", other);
        }
    }

    /** flo_gt
     * 
     */
    @JRubyMethod(name = ">", required = 1)
    public IRubyObject op_gt(ThreadContext context, IRubyObject other) {
        switch (other.getMetaClass().index) {
        case ClassIndex.FIXNUM:
        case ClassIndex.BIGNUM:
        case ClassIndex.FLOAT:
            double b = ((RubyNumeric) other).getDoubleValue();
            return RubyBoolean.newBoolean(getRuntime(), !Double.isNaN(b) && value > b);
        default:
            return coerceRelOp(context, ">", other);
        }
    }

    /** flo_ge
     * 
     */
    @JRubyMethod(name = ">=", required = 1)
    public IRubyObject op_ge(ThreadContext context, IRubyObject other) {
        switch (other.getMetaClass().index) {
        case ClassIndex.FIXNUM:
        case ClassIndex.BIGNUM:
        case ClassIndex.FLOAT:
            double b = ((RubyNumeric) other).getDoubleValue();
            return RubyBoolean.newBoolean(getRuntime(), !Double.isNaN(b) && value >= b);
        default:
            return coerceRelOp(context, ">=", other);
        }
    }

    /** flo_lt
     * 
     */
    @JRubyMethod(name = "<", required = 1)
    public IRubyObject op_lt(ThreadContext context, IRubyObject other) {
        switch (other.getMetaClass().index) {
        case ClassIndex.FIXNUM:
        case ClassIndex.BIGNUM:
        case ClassIndex.FLOAT:
            double b = ((RubyNumeric) other).getDoubleValue();
            return RubyBoolean.newBoolean(getRuntime(), !Double.isNaN(b) && value < b);
        default:
            return coerceRelOp(context, "<", other);
		}
    }

    /** flo_le
     * 
     */
    @JRubyMethod(name = "<=", required = 1)
    public IRubyObject op_le(ThreadContext context, IRubyObject other) {
        switch (other.getMetaClass().index) {
        case ClassIndex.FIXNUM:
        case ClassIndex.BIGNUM:
        case ClassIndex.FLOAT:
            double b = ((RubyNumeric) other).getDoubleValue();
            return RubyBoolean.newBoolean(getRuntime(), !Double.isNaN(b) && value <= b);
        default:
            return coerceRelOp(context, "<=", other);
		}
	}
	
    /** flo_eql
     * 
     */
    @JRubyMethod(name = "eql?", required = 1)
    public IRubyObject eql_p(IRubyObject other) {
        if (other instanceof RubyFloat) {
            double b = ((RubyFloat) other).value;
            if (Double.isNaN(value) || Double.isNaN(b)) {
                return getRuntime().getFalse();
            }
            if (value == b) {
                return getRuntime().getTrue();
            }
        }
        return getRuntime().getFalse();
    }

    /** flo_hash
     * 
     */
    @JRubyMethod(name = "hash")
    public RubyFixnum hash() {
        return getRuntime().newFixnum(hashCode());
    }

    public final int hashCode() {
        long l = Double.doubleToLongBits(value);
        return (int)(l ^ l >>> 32);
    }    

    /** flo_fo 
     * 
     */
    @JRubyMethod(name = "to_f")
    public IRubyObject to_f() {
        return this;
    }
        
    /** flo_abs
     * 
     */
    @JRubyMethod(name = "abs")
    public IRubyObject abs() {
        if (Double.doubleToLongBits(value) < 0) {
            return RubyFloat.newFloat(getRuntime(), Math.abs(value));
        }
        return this;
    }
    
    /** flo_zero_p
     * 
     */
    @JRubyMethod(name = "zero?")
    public IRubyObject zero_p() {
        return RubyBoolean.newBoolean(getRuntime(), value == 0.0);
    }

    /** flo_truncate
     * 
     */
    @JRubyMethod(name = {"truncate", "to_i", "to_int"})
    public IRubyObject truncate() {
        double f = value;
        if (f > 0.0) f = Math.floor(f);
        if (f < 0.0) f = Math.ceil(f);

        return dbl2num(getRuntime(), f);
    }

    /** float_to_r, float_decode
     * 
     */
    static final int DBL_MANT_DIG = 53;
    static final int FLT_RADIX = 2;
    @JRubyMethod(name = "to_r", compat = CompatVersion.RUBY1_9)
    public IRubyObject to_r(ThreadContext context) {
        long[]exp = new long[1]; 
        double f = frexp(value, exp);
        f = ldexp(f, DBL_MANT_DIG);
        long n = exp[0] - DBL_MANT_DIG;
        Ruby runtime = context.getRuntime();
        IRubyObject x = f_mul(context, f_to_i(context, runtime.newFloat(f)),
                                       f_expt(context, 
                                              RubyFixnum.newFixnum(context.getRuntime(), FLT_RADIX),
                                              RubyFixnum.newFixnum(runtime, n)));
        return x;
    }

    /** floor
     * 
     */
    @JRubyMethod(name = "floor")
    public IRubyObject floor() {
        return dbl2num(getRuntime(), Math.floor(value));
    }

    /** flo_ceil
     * 
     */
    @JRubyMethod(name = "ceil")
    public IRubyObject ceil() {
        return dbl2num(getRuntime(), Math.ceil(value));
    }

    /** flo_round
     * 
     */
    @JRubyMethod(name = "round")
    public IRubyObject round() {
        double f = value;
        if (f > 0.0) {
            f = Math.floor(f + 0.5);
        }
        if (f < 0.0) {
            f = Math.ceil(f - 0.5);
        }
        return dbl2num(getRuntime(), f);
    }
        
    /** flo_is_nan_p
     * 
     */
    @JRubyMethod(name = "nan?")
    public IRubyObject nan_p() {
        return RubyBoolean.newBoolean(getRuntime(), Double.isNaN(value));
    }

    /** flo_is_infinite_p
     * 
     */
    @JRubyMethod(name = "infinite?")
    public IRubyObject infinite_p() {
        if (Double.isInfinite(value)) {
            return RubyFixnum.newFixnum(getRuntime(), value < 0 ? -1 : 1);
        }
        return getRuntime().getNil();
    }
            
    /** flo_is_finite_p
     * 
     */
    @JRubyMethod(name = "finite?")
    public IRubyObject finite_p() {
        if (Double.isInfinite(value) || Double.isNaN(value)) {
            return getRuntime().getFalse();
        }
        return getRuntime().getTrue();
    }

    public static void marshalTo(RubyFloat aFloat, MarshalStream output) throws java.io.IOException {
        output.registerLinkTarget(aFloat);

        String strValue = aFloat.toString();
    
        if (Double.isInfinite(aFloat.value)) {
            strValue = aFloat.value < 0 ? "-inf" : "inf";
        } else if (Double.isNaN(aFloat.value)) {
            strValue = "nan";
        }
        output.writeString(strValue);
    }
        
    public static RubyFloat unmarshalFrom(UnmarshalStream input) throws java.io.IOException {
        RubyFloat result = RubyFloat.newFloat(input.getRuntime(), org.jruby.util.Convert.byteListToDouble(input.unmarshalString(),false));
        input.registerLinkTarget(result);
        return result;
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2002 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2002-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;
import org.jruby.common.IRubyWarnings.ID;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;

/**
 * GC (Garbage Collection) Module
 *
 * Note: Since we rely on Java's memory model we can't provide the
 * kind of control over garbage collection that MRI provides.
 *
 * @author Anders
 */
@JRubyModule(name="GC")
public class RubyGC {
    public static RubyModule createGCModule(Ruby runtime) {
        RubyModule result = runtime.defineModule("GC");
        runtime.setGC(result);
        
        result.defineAnnotatedMethods(RubyGC.class);
        
        return result;        
    }

    @JRubyMethod(module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject start(IRubyObject recv) {
        System.gc();
        return recv.getRuntime().getNil();
    }

    @JRubyMethod
    public static IRubyObject garbage_collect(IRubyObject recv) {
        System.gc();
        return recv.getRuntime().getNil();
    }

    @JRubyMethod(module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject enable(IRubyObject recv) {
        recv.getRuntime().getWarnings().warn(ID.EMPTY_IMPLEMENTATION, "GC.enable will not work on JRuby", "GC.enable");
        return recv.getRuntime().getNil();
    }

    @JRubyMethod(module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject disable(IRubyObject recv) {
        recv.getRuntime().getWarnings().warn(ID.EMPTY_IMPLEMENTATION, "GC.disable will not work on JRuby", "GC.disable");
        return recv.getRuntime().getNil();
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2002-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2004 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2006 Tim Azzopardi <tim@tigerfive.com>
 * Copyright (C) 2006 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * Copyright (C) 2006 Michael Studman <codehaus@michaelstudman.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import org.jruby.util.io.STDIO;
import java.util.HashMap;
import java.util.Map;

import org.jruby.anno.JRubyMethod;
import org.jruby.common.IRubyWarnings.ID;
import org.jruby.environment.OSEnvironmentReaderExcepton;
import org.jruby.environment.OSEnvironment;
import org.jruby.internal.runtime.ValueAccessor;
import org.jruby.javasupport.JavaUtil;
import org.jruby.javasupport.util.RuntimeHelpers;
import org.jruby.runtime.Constants;
import org.jruby.runtime.GlobalVariable;
import org.jruby.runtime.IAccessor;
import org.jruby.runtime.ReadonlyGlobalVariable;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.KCode;

/** This class initializes global variables and constants.
 * 
 * @author jpetersen
 */
public class RubyGlobal {
    
    /**
     * Obligate string-keyed and string-valued hash, used for ENV and ENV_JAVA
     * 
     */
    public static class StringOnlyRubyHash extends RubyHash {
        
        public StringOnlyRubyHash(Ruby runtime, Map valueMap, IRubyObject defaultValue) {
            super(runtime, valueMap, defaultValue);
        }

        @Override
        public RubyHash to_hash() {
            Ruby runtime = getRuntime();
            RubyHash hash = RubyHash.newHash(runtime);
            hash.replace(runtime.getCurrentContext(), this);
            return hash;
        }

        @Override
        public IRubyObject op_aref(ThreadContext context, IRubyObject key) {
            return super.op_aref(context, key.convertToString());
        }

        @Override
        public IRubyObject op_aset(ThreadContext context, IRubyObject key, IRubyObject value) {
            if (!key.respondsTo("to_str")) {
                throw getRuntime().newTypeError("can't convert " + key.getMetaClass() + " into String");
            }
            if (!value.respondsTo("to_str") && !value.isNil()) {
                throw getRuntime().newTypeError("can't convert " + value.getMetaClass() + " into String");
            }

            if (value.isNil()) {
                return super.delete(context, key, org.jruby.runtime.Block.NULL_BLOCK);
            }
            
            //return super.aset(getRuntime().newString("sadfasdF"), getRuntime().newString("sadfasdF"));
            return super.op_aset(context, RuntimeHelpers.invoke(context, key, "to_str"),
                    value.isNil() ? getRuntime().getNil() : RuntimeHelpers.invoke(context, value, "to_str"));
        }
        
        @JRubyMethod
        @Override
        public IRubyObject to_s(){
            return getRuntime().newString("ENV");
        }
    }
    
    public static void createGlobals(ThreadContext context, Ruby runtime) {
        runtime.defineGlobalConstant("TOPLEVEL_BINDING", runtime.newBinding());
        
        runtime.defineGlobalConstant("TRUE", runtime.getTrue());
        runtime.defineGlobalConstant("FALSE", runtime.getFalse());
        runtime.defineGlobalConstant("NIL", runtime.getNil());
        
        // define ARGV and $* for this runtime
        RubyArray argvArray = runtime.newArray();
        String[] argv = runtime.getInstanceConfig().getArgv();
        for (int i = 0; i < argv.length; i++) {
            argvArray.append(RubyString.newString(runtime, argv[i].getBytes()));
        }
        runtime.defineGlobalConstant("ARGV", argvArray);
        runtime.getGlobalVariables().defineReadonly("$*", new ValueAccessor(argvArray));

        IAccessor d = new ValueAccessor(runtime.newString(
                runtime.getInstanceConfig().displayedFileName()));
        runtime.getGlobalVariables().define("$PROGRAM_NAME", d);
        runtime.getGlobalVariables().define("$0", d);

        // Version information:
        IRubyObject version = runtime.newString(Constants.RUBY_VERSION).freeze(context);
        IRubyObject release = runtime.newString(Constants.COMPILE_DATE).freeze(context);
        IRubyObject platform = runtime.newString(Constants.PLATFORM).freeze(context);
        IRubyObject engine = runtime.newString(Constants.ENGINE).freeze(context);

        runtime.defineGlobalConstant("RUBY_VERSION", version);
        runtime.defineGlobalConstant("RUBY_PATCHLEVEL", runtime.newString(Constants.RUBY_PATCHLEVEL).freeze(context));
        runtime.defineGlobalConstant("RUBY_RELEASE_DATE", release);
        runtime.defineGlobalConstant("RUBY_PLATFORM", platform);
        runtime.defineGlobalConstant("RUBY_ENGINE", engine);

        runtime.defineGlobalConstant("VERSION", version);
        runtime.defineGlobalConstant("RELEASE_DATE", release);
        runtime.defineGlobalConstant("PLATFORM", platform);
        
        IRubyObject jrubyVersion = runtime.newString(Constants.VERSION).freeze(context);
        runtime.defineGlobalConstant("JRUBY_VERSION", jrubyVersion);
		
        GlobalVariable kcodeGV = new KCodeGlobalVariable(runtime, "$KCODE", runtime.newString("NONE"));
        runtime.defineVariable(kcodeGV);
        runtime.defineVariable(new GlobalVariable.Copy(runtime, "$-K", kcodeGV));
        IRubyObject defaultRS = runtime.newString(runtime.getInstanceConfig().getRecordSeparator()).freeze(context);
        GlobalVariable rs = new StringGlobalVariable(runtime, "$/", defaultRS);
        runtime.defineVariable(rs);
        runtime.setRecordSeparatorVar(rs);
        runtime.getGlobalVariables().setDefaultSeparator(defaultRS);
        runtime.defineVariable(new StringGlobalVariable(runtime, "$\\", runtime.getNil()));
        runtime.defineVariable(new StringGlobalVariable(runtime, "$,", runtime.getNil()));

        runtime.defineVariable(new LineNumberGlobalVariable(runtime, "$.", RubyFixnum.one(runtime)));
        runtime.defineVariable(new LastlineGlobalVariable(runtime, "$_"));
        runtime.defineVariable(new LastExitStatusVariable(runtime, "$?"));

        runtime.defineVariable(new ErrorInfoGlobalVariable(runtime, "$!", runtime.getNil()));
        runtime.defineVariable(new NonEffectiveGlobalVariable(runtime, "$=", runtime.getFalse()));

        if(runtime.getInstanceConfig().getInputFieldSeparator() == null) {
            runtime.defineVariable(new GlobalVariable(runtime, "$;", runtime.getNil()));
        } else {
            runtime.defineVariable(new GlobalVariable(runtime, "$;", RubyRegexp.newRegexp(runtime, runtime.getInstanceConfig().getInputFieldSeparator(), 0)));
        }
        
        Boolean verbose = runtime.getInstanceConfig().getVerbose();
        IRubyObject verboseValue = null;
        if (verbose == null) {
            verboseValue = runtime.getNil();
        } else if(verbose == Boolean.TRUE) {
            verboseValue = runtime.getTrue();
        } else {
            verboseValue = runtime.getFalse();
        }
        runtime.defineVariable(new VerboseGlobalVariable(runtime, "$VERBOSE", verboseValue));
        
        IRubyObject debug = runtime.newBoolean(runtime.getInstanceConfig().isDebug());
        runtime.defineVariable(new DebugGlobalVariable(runtime, "$DEBUG", debug));
        runtime.defineVariable(new DebugGlobalVariable(runtime, "$-d", debug));

        runtime.defineVariable(new SafeGlobalVariable(runtime, "$SAFE"));

        runtime.defineVariable(new BacktraceGlobalVariable(runtime, "$@"));

        IRubyObject stdin = new RubyIO(runtime, STDIO.IN);
        IRubyObject stdout = new RubyIO(runtime, STDIO.OUT);
        IRubyObject stderr = new RubyIO(runtime, STDIO.ERR);

        runtime.defineVariable(new InputGlobalVariable(runtime, "$stdin", stdin));

        runtime.defineVariable(new OutputGlobalVariable(runtime, "$stdout", stdout));
        runtime.getGlobalVariables().alias("$>", "$stdout");
        runtime.getGlobalVariables().alias("$defout", "$stdout");

        runtime.defineVariable(new OutputGlobalVariable(runtime, "$stderr", stderr));
        runtime.getGlobalVariables().alias("$deferr", "$stderr");

        runtime.defineGlobalConstant("STDIN", stdin);
        runtime.defineGlobalConstant("STDOUT", stdout);
        runtime.defineGlobalConstant("STDERR", stderr);

        runtime.defineVariable(new LoadedFeatures(runtime, "$\""));
        runtime.defineVariable(new LoadedFeatures(runtime, "$LOADED_FEATURES"));

        runtime.defineVariable(new LoadPath(runtime, "$:"));
        runtime.defineVariable(new LoadPath(runtime, "$-I"));
        runtime.defineVariable(new LoadPath(runtime, "$LOAD_PATH"));
        
        runtime.defineVariable(new MatchMatchGlobalVariable(runtime, "$&"));
        runtime.defineVariable(new PreMatchGlobalVariable(runtime, "$`"));
        runtime.defineVariable(new PostMatchGlobalVariable(runtime, "$'"));
        runtime.defineVariable(new LastMatchGlobalVariable(runtime, "$+"));
        runtime.defineVariable(new BackRefGlobalVariable(runtime, "$~"));

        // On platforms without a c-library accessable through JNA, getpid will return hashCode 
        // as $$ used to. Using $$ to kill processes could take down many runtimes, but by basing
        // $$ on getpid() where available, we have the same semantics as MRI.
        runtime.getGlobalVariables().defineReadonly("$$", new ValueAccessor(runtime.newFixnum(runtime.getPosix().getpid())));

        // after defn of $stderr as the call may produce warnings
        defineGlobalEnvConstants(runtime);
        
        // Fixme: Do we need the check or does Main.java not call this...they should consolidate 
        if (runtime.getGlobalVariables().get("$*").isNil()) {
            runtime.getGlobalVariables().defineReadonly("$*", new ValueAccessor(runtime.newArray()));
        }
        
        runtime.getGlobalVariables().defineReadonly("$-p", 
                new ValueAccessor(runtime.getInstanceConfig().isAssumePrinting() ? runtime.getTrue() : runtime.getNil()));
        runtime.getGlobalVariables().defineReadonly("$-n", 
                new ValueAccessor(runtime.getInstanceConfig().isAssumeLoop() ? runtime.getTrue() : runtime.getNil()));
        runtime.getGlobalVariables().defineReadonly("$-a", 
                new ValueAccessor(runtime.getInstanceConfig().isSplit() ? runtime.getTrue() : runtime.getNil()));
        runtime.getGlobalVariables().defineReadonly("$-l", 
                new ValueAccessor(runtime.getInstanceConfig().isProcessLineEnds() ? runtime.getTrue() : runtime.getNil()));

        // ARGF, $< object
        RubyArgsFile.initArgsFile(runtime);
    }

    private static void defineGlobalEnvConstants(Ruby runtime) {

    	Map environmentVariableMap = null;
    	OSEnvironment environment = new OSEnvironment();
    	try {
    		environmentVariableMap = environment.getEnvironmentVariableMap(runtime);
    	} catch (OSEnvironmentReaderExcepton e) {
    		// If the environment variables are not accessible shouldn't terminate 
    		runtime.getWarnings().warn(ID.MISCELLANEOUS, e.getMessage());
    	}
		
    	if (environmentVariableMap == null) {
            // if the environment variables can't be obtained, define an empty ENV
    		environmentVariableMap = new HashMap();
    	}

        StringOnlyRubyHash h1 = new StringOnlyRubyHash(runtime,
                                                       environmentVariableMap, runtime.getNil());
        h1.getSingletonClass().defineAnnotatedMethods(StringOnlyRubyHash.class);
        runtime.defineGlobalConstant("ENV", h1);

        // Define System.getProperties() in ENV_JAVA
        Map systemProps = environment.getSystemPropertiesMap(runtime);
        runtime.defineGlobalConstant("ENV_JAVA", new StringOnlyRubyHash(
                runtime, systemProps, runtime.getNil()));
        
    }

    private static class NonEffectiveGlobalVariable extends GlobalVariable {
        public NonEffectiveGlobalVariable(Ruby runtime, String name, IRubyObject value) {
            super(runtime, name, value);
        }

        @Override
        public IRubyObject set(IRubyObject value) {
            runtime.getWarnings().warn(ID.INEFFECTIVE_GLOBAL, "warning: variable " + name + " is no longer effective; ignored", name);
            return value;
        }

        @Override
        public IRubyObject get() {
            runtime.getWarnings().warn(ID.INEFFECTIVE_GLOBAL, "warning: variable " + name + " is no longer effective", name);
            return runtime.getFalse();
        }
    }

    private static class LastExitStatusVariable extends GlobalVariable {
        public LastExitStatusVariable(Ruby runtime, String name) {
            super(runtime, name, runtime.getNil());
        }
        
        @Override
        public IRubyObject get() {
            IRubyObject lastExitStatus = runtime.getCurrentContext().getLastExitStatus();
            return lastExitStatus == null ? runtime.getNil() : lastExitStatus;
        }
        
        @Override
        public IRubyObject set(IRubyObject lastExitStatus) {
            runtime.getCurrentContext().setLastExitStatus(lastExitStatus);
            
            return lastExitStatus;
        }
    }

    private static class MatchMatchGlobalVariable extends GlobalVariable {
        public MatchMatchGlobalVariable(Ruby runtime, String name) {
            super(runtime, name, runtime.getNil());
        }
        
        @Override
        public IRubyObject get() {
            return RubyRegexp.last_match(runtime.getCurrentContext().getCurrentFrame().getBackRef());
        }
    }

    private static class PreMatchGlobalVariable extends GlobalVariable {
        public PreMatchGlobalVariable(Ruby runtime, String name) {
            super(runtime, name, runtime.getNil());
        }
        
        @Override
        public IRubyObject get() {
            return RubyRegexp.match_pre(runtime.getCurrentContext().getCurrentFrame().getBackRef());
        }
    }

    private static class PostMatchGlobalVariable extends GlobalVariable {
        public PostMatchGlobalVariable(Ruby runtime, String name) {
            super(runtime, name, runtime.getNil());
        }
        
        @Override
        public IRubyObject get() {
            return RubyRegexp.match_post(runtime.getCurrentContext().getCurrentFrame().getBackRef());
        }
    }

    private static class LastMatchGlobalVariable extends GlobalVariable {
        public LastMatchGlobalVariable(Ruby runtime, String name) {
            super(runtime, name, runtime.getNil());
        }
        
        @Override
        public IRubyObject get() {
            return RubyRegexp.match_last(runtime.getCurrentContext().getCurrentFrame().getBackRef());
        }
    }

    private static class BackRefGlobalVariable extends GlobalVariable {
        public BackRefGlobalVariable(Ruby runtime, String name) {
            super(runtime, name, runtime.getNil());
        }
        
        @Override
        public IRubyObject get() {
            return RuntimeHelpers.getBackref(runtime, runtime.getCurrentContext());
        }

        @Override
        public IRubyObject set(IRubyObject value) {
            RuntimeHelpers.setBackref(runtime, runtime.getCurrentContext(), value);
            return value;
        }
    }

    // Accessor methods.

    private static class LineNumberGlobalVariable extends GlobalVariable {
        public LineNumberGlobalVariable(Ruby runtime, String name, RubyFixnum value) {
            super(runtime, name, value);
        }

        @Override
        public IRubyObject set(IRubyObject value) {
            RubyArgsFile.setCurrentLineNumber(runtime.getGlobalVariables().get("$<"),RubyNumeric.fix2int(value));
            return super.set(value);
        }
    }

    private static class ErrorInfoGlobalVariable extends GlobalVariable {
        public ErrorInfoGlobalVariable(Ruby runtime, String name, IRubyObject value) {
            super(runtime, name, null);
            set(value);
        }

        @Override
        public IRubyObject set(IRubyObject value) {
            if (!value.isNil() &&
                    !runtime.getException().isInstance(value) &&
                    !(JavaUtil.isJavaObject(value) && JavaUtil.unwrapJavaObject(value) instanceof Exception)) {
                throw runtime.newTypeError("assigning non-exception to $!");
            }
            
            return runtime.getCurrentContext().setErrorInfo(value);
        }

        @Override
        public IRubyObject get() {
            return runtime.getCurrentContext().getErrorInfo();
        }
    }

    // FIXME: move out of this class!
    public static class StringGlobalVariable extends GlobalVariable {
        public StringGlobalVariable(Ruby runtime, String name, IRubyObject value) {
            super(runtime, name, value);
        }

        @Override
        public IRubyObject set(IRubyObject value) {
            if (!value.isNil() && ! (value instanceof RubyString)) {
                throw runtime.newTypeError("value of " + name() + " must be a String");
            }
            return super.set(value);
        }
    }

    public static class KCodeGlobalVariable extends GlobalVariable {
        public KCodeGlobalVariable(Ruby runtime, String name, IRubyObject value) {
            super(runtime, name, value);
        }

        @Override
        public IRubyObject get() {
            return runtime.getKCode().kcode(runtime);
        }

        @Override
        public IRubyObject set(IRubyObject value) {
            runtime.setKCode(KCode.create(runtime, value.convertToString().toString()));
            return value;
        }
    }

    private static class SafeGlobalVariable extends GlobalVariable {
        public SafeGlobalVariable(Ruby runtime, String name) {
            super(runtime, name, null);
        }

        @Override
        public IRubyObject get() {
            return runtime.newFixnum(runtime.getSafeLevel());
        }

        @Override
        public IRubyObject set(IRubyObject value) {
//            int level = RubyNumeric.fix2int(value);
//            if (level < runtime.getSafeLevel()) {
//            	throw runtime.newSecurityError("tried to downgrade safe level from " + 
//            			runtime.getSafeLevel() + " to " + level);
//            }
//            runtime.setSafeLevel(level);
            // thread.setSafeLevel(level);
            runtime.getWarnings().warn(ID.SAFE_NOT_SUPPORTED, "SAFE levels are not supported in JRuby");
            return RubyFixnum.newFixnum(runtime, runtime.getSafeLevel());
        }
    }

    private static class VerboseGlobalVariable extends GlobalVariable {
        public VerboseGlobalVariable(Ruby runtime, String name, IRubyObject initialValue) {
            super(runtime, name, initialValue);
            set(initialValue);
        }
        
        @Override
        public IRubyObject get() {
            return runtime.getVerbose();
        }

        @Override
        public IRubyObject set(IRubyObject newValue) {
            if (newValue.isNil()) {
                runtime.setVerbose(newValue);
            } else {
                runtime.setVerbose(runtime.newBoolean(newValue.isTrue()));
            }

            return newValue;
        }
    }

    private static class DebugGlobalVariable extends GlobalVariable {
        public DebugGlobalVariable(Ruby runtime, String name, IRubyObject initialValue) {
            super(runtime, name, initialValue);
            set(initialValue);
        }

        @Override
        public IRubyObject get() {
            return runtime.getDebug();
        }

        @Override
        public IRubyObject set(IRubyObject newValue) {
            if (newValue.isNil()) {
                runtime.setDebug(newValue);
            } else {
                runtime.setDebug(runtime.newBoolean(newValue.isTrue()));
            }

            return newValue;
        }
    }

    private static class BacktraceGlobalVariable extends GlobalVariable {
        public BacktraceGlobalVariable(Ruby runtime, String name) {
            super(runtime, name, null);
        }

        @Override
        public IRubyObject get() {
            IRubyObject errorInfo = runtime.getGlobalVariables().get("$!");
            IRubyObject backtrace = errorInfo.isNil() ? runtime.getNil() : errorInfo.callMethod(errorInfo.getRuntime().getCurrentContext(), "backtrace");
            //$@ returns nil if $!.backtrace is not an array
            if (!(backtrace instanceof RubyArray)) {
                backtrace = runtime.getNil();
            }
            return backtrace;
        }

        @Override
        public IRubyObject set(IRubyObject value) {
            if (runtime.getGlobalVariables().get("$!").isNil()) {
                throw runtime.newArgumentError("$! not set.");
            }
            runtime.getGlobalVariables().get("$!").callMethod(value.getRuntime().getCurrentContext(), "set_backtrace", value);
            return value;
        }
    }

    private static class LastlineGlobalVariable extends GlobalVariable {
        public LastlineGlobalVariable(Ruby runtime, String name) {
            super(runtime, name, null);
        }

        @Override
        public IRubyObject get() {
            return RuntimeHelpers.getLastLine(runtime, runtime.getCurrentContext());
        }

        @Override
        public IRubyObject set(IRubyObject value) {
            RuntimeHelpers.setLastLine(runtime, runtime.getCurrentContext(), value);
            return value;
        }
    }

    private static class InputGlobalVariable extends GlobalVariable {
        public InputGlobalVariable(Ruby runtime, String name, IRubyObject value) {
            super(runtime, name, value);
        }

        @Override
        public IRubyObject set(IRubyObject value) {
            if (value == get()) {
                return value;
            }
            
            return super.set(value);
        }
    }

    private static class OutputGlobalVariable extends GlobalVariable {
        public OutputGlobalVariable(Ruby runtime, String name, IRubyObject value) {
            super(runtime, name, value);
        }

        @Override
        public IRubyObject set(IRubyObject value) {
            if (value == get()) {
                return value;
            }
            if (value instanceof RubyIO) {
                RubyIO io = (RubyIO)value;
                
                // HACK: in order to have stdout/err act like ttys and flush always,
                // we set anything assigned to stdout/stderr to sync
                io.getHandler().setSync(true);
            }

            if (!value.respondsTo("write")) {
                throw runtime.newTypeError(name() + " must have write method, " +
                                    value.getType().getName() + " given");
            }

            return super.set(value);
        }
    }

    private static class LoadPath extends ReadonlyGlobalVariable {
        public LoadPath(Ruby runtime, String name) {
            super(runtime, name, null);
        }
        
        /**
         * @see org.jruby.runtime.GlobalVariable#get()
         */
        @Override
        public IRubyObject get() {
            return runtime.getLoadService().getLoadPath();
        }
    }

    private static class LoadedFeatures extends ReadonlyGlobalVariable {
        public LoadedFeatures(Ruby runtime, String name) {
            super(runtime, name, null);
        }
        
        /**
         * @see org.jruby.runtime.GlobalVariable#get()
         */
        @Override
        public IRubyObject get() {
            return runtime.getLoadService().getLoadedFeatures();
        }
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Chad Fowler <chadfowler@chadfowler.com>
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004-2006 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2005 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2006 Ola Bini <Ola.Bini@ki.se>
 * Copyright (C) 2006 Tim Azzopardi <tim@tigerfive.com>
 * Copyright (C) 2006 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * Copyright (C) 2007 MenTaLguY <mental@rydia.net>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.io.IOException;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.common.IRubyWarnings.ID;
import org.jruby.javasupport.JavaUtil;
import org.jruby.javasupport.util.RuntimeHelpers;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.marshal.MarshalStream;
import org.jruby.runtime.marshal.UnmarshalStream;
import org.jruby.util.ByteList;
import org.jruby.util.TypeConverter;

// Design overview:
//
// RubyHash is implemented as hash table with a singly-linked list of
// RubyHash.RubyHashEntry objects for each bucket.  RubyHashEntry objects
// are also kept in a doubly-linked list which reflects their insertion
// order and is used for iteration.  For simplicity, this latter list is
// circular; a dummy RubyHashEntry, RubyHash.head, is used to mark the
// ends of the list.
//
// When an entry is removed from the table, it is also removed from the
// doubly-linked list.  However, while the reference to the previous
// RubyHashEntry is cleared (to mark the entry as dead), the reference
// to the next RubyHashEntry is preserved so that iterators are not
// invalidated: any iterator with a reference to a dead entry can climb
// back up into the list of live entries by chasing next references until
// it finds a live entry (or head).
//
// Ordinarily, this scheme would require O(N) time to clear a hash (since
// each RubyHashEntry would need to be visited and unlinked from the
// iteration list), but RubyHash also maintains a generation count.  Every
// time the hash is cleared, the doubly-linked list is simply discarded and
// the generation count incremented.  Iterators check to see whether the
// generation count has changed; if it has, they reset themselves back to
// the new start of the list.
//
// This design means that iterators are never invalidated by changes to the
// hashtable, and they do not need to modify the structure during their
// lifecycle.
//

/** Implementation of the Hash class.
 *
 *  Concurrency: no synchronization is required among readers, but
 *  all users must synchronize externally with writers.
 *
 */
@JRubyClass(name = "Hash", include="Enumerable")
public class RubyHash extends RubyObject implements Map {

    public static RubyClass createHashClass(Ruby runtime) {
        RubyClass hashc = runtime.defineClass("Hash", runtime.getObject(), HASH_ALLOCATOR);
        runtime.setHash(hashc);
        hashc.index = ClassIndex.HASH;
        hashc.kindOf = new RubyModule.KindOf() {
            public boolean isKindOf(IRubyObject obj, RubyModule type) {
                return obj instanceof RubyHash;
            }
        };

        hashc.includeModule(runtime.getEnumerable());

        hashc.defineAnnotatedMethods(RubyHash.class);

        return hashc;
    }

    private final static ObjectAllocator HASH_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return new RubyHash(runtime, klass);
        }
    };

    public int getNativeTypeIndex() {
        return ClassIndex.HASH;
    }

    /** rb_hash_s_create
     *
     */
    @JRubyMethod(name = "[]", rest = true, frame = true, meta = true)
    public static IRubyObject create(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        RubyClass klass = (RubyClass) recv;
        Ruby runtime = context.getRuntime();
        RubyHash hash;

        if (args.length == 1) {
            IRubyObject tmp = TypeConverter.convertToTypeWithCheck(
                    args[0], runtime.getHash(), MethodIndex.TO_HASH, "to_hash");

            if (!tmp.isNil()) {
                RubyHash otherHash = (RubyHash) tmp;
                return new RubyHash(runtime, klass, otherHash);
            }
        }

        if ((args.length & 1) != 0) {
            throw runtime.newArgumentError("odd number of args for Hash");
        }

        hash = (RubyHash)klass.allocate();
        for (int i=0; i < args.length; i+=2) hash.op_aset(context, args[i], args[i+1]);

        return hash;
    }

    /** rb_hash_new
     *
     */
    public static final RubyHash newHash(Ruby runtime) {
        return new RubyHash(runtime);
    }

    /** rb_hash_new
     *
     */
    public static final RubyHash newHash(Ruby runtime, Map valueMap, IRubyObject defaultValue) {
        assert defaultValue != null;

        return new RubyHash(runtime, valueMap, defaultValue);
    }

    private RubyHashEntry[] table;
    private int size = 0;
    private int threshold;

    private static final int PROCDEFAULT_HASH_F = 1 << 10;

    private IRubyObject ifNone;

    private RubyHash(Ruby runtime, RubyClass klass, RubyHash other) {
        super(runtime, klass);
        this.ifNone = runtime.getNil();
        threshold = INITIAL_THRESHOLD;
        table = other.internalCopyTable(head);
        size = other.size;
    }

    public RubyHash(Ruby runtime, RubyClass klass) {
        super(runtime, klass);
        this.ifNone = runtime.getNil();
        alloc();
    }

    public RubyHash(Ruby runtime) {
        this(runtime, runtime.getNil());
    }

    public RubyHash(Ruby runtime, IRubyObject defaultValue) {
        super(runtime, runtime.getHash());
        this.ifNone = defaultValue;
        alloc();
    }

    /*
     *  Constructor for internal usage (mainly for Array#|, Array#&, Array#- and Array#uniq)
     *  it doesn't initialize ifNone field
     */
    RubyHash(Ruby runtime, boolean objectSpace) {
        super(runtime, runtime.getHash(), objectSpace);
        alloc();
    }

    // TODO should this be deprecated ? (to be efficient, internals should deal with RubyHash directly)
    public RubyHash(Ruby runtime, Map valueMap, IRubyObject defaultValue) {
        super(runtime, runtime.getHash());
        this.ifNone = defaultValue;
        alloc();

        for (Iterator iter = valueMap.entrySet().iterator();iter.hasNext();) {
            Map.Entry e = (Map.Entry)iter.next();
            internalPut((IRubyObject)e.getKey(), (IRubyObject)e.getValue());
        }
    }

    private final void alloc() {
        threshold = INITIAL_THRESHOLD;
        generation++;
        head.nextAdded = head.prevAdded = head;
        table = new RubyHashEntry[MRI_HASH_RESIZE ? MRI_INITIAL_CAPACITY : JAVASOFT_INITIAL_CAPACITY];
    }

    /* ============================
     * Here are hash internals
     * (This could be extracted to a separate class but it's not too large though)
     * ============================
     */

    private static final int MRI_PRIMES[] = {
        8 + 3, 16 + 3, 32 + 5, 64 + 3, 128 + 3, 256 + 27, 512 + 9, 1024 + 9, 2048 + 5, 4096 + 3,
        8192 + 27, 16384 + 43, 32768 + 3, 65536 + 45, 131072 + 29, 262144 + 3, 524288 + 21, 1048576 + 7,
        2097152 + 17, 4194304 + 15, 8388608 + 9, 16777216 + 43, 33554432 + 35, 67108864 + 15,
        134217728 + 29, 268435456 + 3, 536870912 + 11, 1073741824 + 85, 0
    };

    private static final int JAVASOFT_INITIAL_CAPACITY = 8; // 16 ?
    private static final int MRI_INITIAL_CAPACITY = MRI_PRIMES[0];

    private static final int INITIAL_THRESHOLD = JAVASOFT_INITIAL_CAPACITY - (JAVASOFT_INITIAL_CAPACITY >> 2);
    private static final int MAXIMUM_CAPACITY = 1 << 30;

    private static final RubyHashEntry NO_ENTRY = new RubyHashEntry();
    private int generation = 0; // generation count for O(1) clears
    private final RubyHashEntry head = new RubyHashEntry();
    { head.prevAdded = head.nextAdded = head; }

    static final class RubyHashEntry implements Map.Entry {
        private IRubyObject key;
        private IRubyObject value;
        private RubyHashEntry next;
        private RubyHashEntry prevAdded;
        private RubyHashEntry nextAdded;
        private int hash;

        RubyHashEntry() {
            key = NEVER;
        }

        RubyHashEntry(int h, IRubyObject k, IRubyObject v, RubyHashEntry e, RubyHashEntry head) {
            key = k; value = v; next = e; hash = h;
            prevAdded = head.prevAdded;
            nextAdded = head;
            nextAdded.prevAdded = this;
            prevAdded.nextAdded = this;
        }

        public void detach() {
            if (prevAdded != null) {
                prevAdded.nextAdded = nextAdded;
                nextAdded.prevAdded = prevAdded;
                prevAdded = null;
            }
        }

        public boolean isLive() {
            return prevAdded != null;
        }

        public Object getKey() {
            return key;
        }
        public Object getJavaifiedKey(){
            return JavaUtil.convertRubyToJava(key);
        }

        public Object getValue() {
            return value;
        }
        public Object getJavaifiedValue() {
            return JavaUtil.convertRubyToJava(value);
        }

        public Object setValue(Object value) {
            IRubyObject oldValue = this.value;
            if (value instanceof IRubyObject) {
                this.value = (IRubyObject)value;
            } else {
                throw new UnsupportedOperationException("directEntrySet() doesn't support setValue for non IRubyObject instance entries, convert them manually or use entrySet() instead");
            }
            return oldValue;
        }

        public boolean equals(Object other){
            if(!(other instanceof RubyHashEntry)) return false;
            RubyHashEntry otherEntry = (RubyHashEntry)other;
            if(key == otherEntry.key || key.eql(otherEntry.key)){
                if(value == otherEntry.value || value.equals(otherEntry.value)) return true;
            }
            return false;
        }

        public int hashCode(){
            return key.hashCode() ^ value.hashCode();
        }
    }

    private static int JavaSoftHashValue(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

    private static int JavaSoftBucketIndex(final int h, final int length) {
        return h & (length - 1);
    }

    private static int MRIHashValue(int h) {
        return h & HASH_SIGN_BIT_MASK;
    }

    private static final int HASH_SIGN_BIT_MASK = ~(1 << 31);
    private static int MRIBucketIndex(final int h, final int length) {
        return (h % length);
    }

    private final void resize(int newCapacity) {
        final RubyHashEntry[] oldTable = table;
        final RubyHashEntry[] newTable = new RubyHashEntry[newCapacity];
        for (int j = 0; j < oldTable.length; j++) {
            RubyHashEntry entry = oldTable[j];
            oldTable[j] = null;
            while (entry != null) {
                RubyHashEntry next = entry.next;
                int i = bucketIndex(entry.hash, newCapacity);
                entry.next = newTable[i];
                newTable[i] = entry;
                entry = next;
            }
        }
        table = newTable;
    }

    private final void JavaSoftCheckResize() {
        if (size > threshold) {
            int oldCapacity = table.length;
            if (oldCapacity == MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return;
            }
            int newCapacity = table.length << 1;
            resize(newCapacity);
            threshold = newCapacity - (newCapacity >> 2);
        }
    }

    private static final int MIN_CAPA = 8;
    private static final int ST_DEFAULT_MAX_DENSITY = 5;
    private final void MRICheckResize() {
        if (size / table.length > ST_DEFAULT_MAX_DENSITY) {
            int forSize = table.length + 1; // size + 1;
            for (int i=0, newCapacity = MIN_CAPA; i < MRI_PRIMES.length; i++, newCapacity <<= 1) {
                if (newCapacity > forSize) {
                    resize(MRI_PRIMES[i]);
                    return;
                }
            }
            return; // suboptimal for large hashes (> 1073741824 + 85 entries) not very likely to happen
        }
    }
    // ------------------------------
    private static boolean MRI_HASH = true;
    private static boolean MRI_HASH_RESIZE = true;

    private static int hashValue(final int h) {
        return MRI_HASH ? MRIHashValue(h) : JavaSoftHashValue(h);
    }

    private static int bucketIndex(final int h, final int length) {
        return MRI_HASH ? MRIBucketIndex(h, length) : JavaSoftBucketIndex(h, length);
    }

    private void checkResize() {
        if (MRI_HASH_RESIZE) MRICheckResize(); else JavaSoftCheckResize();
    }
    // ------------------------------
    public static long collisions = 0;

    // put implementation

    private final void internalPut(final IRubyObject key, final IRubyObject value) {
        internalPut(key, value, true);
    }

    private final void internalPut(final IRubyObject key, final IRubyObject value, final boolean checkForExisting) {
        checkResize();
        final int hash = hashValue(key.hashCode());
        final int i = bucketIndex(hash, table.length);

        // if (table[i] != null) collisions++;

        if (checkForExisting) {
            for (RubyHashEntry entry = table[i]; entry != null; entry = entry.next) {
                IRubyObject k;
                if (entry.hash == hash && ((k = entry.key) == key || key.eql(k))) {
                    entry.value = value;
                    return;
                }
            }
        }

        table[i] = new RubyHashEntry(hash, key, value, table[i], head);
        size++;
    }

    // get implementation

    private final IRubyObject internalGet(IRubyObject key) { // specialized for value
        return internalGetEntry(key).value;
    }

    private final RubyHashEntry internalGetEntry(IRubyObject key) {
        final int hash = hashValue(key.hashCode());
        for (RubyHashEntry entry = table[bucketIndex(hash, table.length)]; entry != null; entry = entry.next) {
            IRubyObject k;
            if (entry.hash == hash && ((k = entry.key) == key || key.eql(k))) return entry;
        }
        return NO_ENTRY;
    }

    // delete implementation


    private final RubyHashEntry internalDelete(final IRubyObject key) {
        return internalDelete(hashValue(key.hashCode()), MATCH_KEY, key);
    }

    private final RubyHashEntry internalDeleteEntry(final RubyHashEntry entry) {
        // n.b. we need to recompute the hash in case the key object was modified
        return internalDelete(hashValue(entry.key.hashCode()), MATCH_ENTRY, entry);
    }

    private final RubyHashEntry internalDelete(final int hash, final EntryMatchType matchType, final Object obj) {
        final int i = bucketIndex(hash, table.length);

        RubyHashEntry entry = table[i];
        if (entry != null) {
            RubyHashEntry prior = null;
            for (; entry != null; prior = entry, entry = entry.next) {
                if (entry.hash == hash && matchType.matches(entry, obj)) {
                    if (prior != null) {
                        prior.next = entry.next;
                    } else {
                        table[i] = entry.next;
                    }
                    entry.detach();
                    size--;
                    return entry;
                }
            }
        }

        return NO_ENTRY;
    }

    private static abstract class EntryMatchType {
        public abstract boolean matches(final RubyHashEntry entry, final Object obj);
    }

    private static final EntryMatchType MATCH_KEY = new EntryMatchType() {
        public boolean matches(final RubyHashEntry entry, final Object obj) {
            final IRubyObject key = entry.key;
            return obj == key || (((IRubyObject)obj).eql(key));
        }
    };

    private static final EntryMatchType MATCH_ENTRY = new EntryMatchType() {
        public boolean matches(final RubyHashEntry entry, final Object obj) {
            return entry.equals(obj);
        }
    };

    private final RubyHashEntry[] internalCopyTable(RubyHashEntry destHead) {
         RubyHashEntry[]newTable = new RubyHashEntry[table.length];

         for (RubyHashEntry entry = head.nextAdded; entry != head; entry = entry.nextAdded) {
             int i = bucketIndex(entry.hash, table.length);
             newTable[i] = new RubyHashEntry(entry.hash, entry.key, entry.value, newTable[i], destHead);
         }
         return newTable;
    }

    public static abstract class Visitor {
        public abstract void visit(IRubyObject key, IRubyObject value);
    }

    public void visitAll(Visitor visitor) {
        int startGeneration = generation;
        for (RubyHashEntry entry = head.nextAdded; entry != head; entry = entry.nextAdded) {
            if (startGeneration != generation) {
                startGeneration = generation;
                entry = head.nextAdded;
                if (entry == head) break;
            }
            if (entry.isLive()) visitor.visit(entry.key, entry.value);
        }
    }

    /* ============================
     * End of hash internals
     * ============================
     */

    /*  ================
     *  Instance Methods
     *  ================
     */

    /** rb_hash_initialize
     *
     */
    @JRubyMethod(name = "initialize", optional = 1, frame = true, visibility = Visibility.PRIVATE)
    public IRubyObject initialize(IRubyObject[] args, final Block block) {
        modify();

        if (block.isGiven()) {
            if (args.length > 0) throw getRuntime().newArgumentError("wrong number of arguments");
            ifNone = getRuntime().newProc(Block.Type.PROC, block);
            flags |= PROCDEFAULT_HASH_F;
        } else {
            Arity.checkArgumentCount(getRuntime(), args, 0, 1);
            if (args.length == 1) ifNone = args[0];
        }
        return this;
    }

    /** rb_hash_default
     *
     */
    @Deprecated
    public IRubyObject default_value_get(ThreadContext context, IRubyObject[] args) {
        switch (args.length) {
            case 0: return default_value_get(context);
            case 1: return default_value_get(context, args[0]);
            default: throw context.getRuntime().newArgumentError(args.length, 1);
        }
    }
    @JRubyMethod(name = "default", frame = true)
    public IRubyObject default_value_get(ThreadContext context) {
        if ((flags & PROCDEFAULT_HASH_F) != 0) {
            return getRuntime().getNil();
        }
        return ifNone;
    }
    @JRubyMethod(name = "default", frame = true)
    public IRubyObject default_value_get(ThreadContext context, IRubyObject arg) {
        if ((flags & PROCDEFAULT_HASH_F) != 0) {
            return RuntimeHelpers.invoke(context, ifNone, "call", this, arg);
        }
        return ifNone;
    }

    /** rb_hash_set_default
     *
     */
    @JRubyMethod(name = "default=", required = 1)
    public IRubyObject default_value_set(final IRubyObject defaultValue) {
        modify();

        ifNone = defaultValue;
        flags &= ~PROCDEFAULT_HASH_F;

        return ifNone;
    }

    /** rb_hash_default_proc
     *
     */
    @JRubyMethod(name = "default_proc", frame = true)
    public IRubyObject default_proc() {
        return (flags & PROCDEFAULT_HASH_F) != 0 ? ifNone : getRuntime().getNil();
    }

    /** rb_hash_modify
     *
     */
    public void modify() {
    	testFrozen("hash");
        if (isTaint() && getRuntime().getSafeLevel() >= 4) {
            throw getRuntime().newSecurityError("Insecure: can't modify hash");
        }
    }

    /** inspect_hash
     *
     */
    private IRubyObject inspectHash(final ThreadContext context) {
        final ByteList buffer = new ByteList();
        buffer.append('{');
        final boolean[] firstEntry = new boolean[1];

        firstEntry[0] = true;
        visitAll(new Visitor() {
            public void visit(IRubyObject key, IRubyObject value) {
                if (!firstEntry[0]) buffer.append(',').append(' ');

                buffer.append(inspect(context, key).getByteList());
                buffer.append('=').append('>');
                buffer.append(inspect(context, value).getByteList());
                firstEntry[0] = false;
            }
        });
        buffer.append('}');
        return getRuntime().newString(buffer);
    }

    /** rb_hash_inspect
     *
     */
    @JRubyMethod(name = "inspect")
    public IRubyObject inspect(ThreadContext context) {
        if (size == 0) return getRuntime().newString("{}");
        if (getRuntime().isInspecting(this)) return getRuntime().newString("{...}");

        try {
            getRuntime().registerInspecting(this);
            return inspectHash(context);
        } finally {
            getRuntime().unregisterInspecting(this);
        }
    }

    /** rb_hash_size
     *
     */
    @JRubyMethod(name = {"size", "length"})
    public RubyFixnum rb_size() {
        return getRuntime().newFixnum(size);
    }

    /** rb_hash_empty_p
     *
     */
    @JRubyMethod(name = "empty?")
    public RubyBoolean empty_p() {
        return size == 0 ? getRuntime().getTrue() : getRuntime().getFalse();
    }

    /** rb_hash_to_a
     *
     */
    @JRubyMethod(name = "to_a")
    public RubyArray to_a() {
        final Ruby runtime = getRuntime();
        final RubyArray result = RubyArray.newArray(runtime, size);

        visitAll(new Visitor() {
            public void visit(IRubyObject key, IRubyObject value) {
                result.append(RubyArray.newArray(runtime, key, value));
            }
        });

        result.setTaint(isTaint());
        return result;
    }

    /** rb_hash_to_s & to_s_hash
     *
     */
    @JRubyMethod(name = "to_s")
    public IRubyObject to_s() {
        if (getRuntime().isInspecting(this)) return getRuntime().newString("{...}");
        try {
            getRuntime().registerInspecting(this);
            return to_a().to_s();
        } finally {
            getRuntime().unregisterInspecting(this);
        }
    }

    /** rb_hash_rehash
     *
     */
    @JRubyMethod(name = "rehash")
    public RubyHash rehash() {
        modify();
        final RubyHashEntry[] oldTable = table;
        final RubyHashEntry[] newTable = new RubyHashEntry[oldTable.length];
        for (int j = 0; j < oldTable.length; j++) {
            RubyHashEntry entry = oldTable[j];
            oldTable[j] = null;
            while (entry != null) {
                RubyHashEntry next = entry.next;
                entry.hash = entry.key.hashCode(); // update the hash value
                int i = bucketIndex(entry.hash, newTable.length);
                entry.next = newTable[i];
                newTable[i] = entry;
                entry = next;
            }
        }
        table = newTable;
        return this;
    }

    /** rb_hash_to_hash
     *
     */
    @JRubyMethod(name = "to_hash")
    public RubyHash to_hash() {
        return this;
    }

    public RubyHash convertToHash() {
        return this;
    }

    public final void fastASet(IRubyObject key, IRubyObject value) {
        internalPut(key, value);
    }

    @Deprecated
    public IRubyObject op_aset(IRubyObject key, IRubyObject value) {
        return op_aset(getRuntime().getCurrentContext(), key, value);
    }

    /** rb_hash_aset
     *
     */
    @JRubyMethod(name = {"[]=", "store"}, required = 2)
    public IRubyObject op_aset(ThreadContext context, IRubyObject key, IRubyObject value) {
        modify();

        if (!(key instanceof RubyString)) {
            internalPut(key, value);
        } else {
            final RubyHashEntry entry = internalGetEntry(key);
            if (entry != NO_ENTRY) {
                entry.value = value;
            } else {
                RubyString realKey = (RubyString)key;
                
                if (!realKey.isFrozen()) {
                    realKey = realKey.strDup(context.getRuntime(), realKey.getMetaClass().getRealClass());;
                    realKey.setFrozen(true);
                }
                
                internalPut(realKey, value, false);
            }
        }

        return value;
    }

    /**
     * Note: this is included as a compatibility measure for AR-JDBC
     * @deprecated use RubyHash.op_aset instead
     */
    public IRubyObject aset(IRubyObject key, IRubyObject value) {
        return op_aset(getRuntime().getCurrentContext(), key, value);
    }

    /**
     * Note: this is included as a compatibility measure for Mongrel+JRuby
     * @deprecated use RubyHash.op_aref instead
     */
    public IRubyObject aref(IRubyObject key) {
        return op_aref(getRuntime().getCurrentContext(), key);
    }

    public final IRubyObject fastARef(IRubyObject key) { // retuns null when not found to avoid unnecessary getRuntime().getNil() call
        return internalGet(key);
    }

    /** rb_hash_aref
     *
     */
    @JRubyMethod(name = "[]", required = 1)
    public IRubyObject op_aref(ThreadContext context, IRubyObject key) {
        IRubyObject value;
        return ((value = internalGet(key)) == null) ? callMethod(context, MethodIndex.DEFAULT, "default", key) : value;
    }

    /** rb_hash_fetch
     *
     */
    @JRubyMethod(name = "fetch", required = 1, optional = 1, frame = true)
    public IRubyObject fetch(ThreadContext context, IRubyObject[] args, Block block) {
        if (args.length == 2 && block.isGiven()) {
            getRuntime().getWarnings().warn(ID.BLOCK_BEATS_DEFAULT_VALUE, "block supersedes default value argument");
        }

        IRubyObject value;
        if ((value = internalGet(args[0])) == null) {
            if (block.isGiven()) return block.yield(context, args[0]);
            if (args.length == 1) throw getRuntime().newIndexError("key not found");
            return args[1];
        }
        return value;
    }

    /** rb_hash_has_key
     *
     */
    @JRubyMethod(name = {"has_key?", "key?", "include?", "member?"}, required = 1)
    public RubyBoolean has_key_p(IRubyObject key) {
        return internalGetEntry(key) == NO_ENTRY ? getRuntime().getFalse() : getRuntime().getTrue();
    }

    private class Found extends RuntimeException {}

    private boolean hasValue(final ThreadContext context, final IRubyObject expected) {
        try {
            visitAll(new Visitor() {
                public void visit(IRubyObject key, IRubyObject value) {
                    if (equalInternal(context, value, expected)) {
                        throw new Found();
                    }
                }
            });
            return false;
        } catch (Found found) {
            return true;
        }
    }

    /** rb_hash_has_value
     *
     */
    @JRubyMethod(name = {"has_value?", "value?"}, required = 1)
    public RubyBoolean has_value_p(ThreadContext context, IRubyObject expected) {
        return getRuntime().newBoolean(hasValue(context, expected));
    }

    /** rb_hash_each
     *
     */
    @JRubyMethod(name = "each", frame = true)
    public RubyHash each(final ThreadContext context, final Block block) {
        final Ruby runtime = getRuntime();

        visitAll(new Visitor() {
            public void visit(IRubyObject key, IRubyObject value) {
                // rb_assoc_new equivalent
                block.yield(context, RubyArray.newArray(runtime, key, value), null, null, false);
            }
        });

        return this;
    }

    /** rb_hash_each_pair
     *
     */
    @JRubyMethod(name = "each_pair", frame = true)
    public RubyHash each_pair(final ThreadContext context, final Block block) {
        final Ruby runtime = getRuntime();

        visitAll(new Visitor() {
            public void visit(IRubyObject key, IRubyObject value) {
                // rb_yield_values(2,...) equivalent
                block.yield(context, RubyArray.newArray(runtime, key, value), null, null, true);
            }
        });

        return this;	
    }

    /** rb_hash_each_value
     *
     */
    @JRubyMethod(name = "each_value", frame = true)
    public RubyHash each_value(final ThreadContext context, final Block block) {
        visitAll(new Visitor() {
            public void visit(IRubyObject key, IRubyObject value) {
                block.yield(context, value);
            }
        });

        return this;
    }

    /** rb_hash_each_key
     *
     */
    @JRubyMethod(name = "each_key", frame = true)
    public RubyHash each_key(final ThreadContext context, final Block block) {
        visitAll(new Visitor() {
            public void visit(IRubyObject key, IRubyObject value) {
                block.yield(context, key);
            }
        });

        return this;
    }

    /** rb_hash_sort
     *
     */
    @JRubyMethod(name = "sort", frame = true)
    public RubyArray sort(Block block) {
        return to_a().sort_bang(block);
    }

    private static class FoundKey extends RuntimeException {
        public IRubyObject key;
        FoundKey(IRubyObject key) {
            super();
            this.key = key;
        }
    }

    /** rb_hash_index
     *
     */
    @JRubyMethod(name = "index", required = 1)
    public IRubyObject index(ThreadContext context, IRubyObject expected) {
        IRubyObject key = internalIndex(context, expected);
        if (key != null) {
            return key;
        } else {
            return getRuntime().getNil();
        }
    }

    private IRubyObject internalIndex(final ThreadContext context, final IRubyObject expected) {
        try {
            visitAll(new Visitor() {
                public void visit(IRubyObject key, IRubyObject value) {
                    if (equalInternal(context, value, expected)) {
                        throw new FoundKey(key);
                    }
                }
            });
            return null;
        } catch (FoundKey found) {
            return found.key;
        }
    }

    /** rb_hash_indexes
     *
     */
    @JRubyMethod(name = {"indexes", "indices"}, rest = true)
    public RubyArray indices(ThreadContext context, IRubyObject[] indices) {
        return values_at(context, indices);
    }

    /** rb_hash_keys
     *
     */
    @JRubyMethod(name = "keys")
    public RubyArray keys() {
        final Ruby runtime = getRuntime();
        final RubyArray keys = RubyArray.newArray(runtime, size);

        visitAll(new Visitor() {
            public void visit(IRubyObject key, IRubyObject value) {
                keys.append(key);
            }
        });

        return keys;
    }

    /** rb_hash_values
     *
     */
    @JRubyMethod(name = "values")
    public RubyArray rb_values() {
        final RubyArray values = RubyArray.newArray(getRuntime(), size);

        visitAll(new Visitor() {
            public void visit(IRubyObject key, IRubyObject value) {
                values.append(value);
            }
        });

        return values;
    }

    /** rb_hash_equal
     *
     */

    private static final boolean EQUAL_CHECK_DEFAULT_VALUE = false;

    private static class Mismatch extends RuntimeException {}

    @Override
    @JRubyMethod(name = "==", required = 1)
    public IRubyObject op_equal(final ThreadContext context, final IRubyObject other) {
        if (this == other) return getRuntime().getTrue();
        if (!(other instanceof RubyHash)) {
            if (other.respondsTo("to_hash") && equalInternal(context, other, this)) return getRuntime().getTrue();
            return getRuntime().getFalse();
        }

        final RubyHash otherHash = (RubyHash)other;
        if (size != otherHash.size) return getRuntime().getFalse();

        final Ruby runtime = getRuntime();

        if (EQUAL_CHECK_DEFAULT_VALUE) {
            if (!equalInternal(context, ifNone, otherHash.ifNone) &&
               (flags & PROCDEFAULT_HASH_F) != (otherHash.flags & PROCDEFAULT_HASH_F)) return runtime.getFalse();
        }

        try {
             visitAll(new Visitor() {
                 public void visit(IRubyObject key, IRubyObject value) {
                     IRubyObject otherValue = otherHash.internalGet(key);
                     if (otherValue == null || !equalInternal(context, value, otherValue)) throw new Mismatch();
                 }
             });
             return runtime.getTrue();
        } catch (Mismatch e) {
             return runtime.getFalse();
        }
    }

    /** rb_hash_shift
     *
     */
    @JRubyMethod(name = "shift")
    public IRubyObject shift(ThreadContext context) {
        modify();

        RubyHashEntry entry = head.nextAdded;
        if (entry != head) {
            RubyArray result = RubyArray.newArray(getRuntime(), entry.key, entry.value);
            internalDeleteEntry(entry);
            return result;
        }

        if ((flags & PROCDEFAULT_HASH_F) != 0) {
            return RuntimeHelpers.invoke(context, ifNone, "call", this, getRuntime().getNil());
        } else {
            return ifNone;
        }
    }

    public final boolean fastDelete(IRubyObject key) {
        return internalDelete(key) != NO_ENTRY;
    }

    /** rb_hash_delete
     *
     */
    @JRubyMethod(name = "delete", required = 1, frame = true)
    public IRubyObject delete(ThreadContext context, IRubyObject key, Block block) {
        modify();

        final RubyHashEntry entry = internalDelete(key);
        if (entry != NO_ENTRY) return entry.value;

        if (block.isGiven()) return block.yield(context, key);
        return getRuntime().getNil();
    }

    /** rb_hash_select
     *
     */
    @JRubyMethod(name = "select", frame = true)
    public IRubyObject select(final ThreadContext context, final Block block) {
        final RubyArray result = getRuntime().newArray();
        final Ruby runtime = getRuntime();

        visitAll(new Visitor() {
            public void visit(IRubyObject key, IRubyObject value) {
                if (block.yield(context, runtime.newArray(key, value), null, null, true).isTrue()) {
                    result.append(runtime.newArray(key, value));
                }
            }
        });

        return result;
    }

    /** rb_hash_delete_if
     *
     */
    @JRubyMethod(name = "delete_if", frame = true)
    public RubyHash delete_if(final ThreadContext context, final Block block) {
        modify();

        final Ruby runtime = getRuntime();
        final RubyHash self = this;
        visitAll(new Visitor() {
            public void visit(IRubyObject key, IRubyObject value) {
                if (block.yield(context, RubyArray.newArray(runtime, key, value), null, null, true).isTrue()) {
                    self.delete(context, key, block);
                }
            }
        });

        return this;
    }

    /** rb_hash_reject
     *
     */
    @JRubyMethod(name = "reject", frame = true)
    public RubyHash reject(ThreadContext context, Block block) {
        return ((RubyHash)dup()).delete_if(context, block);
    }

    /** rb_hash_reject_bang
     *
     */
    @JRubyMethod(name = "reject!", frame = true)
    public IRubyObject reject_bang(ThreadContext context, Block block) {
        int n = size;
        delete_if(context, block);
        if (n == size) return getRuntime().getNil();
        return this;
    }

    /** rb_hash_clear
     *
     */
    @JRubyMethod(name = "clear")
    public RubyHash rb_clear() {
        modify();

        if (size > 0) {
            alloc();
            size = 0;
        }

        return this;
    }

    /** rb_hash_invert
     *
     */
    @JRubyMethod(name = "invert")
    public RubyHash invert(final ThreadContext context) {
        final RubyHash result = newHash(getRuntime());

        visitAll(new Visitor() {
            public void visit(IRubyObject key, IRubyObject value) {
                result.op_aset(context, value, key);
            }
        });

        return result;
    }

    /** rb_hash_update
     *
     */
    @JRubyMethod(name = {"merge!", "update"}, required = 1, frame = true)
    public RubyHash merge_bang(final ThreadContext context, final IRubyObject other, final Block block) {
        modify();

        final Ruby runtime = getRuntime();
        final RubyHash otherHash = other.convertToHash();
        final RubyHash self = this;
        otherHash.visitAll(new Visitor() {
            public void visit(IRubyObject key, IRubyObject value) {
                if (block.isGiven()) {
                    IRubyObject existing = self.internalGet(key);
                    if (existing != null)
                        value = block.yield(context, RubyArray.newArrayNoCopy(runtime, new IRubyObject[]{key, existing, value}));
                }
                self.op_aset(context, key, value);
            }
        });

        return this;
    }

    /** rb_hash_merge
     *
     */
    @JRubyMethod(name = "merge", required = 1, frame = true)
    public RubyHash merge(ThreadContext context, IRubyObject other, Block block) {
        return ((RubyHash)dup()).merge_bang(context, other, block);
    }

    /** rb_hash_replace
     *
     */
    @JRubyMethod(name = "initialize_copy", required = 1, visibility = Visibility.PRIVATE)
    public RubyHash initialize_copy(ThreadContext context, IRubyObject other) {
        return replace(context, other);
    }

    /** rb_hash_replace
     *
     */
    @JRubyMethod(name = "replace", required = 1)
    public RubyHash replace(final ThreadContext context, IRubyObject other) {
        final RubyHash otherHash = other.convertToHash();

        if (this == otherHash) return this;

        rb_clear();

        final RubyHash self = this;
        otherHash.visitAll(new Visitor() {
            public void visit(IRubyObject key, IRubyObject value) {
                self.op_aset(context, key, value);
            }
        });

        ifNone = otherHash.ifNone;

        if ((otherHash.flags & PROCDEFAULT_HASH_F) != 0) {
            flags |= PROCDEFAULT_HASH_F;
        } else {
            flags &= ~PROCDEFAULT_HASH_F;
        }

        return this;
    }

    /** rb_hash_values_at
     *
     */
    @JRubyMethod(name = "values_at", rest = true)
    public RubyArray values_at(ThreadContext context, IRubyObject[] args) {
        RubyArray result = RubyArray.newArray(getRuntime(), args.length);
        for (int i = 0; i < args.length; i++) {
            result.append(op_aref(context, args[i]));
        }
        return result;
    }

    public boolean hasDefaultProc() {
        return (flags & PROCDEFAULT_HASH_F) != 0;
    }

    public IRubyObject getIfNone(){
        return ifNone;
    }

    private static class VisitorIOException extends RuntimeException {
        VisitorIOException(Throwable cause) {
            super(cause);
        }
    }

    // FIXME:  Total hack to get flash in Rails marshalling/unmarshalling in session ok...We need
    // to totally change marshalling to work with overridden core classes.
    public static void marshalTo(final RubyHash hash, final MarshalStream output) throws IOException {
        output.registerLinkTarget(hash);
        output.writeInt(hash.size);
        try {
            hash.visitAll(new Visitor() {
                public void visit(IRubyObject key, IRubyObject value) {
                    try {
                        output.dumpObject(key);
                        output.dumpObject(value);
                    } catch (IOException e) {
                        throw new VisitorIOException(e);
                    }
                }
            });
        } catch (VisitorIOException e) {
            throw (IOException)e.getCause();
        }

        if (!hash.ifNone.isNil()) output.dumpObject(hash.ifNone);
    }

    public static RubyHash unmarshalFrom(UnmarshalStream input, boolean defaultValue) throws IOException {
        RubyHash result = newHash(input.getRuntime());
        input.registerLinkTarget(result);
        int size = input.unmarshalInt();
        ThreadContext context = input.getRuntime().getCurrentContext();
        for (int i = 0; i < size; i++) {
            result.op_aset(context, input.unmarshalObject(), input.unmarshalObject());
        }
        if (defaultValue) result.default_value_set(input.unmarshalObject());
        return result;
    }

    public Class getJavaClass() {
        return Map.class;
    }

    // Satisfy java.util.Set interface (for Java integration)

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public boolean containsKey(Object key) {
        return internalGet(JavaUtil.convertJavaToRuby(getRuntime(), key)) != null;
    }

    public boolean containsValue(Object value) {
        return hasValue(getRuntime().getCurrentContext(), JavaUtil.convertJavaToRuby(getRuntime(), value));
    }

    public Object get(Object key) {
        return JavaUtil.convertRubyToJava(internalGet(JavaUtil.convertJavaToRuby(getRuntime(), key)));
    }

    public Object put(Object key, Object value) {
        internalPut(JavaUtil.convertJavaToRuby(getRuntime(), key), JavaUtil.convertJavaToRuby(getRuntime(), value));
        return value;
    }

    public Object remove(Object key) {
        IRubyObject rubyKey = JavaUtil.convertJavaToRuby(getRuntime(), key);
        return internalDelete(rubyKey).value;
    }

    public void putAll(Map map) {
        Ruby runtime = getRuntime();
        for (Iterator iter = map.keySet().iterator(); iter.hasNext();) {
            Object key = iter.next();
            internalPut(JavaUtil.convertJavaToRuby(runtime, key), JavaUtil.convertJavaToRuby(runtime, map.get(key)));
        }
    }

    public void clear() {
        rb_clear();
    }

    public boolean equals(Object other) {
        if (!(other instanceof RubyHash)) return false;
        if (this == other) return true;
        return op_equal(getRuntime().getCurrentContext(), (RubyHash)other).isTrue() ? true : false;
    }

    public Set keySet() {
        return new BaseSet(KEY_VIEW);
    }

    public Set directKeySet() {
        return new BaseSet(DIRECT_KEY_VIEW);
    }

    public Collection values() {
        return new BaseCollection(VALUE_VIEW);
    }

    public Collection directValues() {
        return new BaseCollection(DIRECT_VALUE_VIEW);
    }

    public Set entrySet() {
        return new BaseSet(ENTRY_VIEW);
    }

    public Set directEntrySet() {
        return new BaseSet(DIRECT_ENTRY_VIEW);
    }

    private class BaseSet extends AbstractSet {
        final EntryView view;

        public BaseSet(EntryView view) {
            this.view = view;
        }

        public Iterator iterator() {
            return new BaseIterator(view);
        }

        public boolean contains(Object o) {
            return view.contains(RubyHash.this, o);
        }

        public void clear() {
            RubyHash.this.clear();
        }

        public int size() {
            return RubyHash.this.size;
        }

        public boolean remove(Object o) {
            return view.remove(RubyHash.this, o);
        }
    }

    private class BaseCollection extends AbstractCollection {
        final EntryView view;

        public BaseCollection(EntryView view) {
            this.view = view;
        }

        public Iterator iterator() {
            return new BaseIterator(view);
        }

        public boolean contains(Object o) {
            return view.contains(RubyHash.this, o);
        }

        public void clear() {
            RubyHash.this.clear();
        }

        public int size() {
            return RubyHash.this.size;
        }

        public boolean remove(Object o) {
            return view.remove(RubyHash.this, o);
        }
    }

    private class BaseIterator implements Iterator {
        final private EntryView view;
        private RubyHashEntry entry;
        private boolean peeking;
        private int startGeneration;

        public BaseIterator(EntryView view) {
            this.view = view;
            this.entry = head;
            this.startGeneration = generation;
        }

        private void advance(boolean consume) {
            if (!peeking) {
                do {
                    if (startGeneration != generation) {
                        startGeneration = generation;
                        entry = head;
                    }
                    entry = entry.nextAdded;
                } while (entry != head && !entry.isLive());
            }
            peeking = !consume;
        }

        public Object next() {
            advance(true);
            if (entry == head) {
                peeking = true; // remain where we are
                throw new NoSuchElementException();
            }
            return view.convertEntry(getRuntime(), entry);
        }

        // once hasNext has been called, we commit to next() returning
        // the entry it found, even if it were subsequently deleted
        public boolean hasNext() {
            advance(false);
            return entry != head;
        }

        public void remove() {
            if (entry == head) {
                throw new IllegalStateException("Iterator out of range");
            }
            internalDeleteEntry(entry);
        }
    }

    private static abstract class EntryView {
        public abstract Object convertEntry(Ruby runtime, RubyHashEntry value);
        public abstract boolean contains(RubyHash hash, Object o);
        public abstract boolean remove(RubyHash hash, Object o);
    }

    private static final EntryView DIRECT_KEY_VIEW = new EntryView() {
        public Object convertEntry(Ruby runtime, RubyHashEntry entry) {
            return entry.key;
        }
        public boolean contains(RubyHash hash, Object o) {
            if (!(o instanceof IRubyObject)) return false;
            return hash.internalGet((IRubyObject)o) != null;
        }
        public boolean remove(RubyHash hash, Object o) {
            if (!(o instanceof IRubyObject)) return false;
            return hash.internalDelete((IRubyObject)o) != NO_ENTRY;
        }
    };

    private static final EntryView KEY_VIEW = new EntryView() {
        public Object convertEntry(Ruby runtime, RubyHashEntry entry) {
            return JavaUtil.convertRubyToJava(entry.key, Object.class);
        }
        public boolean contains(RubyHash hash, Object o) {
            return hash.containsKey(o);
        }
        public boolean remove(RubyHash hash, Object o) {
            return hash.remove(o) != null;
        }
    };

    private static final EntryView DIRECT_VALUE_VIEW = new EntryView() {
        public Object convertEntry(Ruby runtime, RubyHashEntry entry) {
            return entry.value;
        }
        public boolean contains(RubyHash hash, Object o) {
            if (!(o instanceof IRubyObject)) return false;
            IRubyObject obj = (IRubyObject)o;
            return hash.hasValue(obj.getRuntime().getCurrentContext(), obj);
        }
        public boolean remove(RubyHash hash, Object o) {
            if (!(o instanceof IRubyObject)) return false;
            IRubyObject obj = (IRubyObject) o;
            IRubyObject key = hash.internalIndex(obj.getRuntime().getCurrentContext(), obj);
            if (key == null) return false;
            return hash.internalDelete(key) != NO_ENTRY;
        }
    };

    private final EntryView VALUE_VIEW = new EntryView() {
        public Object convertEntry(Ruby runtime, RubyHashEntry entry) {
            return JavaUtil.convertRubyToJava(entry.value, Object.class);
        }
        public boolean contains(RubyHash hash, Object o) {
            return hash.containsValue(o);
        }
        public boolean remove(RubyHash hash, Object o) {
            IRubyObject value = JavaUtil.convertJavaToRuby(hash.getRuntime(), o);
            IRubyObject key = hash.internalIndex(hash.getRuntime().getCurrentContext(), value);
            if (key == null) return false;
            return hash.internalDelete(key) != NO_ENTRY;
        }
    };

    private final EntryView DIRECT_ENTRY_VIEW = new EntryView() {
        public Object convertEntry(Ruby runtime, RubyHashEntry entry) {
            return entry;
        }
        public boolean contains(RubyHash hash, Object o) {
            if (!(o instanceof RubyHashEntry)) return false;
            RubyHashEntry entry = (RubyHashEntry)o;
            RubyHashEntry candidate = internalGetEntry(entry.key);
            return candidate != NO_ENTRY && entry.equals(candidate);
        }
        public boolean remove(RubyHash hash, Object o) {
            if (!(o instanceof RubyHashEntry)) return false;
            return hash.internalDeleteEntry((RubyHashEntry)o) != NO_ENTRY;
        }
    };

    private final EntryView ENTRY_VIEW = new EntryView() {
        public Object convertEntry(Ruby runtime, RubyHashEntry entry) {
            return new ConvertingEntry(runtime, entry);
        }
        public boolean contains(RubyHash hash, Object o) {
            if (!(o instanceof ConvertingEntry)) return false;
            ConvertingEntry entry = (ConvertingEntry)o;
            RubyHashEntry candidate = hash.internalGetEntry(entry.entry.key);
            return candidate != NO_ENTRY && entry.entry.equals(candidate);
        }
        public boolean remove(RubyHash hash, Object o) {
            if (!(o instanceof ConvertingEntry)) return false;
            ConvertingEntry entry = (ConvertingEntry)o;
            return hash.internalDeleteEntry(entry.entry) != NO_ENTRY;
        }
    };

    private static class ConvertingEntry implements Map.Entry {
        private final RubyHashEntry entry;
        private final Ruby runtime;

        public ConvertingEntry(Ruby runtime, RubyHashEntry entry) {
            this.entry = entry;
            this.runtime = runtime;
        }

        public Object getKey() {
            return JavaUtil.convertRubyToJava(entry.key, Object.class);
        }
        public Object getValue() {
            return JavaUtil.convertRubyToJava(entry.value, Object.class);
        }
        public Object setValue(Object o) {
            return entry.setValue(JavaUtil.convertJavaToRuby(runtime, o));
        }

        public boolean equals(Object o) {
            if (!(o instanceof ConvertingEntry)) {
                return false;
            }
            ConvertingEntry other = (ConvertingEntry)o;
            return entry.equals(other.entry);
        }
        public int hashCode() {
            return entry.hashCode();
        }
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2006 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2007 Koichiro Ohba <koichiro@meadowy.org>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.MalformedInputException;
import java.nio.charset.UnmappableCharacterException;
import java.nio.charset.UnsupportedCharsetException;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;
import org.jruby.anno.JRubyClass;
import org.jruby.runtime.Arity;

import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;

import org.jruby.util.ByteList;

@JRubyClass(name="Iconv")
public class RubyIconv extends RubyObject {
    //static private final String TRANSLIT = "//translit";
    static private final String IGNORE = "//ignore";

    private CharsetDecoder fromEncoding;
    private CharsetEncoder toEncoding;

    public RubyIconv(Ruby runtime, RubyClass type) {
        super(runtime, type);
    }
    
    private static final ObjectAllocator ICONV_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return new RubyIconv(runtime, klass);
        }
    };

    @JRubyModule(name="Iconv::Failure")
    public static class Failure {}
    @JRubyClass(name="Iconv::IllegalSequence", parent="ArgumentError", include="Iconv::Failure")
    public static class IllegalSequence {}
    @JRubyClass(name="Iconv::InvalidCharacter", parent="ArgumentError", include="Iconv::Failure")
    public static class InvalidCharacter {}
    @JRubyClass(name="Iconv::InvalidEncoding", parent="ArgumentError", include="Iconv::Failure")
    public static class InvalidEncoding {}
    @JRubyClass(name="Iconv::OutOfRange", parent="ArgumentError", include="Iconv::Failure")
    public static class OutOfRange {}
    @JRubyClass(name="Iconv::BrokenLibrary", parent="ArgumentError", include="Iconv::Failure")
    public static class BrokenLibrary {}

    public static void createIconv(Ruby runtime) {
        RubyClass iconvClass = runtime.defineClass("Iconv", runtime.getObject(), ICONV_ALLOCATOR);
        
        iconvClass.defineAnnotatedMethods(RubyIconv.class);

        RubyModule failure = iconvClass.defineModuleUnder("Failure");
        RubyClass argumentError = runtime.getArgumentError();

        String[] iconvErrors = {"IllegalSequence", "InvalidCharacter", "InvalidEncoding", 
                "OutOfRange", "BrokenLibrary"};
        
        for (int i = 0; i < iconvErrors.length; i++) {
            RubyClass subClass = iconvClass.defineClassUnder(iconvErrors[i], argumentError, RubyFailure.ICONV_FAILURE_ALLOCATOR);
            subClass.defineAnnotatedMethods(RubyFailure.class);
            subClass.includeModule(failure);
        }    
    }
    
    public static class RubyFailure extends RubyException {
        private IRubyObject success;
        private IRubyObject failed;

        public static RubyFailure newInstance(Ruby runtime, RubyClass excptnClass, String msg) {
            return new RubyFailure(runtime, excptnClass, msg);
        }

        protected static final ObjectAllocator ICONV_FAILURE_ALLOCATOR = new ObjectAllocator() {
            public IRubyObject allocate(Ruby runtime, RubyClass klass) {
                return new RubyFailure(runtime, klass);
            }
        };

        protected RubyFailure(Ruby runtime, RubyClass rubyClass) {
            this(runtime, rubyClass, null);
        }

        public RubyFailure(Ruby runtime, RubyClass rubyClass, String message) {
            super(runtime, rubyClass, message);
        }

        @JRubyMethod(name = "initialize", required = 1, optional = 2, frame = true)
        public IRubyObject initialize(IRubyObject[] args, Block block) {
            super.initialize(args, block);
            success = args.length >= 2 ? args[1] : getRuntime().getNil();
            failed = args.length == 3 ? args[2] : getRuntime().getNil();

            return this;
        }

        @JRubyMethod(name = "success")
        public IRubyObject success() {
            return success;
        }

        @JRubyMethod(name = "failed")
        public IRubyObject failed() {
            return failed;
        }

        @JRubyMethod(name = "inspect")
        public IRubyObject inspect() {
            RubyModule rubyClass = getMetaClass();
            StringBuilder buffer = new StringBuilder("#<");
            buffer.append(rubyClass.getName()).append(": ").append(success.inspect().toString());
            buffer.append(", ").append(failed.inspect().toString()).append(">");

            return getRuntime().newString(buffer.toString());
        }
    }

    private static String getCharset(String encoding) {
        int index = encoding.indexOf("//");
        if (index == -1) return encoding;
        return encoding.substring(0, index);
    }
    
    /* Currently dead code, but useful when we figure out how to actually perform translit.
    private static boolean isTranslit(String encoding) {
        return encoding.toLowerCase().indexOf(TRANSLIT) != -1 ? true : false;
    }*/
    
    private static boolean isIgnore(String encoding) {
        return encoding.toLowerCase().indexOf(IGNORE) != -1 ? true : false;
    }

    @JRubyMethod(name = "open", required = 2, frame = true, meta = true)
    public static IRubyObject open(ThreadContext context, IRubyObject recv, IRubyObject to, IRubyObject from, Block block) {
        Ruby runtime = context.getRuntime();

        RubyIconv iconv = newIconv(context, recv, to, from);

        if (!block.isGiven()) return iconv;

        IRubyObject result = runtime.getNil();
        try {
            result = block.yield(context, iconv);
        } finally {
            iconv.close();
        }

        return result;
    }
    
    private static RubyIconv newIconv(ThreadContext context, IRubyObject recv,
            IRubyObject to, IRubyObject from) {
        RubyClass klazz = (RubyClass)recv;

        return (RubyIconv) klazz.newInstance(
                context, new IRubyObject[] {to, from}, Block.NULL_BLOCK);
    }

    @JRubyMethod(name = "initialize", required = 2, frame = true)
    public IRubyObject initialize(IRubyObject arg1, IRubyObject arg2, Block unusedBlock) {
        Ruby runtime = getRuntime();
        if (!arg1.respondsTo("to_str")) {
            throw runtime.newTypeError("can't convert " + arg1.getMetaClass() + " into String");
        }
        if (!arg2.respondsTo("to_str")) {
            throw runtime.newTypeError("can't convert " + arg2.getMetaClass() + " into String");
        }

        String to = arg1.convertToString().toString();
        String from = arg2.convertToString().toString();

        try {

            fromEncoding = Charset.forName(getCharset(from)).newDecoder();
            toEncoding = Charset.forName(getCharset(to)).newEncoder();

            if (!isIgnore(from)) fromEncoding.onUnmappableCharacter(CodingErrorAction.REPORT);
            if (!isIgnore(to)) toEncoding.onUnmappableCharacter(CodingErrorAction.REPORT);
        } catch (IllegalCharsetNameException e) {
            throw runtime.newInvalidEncoding("invalid encoding");
        } catch (UnsupportedCharsetException e) {
            throw runtime.newInvalidEncoding("invalid encoding");
        } catch (Exception e) {
            throw runtime.newSystemCallError(e.toString());
        }

        return this;
    }

    @JRubyMethod(name = "close")
    public IRubyObject close() {
        toEncoding = null;
        fromEncoding = null;
        return RubyString.newEmptyString(getRuntime());
    }

    @JRubyMethod
    public IRubyObject iconv(IRubyObject str) {
        return iconv(str, 0, -1);
    }

    @JRubyMethod
    public IRubyObject iconv(IRubyObject str, IRubyObject startArg) {
        int start = 0;
        if (!startArg.isNil()) start = RubyNumeric.fix2int(startArg);
        return iconv(str, start, -1);
    }

    @JRubyMethod
    public IRubyObject iconv(IRubyObject str, IRubyObject startArg, IRubyObject endArg) {
        int start = 0;
        int end = -1;

        if (!startArg.isNil()) start = RubyNumeric.fix2int(startArg);
        if (!endArg.isNil()) end = RubyNumeric.fix2int(endArg);

        return iconv(str, start, end);
    }
    
    private IRubyObject iconv(IRubyObject str, int start, int end) {
        if (str.isNil()) {
            fromEncoding.reset();
            toEncoding.reset();
            return RubyString.newEmptyString(getRuntime());
        }

        return _iconv(str.convertToString(), start, end);
    }

    /**
     * Variable-arity version for compatibility. Not bound to Ruby.
     * @deprecated Use the versions with one, two or three arguments.
     */
    public IRubyObject iconv(IRubyObject[] args) {
        switch (args.length) {
        case 1:
            return iconv(args[0]);
        case 2:
            return iconv(args[0], args[1]);
        case 3:
            return iconv(args[0], args[1], args[2]);
        default:
            Arity.raiseArgumentError(getRuntime(), args.length, 1, 2);
            return null; // not reached
        }
    }

    // FIXME: We are assuming that original string will be raw bytes.  If -Ku is provided
    // this will not be true, but that is ok for now.  Deal with that when someone needs it.
    private IRubyObject _iconv(RubyString str, int start, int end) {
        if (fromEncoding == null) {
            throw getRuntime().newArgumentError("closed iconv");
        }
        
        ByteList bytes = str.getByteList();
        
        // treat start and end as start...end for end >= 0, start..end for end < 0
        if (start < 0) {
            start += bytes.length();
        }
        
        if (end < 0) {
            end += 1 + bytes.length();
        } else if (end > bytes.length()) {
            end = bytes.length();
        }
        
        if (start < 0 || end < start) { // invalid ranges result in an empty string
            return RubyString.newEmptyString(getRuntime());
        }
        
        ByteBuffer buf = ByteBuffer.wrap(bytes.unsafeBytes(), bytes.begin() + start, end - start);
        
        try {
            CharBuffer cbuf = fromEncoding.decode(buf);
            buf = toEncoding.encode(cbuf);
        } catch (MalformedInputException e) {
        } catch (UnmappableCharacterException e) {
        } catch (CharacterCodingException e) {
            throw getRuntime().newInvalidEncoding("invalid sequence");
        } catch (IllegalStateException e) {
        }
        byte[] arr = buf.array();
        
        return getRuntime().newString(new ByteList(arr, 0, buf.limit()));
    }

    @JRubyMethod(name = "iconv", required = 2, rest = true, meta = true)
    public static IRubyObject iconv(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block unusedBlock) {
        return convertWithArgs(context, recv, args, "iconv");
    }
    
    @JRubyMethod(name = "conv", required = 3, rest = true, meta = true)
    public static IRubyObject conv(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block unusedBlock) {
        return convertWithArgs(context, recv, args, "conv").join(context, RubyString.newEmptyString(recv.getRuntime()));
    }

    @JRubyMethod(name = "charset_map", meta= true)
    public static IRubyObject charset_map_get(IRubyObject recv) {
        return recv.getRuntime().getCharsetMap();
    }

    private static String mapCharset(ThreadContext context, IRubyObject val) {
        RubyHash charset = val.getRuntime().getCharsetMap();
        if (charset.size() > 0) {
            RubyString key = val.callMethod(context, "downcase").convertToString();
            IRubyObject tryVal = charset.fastARef(key);
            if (tryVal != null) val = tryVal;
        }

        return val.convertToString().toString();
    }

    public static RubyArray convertWithArgs(ThreadContext context, IRubyObject recv, IRubyObject[] args, String function) {
        assert args.length >= 2;

        RubyArray array = context.getRuntime().newArray(args.length - 2);
        RubyIconv iconv = newIconv(context, recv, args[0], args[1]);

        try {
            for (int i = 2; i < args.length; i++) {
                array.append(iconv.iconv(args[i]));
            }
        } finally {
            iconv.close();
        }

        return array;
    }
    
    /*
    private static IRubyObject convert(String fromEncoding, String toEncoding, RubyString original) 
        throws UnsupportedEncodingException {
        // Get all bytes from PLAIN string pretend they are not encoded in any way.
        byte[] string = original.getBytes();
        // Now create a string pretending it is from fromEncoding
        string = new String(string, fromEncoding).getBytes(toEncoding);
        // Finally recode back to PLAIN
        return RubyString.newString(original.getRuntime(), string);
    }
    */
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2007 Nick Sieger <nicksieger@gmail.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import java.util.Set;
import java.util.StringTokenizer;
import org.jruby.ast.executable.Script;
import org.jruby.exceptions.MainExitException;
import org.jruby.runtime.Constants;
import org.jruby.runtime.load.LoadService;
import org.jruby.util.ClassCache;
import org.jruby.util.JRubyFile;
import org.jruby.util.KCode;
import org.jruby.util.NormalizedFile;
import org.jruby.util.SafePropertyAccessor;
import org.objectweb.asm.Opcodes;

public class RubyInstanceConfig {

    /**
     * The max count of active methods eligible for JIT-compilation.
     */
    private static final int JIT_MAX_METHODS_LIMIT = 4096;

    /**
     * The max size of JIT-compiled methods (full class size) allowed.
     */
    private static final int JIT_MAX_SIZE_LIMIT = Integer.MAX_VALUE;

    /**
     * The JIT threshold to the specified method invocation count.
     */
    private static final int JIT_THRESHOLD = 50;
    
    /** The version to use for generated classes. Set to current JVM version by default */
    public static final int JAVA_VERSION;
    
    /**
     * Default size for chained compilation.
     */
    private static final int CHAINED_COMPILE_LINE_COUNT_DEFAULT = 500;
    
    /**
     * The number of lines at which a method, class, or block body is split into
     * chained methods (to dodge 64k method-size limit in JVM).
     */
    public static final int CHAINED_COMPILE_LINE_COUNT
            = SafePropertyAccessor.getInt("jruby.compile.chainsize", CHAINED_COMPILE_LINE_COUNT_DEFAULT);

    public enum CompileMode {
        JIT, FORCE, OFF;

        public boolean shouldPrecompileCLI() {
            switch (this) {
            case JIT: case FORCE:
                return true;
            }
            return false;
        }

        public boolean shouldJIT() {
            switch (this) {
            case JIT: case FORCE:
                return true;
            }
            return false;
        }

        public boolean shouldPrecompileAll() {
            return this == FORCE;
        }
    }
    private InputStream input          = System.in;
    private PrintStream output         = System.out;
    private PrintStream error          = System.err;
    private Profile profile            = Profile.DEFAULT;
    private boolean objectSpaceEnabled
            = SafePropertyAccessor.getBoolean("jruby.objectspace.enabled", false);

    private CompileMode compileMode = CompileMode.JIT;
    private boolean runRubyInProcess   = true;
    private String currentDirectory;
    private Map environment;
    private String[] argv = {};

    private final boolean jitLogging;
    private final boolean jitLoggingVerbose;
    private final int jitLogEvery;
    private final int jitThreshold;
    private final int jitMax;
    private final int jitMaxSize;
    private final boolean samplingEnabled;
    private CompatVersion compatVersion;

    private ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
    private ClassLoader loader = contextLoader == null ? RubyInstanceConfig.class.getClassLoader() : contextLoader;

    private ClassCache<Script> classCache;

    // from CommandlineParser
    private List<String> loadPaths = new ArrayList<String>();
    private Set<String> excludedMethods = new HashSet<String>();
    private StringBuffer inlineScript = new StringBuffer();
    private boolean hasInlineScript = false;
    private String scriptFileName = null;
    private List<String> requiredLibraries = new ArrayList<String>();
    private boolean benchmarking = false;
    private boolean argvGlobalsOn = false;
    private boolean assumeLoop = false;
    private boolean assumePrinting = false;
    private Map optionGlobals = new HashMap();
    private boolean processLineEnds = false;
    private boolean split = false;
    // This property is a Boolean, to allow three values, so it can match MRI's nil, false and true
    private Boolean verbose = Boolean.FALSE;
    private boolean debug = false;
    private boolean showVersion = false;
    private boolean showBytecode = false;
    private boolean showCopyright = false;
    private boolean endOfArguments = false;
    private boolean shouldRunInterpreter = true;
    private boolean shouldPrintUsage = false;
    private boolean shouldPrintProperties=false;
    private boolean yarv = false;
    private boolean rubinius = false;
    private boolean yarvCompile = false;
    private KCode kcode = KCode.NONE;
    private String recordSeparator = "\n";
    private boolean shouldCheckSyntax = false;
    private String inputFieldSeparator = null;
    private boolean managementEnabled = true;

    private int safeLevel = 0;

    private String jrubyHome;

    public static final boolean FASTEST_COMPILE_ENABLED
            = SafePropertyAccessor.getBoolean("jruby.compile.fastest");
    public static final boolean BOXED_COMPILE_ENABLED
            = FASTEST_COMPILE_ENABLED
            || SafePropertyAccessor.getBoolean("jruby.compile.boxed");
    public static final boolean FASTOPS_COMPILE_ENABLED
            = FASTEST_COMPILE_ENABLED
            || SafePropertyAccessor.getBoolean("jruby.compile.fastops");
    public static final boolean FRAMELESS_COMPILE_ENABLED
            = FASTEST_COMPILE_ENABLED
            || SafePropertyAccessor.getBoolean("jruby.compile.frameless");
    public static final boolean POSITIONLESS_COMPILE_ENABLED
            = FASTEST_COMPILE_ENABLED
            || SafePropertyAccessor.getBoolean("jruby.compile.positionless");
    public static final boolean THREADLESS_COMPILE_ENABLED
            = FASTEST_COMPILE_ENABLED
            || SafePropertyAccessor.getBoolean("jruby.compile.threadless");
    public static final boolean LAZYHANDLES_COMPILE = SafePropertyAccessor.getBoolean("jruby.compile.lazyHandles", false);
    public static final boolean FORK_ENABLED
            = SafePropertyAccessor.getBoolean("jruby.fork.enabled");
    public static final boolean POOLING_ENABLED
            = SafePropertyAccessor.getBoolean("jruby.thread.pool.enabled");
    public static final int POOL_MAX
            = SafePropertyAccessor.getInt("jruby.thread.pool.max", Integer.MAX_VALUE);
    public static final int POOL_MIN
            = SafePropertyAccessor.getInt("jruby.thread.pool.min", 0);
    public static final int POOL_TTL
            = SafePropertyAccessor.getInt("jruby.thread.pool.ttl", 60);

    public static final boolean NATIVE_NET_PROTOCOL
            = SafePropertyAccessor.getBoolean("jruby.native.net.protocol", false);

    public static boolean FULL_TRACE_ENABLED
            = SafePropertyAccessor.getBoolean("jruby.debug.fullTrace", false);

    public static final String COMPILE_EXCLUDE
            = SafePropertyAccessor.getProperty("jruby.jit.exclude");
    public static boolean nativeEnabled = true;
    

    public static interface LoadServiceCreator {
        LoadService create(Ruby runtime);

        LoadServiceCreator DEFAULT = new LoadServiceCreator() {
                public LoadService create(Ruby runtime) {
                    return new LoadService(runtime);
                }
            };
    }

    private LoadServiceCreator creator = LoadServiceCreator.DEFAULT;


    static {
        String specVersion = null;
        try {
            specVersion = System.getProperty("jruby.bytecode.version");
            if (specVersion == null) {
                specVersion = System.getProperty("java.specification.version");
            }
            if (System.getProperty("jruby.native.enabled") != null) {
                nativeEnabled = Boolean.getBoolean("jruby.native.enabled");
            }
        } catch (SecurityException se) {
            nativeEnabled = false;
            specVersion = "1.5";
        }
        
        if (specVersion.equals("1.5")) {
            JAVA_VERSION = Opcodes.V1_5;
        } else {
            JAVA_VERSION = Opcodes.V1_6;
        }
    }

    public int characterIndex = 0;

    public RubyInstanceConfig() {
        if (Ruby.isSecurityRestricted())
            currentDirectory = "/";
        else {
            currentDirectory = JRubyFile.getFileProperty("user.dir");
        }

        samplingEnabled = SafePropertyAccessor.getBoolean("jruby.sampling.enabled", false);
        String compatString = SafePropertyAccessor.getProperty("jruby.compat.version", "RUBY1_8");
        if (compatString.equalsIgnoreCase("RUBY1_8")) {
            compatVersion = CompatVersion.RUBY1_8;
        } else if (compatString.equalsIgnoreCase("RUBY1_9")) {
            compatVersion = CompatVersion.RUBY1_9;
        } else {
            System.err.println("Compatibility version `" + compatString + "' invalid; use RUBY1_8 or RUBY1_9. Using RUBY1_8.");
            compatVersion = CompatVersion.RUBY1_8;
        }

        if (Ruby.isSecurityRestricted()) {
            compileMode = CompileMode.OFF;
            jitLogging = false;
            jitLoggingVerbose = false;
            jitLogEvery = 0;
            jitThreshold = -1;
            jitMax = 0;
            jitMaxSize = -1;
            managementEnabled = false;
        } else {
            String threshold = SafePropertyAccessor.getProperty("jruby.jit.threshold");
            String max = SafePropertyAccessor.getProperty("jruby.jit.max");
            String maxSize = SafePropertyAccessor.getProperty("jruby.jit.maxsize");
            
            if (COMPILE_EXCLUDE != null) {
                String[] elements = COMPILE_EXCLUDE.split(",");
                for (String element : elements) excludedMethods.add(element);
            }
            
            managementEnabled = SafePropertyAccessor.getBoolean("jruby.management.enabled", true);
            runRubyInProcess = SafePropertyAccessor.getBoolean("jruby.launch.inproc", true);
            boolean jitProperty = SafePropertyAccessor.getProperty("jruby.jit.enabled") != null;
            if (jitProperty) {
                error.print("jruby.jit.enabled property is deprecated; use jruby.compile.mode=(OFF|JIT|FORCE) for -C, default, and +C flags");
                compileMode = SafePropertyAccessor.getBoolean("jruby.jit.enabled") ? CompileMode.JIT : CompileMode.OFF;
            } else {
                String jitModeProperty = SafePropertyAccessor.getProperty("jruby.compile.mode", "JIT");

                if (jitModeProperty.equals("OFF")) {
                    compileMode = CompileMode.OFF;
                } else if (jitModeProperty.equals("JIT")) {
                    compileMode = CompileMode.JIT;
                } else if (jitModeProperty.equals("FORCE")) {
                    compileMode = CompileMode.FORCE;
                } else {
                    error.print("jruby.compile.mode property must be OFF, JIT, FORCE, or unset; defaulting to JIT");
                    compileMode = CompileMode.JIT;
                }
            }
            jitLogging = SafePropertyAccessor.getBoolean("jruby.jit.logging");
            jitLoggingVerbose = SafePropertyAccessor.getBoolean("jruby.jit.logging.verbose");
            String logEvery = SafePropertyAccessor.getProperty("jruby.jit.logEvery");
            jitLogEvery = logEvery == null ? 0 : Integer.parseInt(logEvery);
            jitThreshold = threshold == null ?
                    JIT_THRESHOLD : Integer.parseInt(threshold);
            jitMax = max == null ?
                    JIT_MAX_METHODS_LIMIT : Integer.parseInt(max);
            jitMaxSize = maxSize == null ?
                    JIT_MAX_SIZE_LIMIT : Integer.parseInt(maxSize);
        }

        // default ClassCache using jitMax as a soft upper bound
        classCache = new ClassCache<Script>(loader, jitMax);

        if (FORK_ENABLED) {
            error.print("WARNING: fork is highly unlikely to be safe or stable on the JVM. Have fun!\n");
        }
    }

    public LoadServiceCreator getLoadServiceCreator() {
        return creator;
    }

    public void setLoadServiceCreator(LoadServiceCreator creator) {
        this.creator = creator;
    }

    public LoadService createLoadService(Ruby runtime) {
        return this.creator.create(runtime);
    }

    public String getBasicUsageHelp() {
        StringBuilder sb = new StringBuilder();
        sb
                .append("Usage: jruby [switches] [--] [programfile] [arguments]\n")
                .append("  -0[octal]       specify record separator (\0, if no argument)\n")
                .append("  -a              autosplit mode with -n or -p (splits $_ into $F)\n")
                .append("  -b              benchmark mode, times the script execution\n")
                .append("  -c              check syntax only\n")
                .append("  -Cdirectory     cd to directory, before executing your script\n")
                .append("  -d              set debugging flags (set $DEBUG to true)\n")
                .append("  -e 'command'    one line of script. Several -e's allowed. Omit [programfile]\n")
                .append("  -Fpattern       split() pattern for autosplit (-a)\n")
                //.append("  -i[extension]   edit ARGV files in place (make backup if extension supplied)\n")
                .append("  -Idirectory     specify $LOAD_PATH directory (may be used more than once)\n")
                .append("  -J[java option] pass an option on to the JVM (e.g. -J-Xmx512m)\n")
                .append("                    use --properties to list JRuby properties\n")
                .append("                    run 'java -help' for a list of other Java options\n")
                .append("  -Kkcode         specifies code-set (e.g. -Ku for Unicode\n")
                .append("  -l              enable line ending processing\n")
                .append("  -n              assume 'while gets(); ... end' loop around your script\n")
                .append("  -p              assume loop like -n but print line also like sed\n")
                .append("  -rlibrary       require the library, before executing your script\n")
                .append("  -s              enable some switch parsing for switches after script name\n")
                .append("  -S              look for the script in bin or using PATH environment variable\n")
                .append("  -T[level]       turn on tainting checks\n")
                .append("  -v              print version number, then turn on verbose mode\n")
                .append("  -w              turn warnings on for your script\n")
                .append("  -W[level]       set warning level; 0=silence, 1=medium, 2=verbose (default)\n")
                //.append("  -x[directory]   strip off text before #!ruby line and perhaps cd to directory\n")
                .append("  -X[option]      enable extended option (omit option to list)\n")
                .append("  --copyright     print the copyright\n")
                .append("  --debug         sets the execution mode most suitable for debugger functionality\n")
                .append("  --jdb           runs JRuby process under JDB\n")
                .append("  --properties    List all configuration Java properties (pass -J-Dproperty=value)\n")
                .append("  --sample        run with profiling using the JVM's sampling profiler\n")
                .append("  --client        use the non-optimizing \"client\" JVM (improves startup; default)\n")
                .append("  --server        use the optimizing \"server\" JVM (improves perf)\n")
                .append("  --manage        enable remote JMX management and monitoring of the VM and JRuby\n")
                .append("  --1.8           specify Ruby 1.8.x compatibility (default)\n")
                .append("  --1.9           specify Ruby 1.9.x compatibility\n")
                .append("  --bytecode      show the JVM bytecode produced by compiling specified code\n")
                .append("  --version       print the version\n");

        return sb.toString();
    }

    public String getExtendedHelp() {
        StringBuilder sb = new StringBuilder();
        sb
                .append("These flags are for extended JRuby options.\n")
                .append("Specify them by passing -X<option>\n")
                .append("  -O              run with ObjectSpace disabled (default; improves performance)\n")
                .append("  +O              run with ObjectSpace enabled (reduces performance)\n")
                .append("  -C              disable all compilation\n")
                .append("  +C              force compilation of all scripts before they are run (except eval)\n")
                .append("  -y              read a YARV-compiled Ruby script and run that (EXPERIMENTAL)\n")
                .append("  -Y              compile a Ruby script into YARV bytecodes and run this (EXPERIMENTAL)\n")
                .append("  -R              read a Rubinius-compiled Ruby script and run that (EXPERIMENTAL)\n");

        return sb.toString();
    }

    public String getPropertyHelp() {
        StringBuilder sb = new StringBuilder();
        sb
                .append("These properties can be used to alter runtime behavior for perf or compatibility.\n")
                .append("Specify them by passing -J-D<property>=<value>\n")
                .append("\nCOMPILER SETTINGS:\n")
                .append("    jruby.compile.mode=JIT|FORCE|OFF\n")
                .append("       Set compilation mode. JIT is default; FORCE compiles all, OFF disables\n")
                .append("    jruby.compile.fastest=true|false\n")
                .append("       (EXPERIMENTAL) Turn on all experimental compiler optimizations\n")
                .append("    jruby.compile.boxed=true|false\n")
                .append("       (EXPERIMENTAL) Use boxed variables; this can speed up some methods. Default is false\n")
                .append("    jruby.compile.frameless=true|false\n")
                .append("       (EXPERIMENTAL) Turn on frameless compilation where possible\n")
                .append("    jruby.compile.positionless=true|false\n")
                .append("       (EXPERIMENTAL) Turn on compilation that avoids updating Ruby position info. Default is false\n")
                .append("    jruby.compile.threadless=true|false\n")
                .append("       (EXPERIMENTAL) Turn on compilation without polling for \"unsafe\" thread events. Default is false\n")
                .append("    jruby.compile.fastops=true|false\n")
                .append("       (EXPERIMENTAL) Turn on fast operators for Fixnum. Default is false\n")
                .append("    jruby.compile.chainsize=<line count>\n")
                .append("       Set the number of lines at which compiled bodies are \"chained\". Default is " + CHAINED_COMPILE_LINE_COUNT_DEFAULT + "\n")
                .append("    jruby.compile.lazyHandles=true|false\n")
                .append("       Generate method bindings (handles) for compiled methods lazily. Default is false.")
                .append("\nJIT SETTINGS:\n")
                .append("    jruby.jit.threshold=<invocation count>\n")
                .append("       Set the JIT threshold to the specified method invocation count. Default is " + JIT_THRESHOLD + ".\n")
                .append("    jruby.jit.max=<method count>\n")
                .append("       Set the max count of active methods eligible for JIT-compilation.\n")
                .append("       Default is " + JIT_MAX_METHODS_LIMIT + " per runtime. A value of 0 disables JIT, -1 disables max.\n")
                .append("    jruby.jit.maxsize=<jitted method size (full .class)>\n")
                .append("       Set the maximum full-class byte size allowed for jitted methods. Default is Integer.MAX_VALUE\n")
                .append("    jruby.jit.logging=true|false\n")
                .append("       Enable JIT logging (reports successful compilation). Default is false\n")
                .append("    jruby.jit.logging.verbose=true|false\n")
                .append("       Enable verbose JIT logging (reports failed compilation). Default is false\n")
                .append("    jruby.jit.logEvery=<method count>\n")
                .append("       Log a message every n methods JIT compiled. Default is 0 (off).\n")
                .append("    jruby.jit.exclude=<ClsOrMod,ClsOrMod::method_name,-::method_name>\n")
                .append("       Exclude methods from JIT by class/module short name, c/m::method_name,\n")
                .append("       or -::method_name for anon/singleton classes/modules. Comma-delimited.\n")
                .append("\nNATIVE SUPPORT:\n")
                .append("    jruby.native.enabled=true|false\n")
                .append("       Enable/disable native extensions (like JNA for non-Java APIs; Default is true\n")
                .append("       (This affects all JRuby instances in a given JVM)\n")
                .append("    jruby.native.verbose=true|false\n")
                .append("       Enable verbose logging of native extension loading. Default is false.\n")
                .append("    jruby.fork.enabled=true|false\n")
                .append("       (EXPERIMENTAL, maybe dangerous) Enable fork(2) on platforms that support it.\n")
                .append("\nTHREAD POOLING:\n")
                .append("    jruby.thread.pool.enabled=true|false\n")
                .append("       Enable reuse of native backing threads via a thread pool. Default is false.\n")
                .append("    jruby.thread.pool.min=<min thread count>\n")
                .append("       The minimum number of threads to keep alive in the pool. Default is 0.\n")
                .append("    jruby.thread.pool.max=<max thread count>\n")
                .append("       The maximum number of threads to allow in the pool. Default is unlimited.\n")
                .append("    jruby.thread.pool.ttl=<time to live, in seconds>\n")
                .append("       The maximum number of seconds to keep alive an idle thread. Default is 60.\n")
                .append("\nMISCELLANY:\n")
                .append("    jruby.compat.version=RUBY1_8|RUBY1_9\n")
                .append("       Specify the major Ruby version to be compatible with; Default is RUBY1_8\n")
                .append("    jruby.objectspace.enabled=true|false\n")
                .append("       Enable or disable ObjectSpace.each_object (default is disabled)\n")
                .append("    jruby.launch.inproc=true|false\n")
                .append("       Set in-process launching of e.g. system('ruby ...'). Default is true\n")
                .append("    jruby.bytecode.version=1.5|1.6\n")
                .append("       Set bytecode version for JRuby to generate. Default is current JVM version.\n")
                .append("    jruby.management.enabled=true|false\n")
                .append("       Set whether JMX management is enabled. Default is true.\n")
                .append("    jruby.debug.fullTrace=true|false\n")
                .append("       Set whether full traces are enabled (c-call/c-return). Default is false.\n");

        return sb.toString();
    }

    public String getVersionString() {
        String ver = Constants.RUBY_VERSION;
        switch (compatVersion) {
        case RUBY1_8:
            ver = Constants.RUBY_VERSION;
            break;
        case RUBY1_9:
            ver = Constants.RUBY1_9_VERSION;
            break;
        }

        String fullVersion = String.format(
                "jruby %s (ruby %s patchlevel %s) (%s rev %s) [%s-java]\n",
                Constants.VERSION, ver, Constants.RUBY_PATCHLEVEL,
                Constants.COMPILE_DATE, Constants.REVISION,
                SafePropertyAccessor.getProperty("os.arch", "unknown")
                );

        return fullVersion;
    }

    public String getCopyrightString() {
        return "JRuby - Copyright (C) 2001-2008 The JRuby Community (and contribs)\n";
    }

    public void processArguments(String[] arguments) {
        new ArgumentProcessor(arguments).processArguments();
    }

    public CompileMode getCompileMode() {
        return compileMode;
    }

    public void setCompileMode(CompileMode compileMode) {
        this.compileMode = compileMode;
    }

    public boolean isJitLogging() {
        return jitLogging;
    }

    public boolean isJitLoggingVerbose() {
        return jitLoggingVerbose;
    }

    public int getJitLogEvery() {
        return jitLogEvery;
    }

    public boolean isSamplingEnabled() {
        return samplingEnabled;
    }

    public int getJitThreshold() {
        return jitThreshold;
    }

    public int getJitMax() {
        return jitMax;
    }

    public int getJitMaxSize() {
        return jitMaxSize;
    }

    public boolean isRunRubyInProcess() {
        return runRubyInProcess;
    }

    public void setRunRubyInProcess(boolean flag) {
        this.runRubyInProcess = flag;
    }

    public void setInput(InputStream newInput) {
        input = newInput;
    }

    public InputStream getInput() {
        return input;
    }

    public CompatVersion getCompatVersion() {
        return compatVersion;
    }

    public void setOutput(PrintStream newOutput) {
        output = newOutput;
    }

    public PrintStream getOutput() {
        return output;
    }

    public void setError(PrintStream newError) {
        error = newError;
    }

    public PrintStream getError() {
        return error;
    }

    public void setCurrentDirectory(String newCurrentDirectory) {
        currentDirectory = newCurrentDirectory;
    }

    public String getCurrentDirectory() {
        return currentDirectory;
    }

    public void setProfile(Profile newProfile) {
        profile = newProfile;
    }

    public Profile getProfile() {
        return profile;
    }

    public void setObjectSpaceEnabled(boolean newObjectSpaceEnabled) {
        objectSpaceEnabled = newObjectSpaceEnabled;
    }

    public boolean isObjectSpaceEnabled() {
        return objectSpaceEnabled;
    }

    public void setEnvironment(Map newEnvironment) {
        environment = newEnvironment;
    }

    public Map getEnvironment() {
        return environment;
    }

    public ClassLoader getLoader() {
        return loader;
    }

    public void setLoader(ClassLoader loader) {
        // Setting the loader needs to reset the class cache
        if(this.loader != loader) {
            this.classCache = new ClassCache<Script>(loader, this.classCache.getMax());
        }
        this.loader = loader;
    }

    public String[] getArgv() {
        return argv;
    }

    public void setArgv(String[] argv) {
        this.argv = argv;
    }

    public String getJRubyHome() {
        if (jrubyHome == null) {
            if (Ruby.isSecurityRestricted()) {
                return "SECURITY RESTRICTED";
            }
            jrubyHome = verifyHome(SafePropertyAccessor.getProperty("jruby.home",
                    SafePropertyAccessor.getProperty("user.home") + "/.jruby"));

            try {
                // This comment also in rbConfigLibrary
                // Our shell scripts pass in non-canonicalized paths, but even if we didn't
                // anyone who did would become unhappy because Ruby apps expect no relative
                // operators in the pathname (rubygems, for example).
                jrubyHome = new NormalizedFile(jrubyHome).getCanonicalPath();
            } catch (IOException e) { }

            jrubyHome = new NormalizedFile(jrubyHome).getAbsolutePath();
        }
        return jrubyHome;
    }

    public void setJRubyHome(String home) {
        jrubyHome = verifyHome(home);
    }

    // We require the home directory to be absolute
    private String verifyHome(String home) {
        if (home.equals(".")) {
            home = System.getProperty("user.dir");
        }
        if (!home.startsWith("file:")) {
            NormalizedFile f = new NormalizedFile(home);
            if (!f.isAbsolute()) {
                home = f.getAbsolutePath();
            }
            f.mkdirs();
        }
        return home;
    }

    private class ArgumentProcessor {
        private String[] arguments;
        private int argumentIndex = 0;

        public ArgumentProcessor(String[] arguments) {
            this.arguments = arguments;
        }

        public void processArguments() {
            while (argumentIndex < arguments.length && isInterpreterArgument(arguments[argumentIndex])) {
                processArgument();
                argumentIndex++;
            }

            if (!hasInlineScript && scriptFileName == null) {
                if (argumentIndex < arguments.length) {
                    setScriptFileName(arguments[argumentIndex]); //consume the file name
                    argumentIndex++;
                }
            }

            processArgv();
        }

        private void processArgv() {
            List<String> arglist = new ArrayList<String>();
            for (; argumentIndex < arguments.length; argumentIndex++) {
                String arg = arguments[argumentIndex];
                if (argvGlobalsOn && arg.startsWith("-")) {
                    arg = arg.substring(1);
                    if (arg.indexOf('=') > 0) {
                        String[] keyvalue = arg.split("=", 2);
                        optionGlobals.put(keyvalue[0], keyvalue[1]);
                    } else {
                        optionGlobals.put(arg, null);
                    }
                } else {
                    argvGlobalsOn = false;
                    arglist.add(arg);
                }
            }

            // Remaining arguments are for the script itself
            argv = arglist.toArray(new String[arglist.size()]);
        }

        private boolean isInterpreterArgument(String argument) {
            return (argument.charAt(0) == '-' || argument.charAt(0) == '+') && !endOfArguments;
        }

        private String getArgumentError(String additionalError) {
            return "jruby: invalid argument\n" + additionalError + "\n";
        }

        private void processArgument() {
            String argument = arguments[argumentIndex];
            FOR : for (characterIndex = 1; characterIndex < argument.length(); characterIndex++) {
                switch (argument.charAt(characterIndex)) {
                case '0': {
                    String temp = grabOptionalValue();
                    if (null == temp) {
                        recordSeparator = "\u0000";
                    } else if (temp.equals("0")) {
                        recordSeparator = "\n\n";
                    } else if (temp.equals("777")) {
                        recordSeparator = "\uFFFF"; // Specify something that can't separate
                    } else {
                        try {
                            int val = Integer.parseInt(temp, 8);
                            recordSeparator = "" + (char) val;
                        } catch (Exception e) {
                            MainExitException mee = new MainExitException(1, getArgumentError(" -0 must be followed by either 0, 777, or a valid octal value"));
                            mee.setUsageError(true);
                            throw mee;
                        }
                    }
                    break FOR;
                }
                case 'a':
                    split = true;
                    break;
                case 'b':
                    benchmarking = true;
                    break;
                case 'c':
                    shouldCheckSyntax = true;
                    break;
                case 'C':
                    try {
                        String saved = grabValue(getArgumentError(" -C must be followed by a directory expression"));
                        File base = new File(currentDirectory);
                        File newDir = new File(saved);
                        if (newDir.isAbsolute()) {
                            currentDirectory = newDir.getCanonicalPath();
                        } else {
                            currentDirectory = new File(base, newDir.getPath()).getCanonicalPath();
                        }
                        if (!(new File(currentDirectory).isDirectory())) {
                            MainExitException mee = new MainExitException(1, "jruby: Can't chdir to " + saved + " (fatal)");
                            mee.setUsageError(true);
                            throw mee;
                        }
                    } catch (IOException e) {
                        MainExitException mee = new MainExitException(1, getArgumentError(" -C must be followed by a valid directory"));
                        mee.setUsageError(true);
                        throw mee;
                    }
                    break;
                case 'd':
                    debug = true;
                    verbose = Boolean.TRUE;
                    break;
                case 'e':
                    inlineScript.append(grabValue(getArgumentError(" -e must be followed by an expression to evaluate")));
                    inlineScript.append('\n');
                    hasInlineScript = true;
                    break FOR;
                case 'F':
                    inputFieldSeparator = grabValue(getArgumentError(" -F must be followed by a pattern for input field separation"));
                    break;
                case 'h':
                    shouldPrintUsage = true;
                    shouldRunInterpreter = false;
                    break;
                // FIXME: -i flag not supported
//                    case 'i' :
//                        break;
                case 'I':
                    String s = grabValue(getArgumentError("-I must be followed by a directory name to add to lib path"));
                    String[] ls = s.split(java.io.File.pathSeparator);
                    for (int i = 0; i < ls.length; i++) {
                        loadPaths.add(ls[i]);
                    }
                    break FOR;
                case 'K':
                    // FIXME: No argument seems to work for -K in MRI plus this should not
                    // siphon off additional args 'jruby -K ~/scripts/foo'.  Also better error
                    // processing.
                    String eArg = grabValue(getArgumentError("provide a value for -K"));
                    kcode = KCode.create(null, eArg);
                    break;
                case 'l':
                    processLineEnds = true;
                    break;
                case 'n':
                    assumeLoop = true;
                    break;
                case 'p':
                    assumePrinting = true;
                    assumeLoop = true;
                    break;
                case 'r':
                    requiredLibraries.add(grabValue(getArgumentError("-r must be followed by a package to require")));
                    break FOR;
                case 's' :
                    argvGlobalsOn = true;
                    break;
                case 'S':
                    runBinScript();
                    break FOR;
                case 'T' :{
                    String temp = grabOptionalValue();
                    int value = 1;

                    if(temp!=null) {
                        try {
                            value = Integer.parseInt(temp, 8);
                        } catch(Exception e) {
                            value = 1;
                        }
                    }

                    safeLevel = value;

                    break FOR;
                }
                case 'v':
                    verbose = Boolean.TRUE;
                    setShowVersion(true);
                    break;
                case 'w':
                    verbose = Boolean.TRUE;
                    break;
                case 'W': {
                    String temp = grabOptionalValue();
                    int value = 2;
                    if (null != temp) {
                        if (temp.equals("2")) {
                            value = 2;
                        } else if (temp.equals("1")) {
                            value = 1;
                        } else if (temp.equals("0")) {
                            value = 0;
                        } else {
                            MainExitException mee = new MainExitException(1, getArgumentError(" -W must be followed by either 0, 1, 2 or nothing"));
                            mee.setUsageError(true);
                            throw mee;
                        }
                    }
                    switch (value) {
                    case 0:
                        verbose = null;
                        break;
                    case 1:
                        verbose = Boolean.FALSE;
                        break;
                    case 2:
                        verbose = Boolean.TRUE;
                        break;
                    }


                    break FOR;
                }
                // FIXME: -x flag not supported
//                    case 'x' :
//                        break;
                case 'X':
                    String extendedOption = grabOptionalValue();

                    if (extendedOption == null) {
                        throw new MainExitException(0, "jruby: missing extended option, listing available options\n" + getExtendedHelp());
                    } else if (extendedOption.equals("-O")) {
                        objectSpaceEnabled = false;
                    } else if (extendedOption.equals("+O")) {
                        objectSpaceEnabled = true;
                    } else if (extendedOption.equals("-C")) {
                        compileMode = CompileMode.OFF;
                    } else if (extendedOption.equals("+C")) {
                        compileMode = CompileMode.FORCE;
                    } else if (extendedOption.equals("-y")) {
                        yarv = true;
                    } else if (extendedOption.equals("-Y")) {
                        yarvCompile = true;
                    } else if (extendedOption.equals("-R")) {
                        rubinius = true;
                    } else {
                        MainExitException mee =
                                new MainExitException(1, "jruby: invalid extended option " + extendedOption + " (-X will list valid options)\n");
                        mee.setUsageError(true);

                        throw mee;
                    }
                    break FOR;
                case '-':
                    if (argument.equals("--command") || argument.equals("--bin")) {
                        characterIndex = argument.length();
                        runBinScript();
                        break;
                    } else if (argument.equals("--compat")) {
                        characterIndex = argument.length();
                        compatVersion = CompatVersion.getVersionFromString(grabValue(getArgumentError("--compat must be RUBY1_8 or RUBY1_9")));
                        if (compatVersion == null) {
                            compatVersion = CompatVersion.RUBY1_8;
                        }
                        break FOR;
                    } else if (argument.equals("--copyright")) {
                        setShowCopyright(true);
                        shouldRunInterpreter = false;
                        break FOR;
                    } else if (argument.equals("--debug")) {
                        compileMode = CompileMode.OFF;
                        FULL_TRACE_ENABLED = true;
                        System.setProperty("jruby.reflection", "true");
                        break FOR;
                    } else if (argument.equals("--jdb")) {
                        debug = true;
                        verbose = Boolean.TRUE;
                        break;
                    } else if (argument.equals("--help")) {
                        shouldPrintUsage = true;
                        shouldRunInterpreter = false;
                        break;
                    } else if (argument.equals("--properties")) {
                        shouldPrintProperties = true;
                        shouldRunInterpreter = false;
                        break;
                    } else if (argument.equals("--version")) {
                        setShowVersion(true);
                        break FOR;
                    } else if (argument.equals("--bytecode")) {
                        setShowBytecode(true);
                        break FOR;
                    } else {
                        if (argument.equals("--")) {
                            // ruby interpreter compatibilty
                            // Usage: ruby [switches] [--] [programfile] [arguments])
                            endOfArguments = true;
                            break;
                        }
                    }
                default:
                    throw new MainExitException(1, "jruby: unknown option " + argument);
                }
            }
        }

        private void runBinScript() {
            String scriptName = grabValue("jruby: provide a bin script to execute");
            if (scriptName.equals("irb")) {
                scriptName = "jirb";
            }

            scriptFileName = scriptName;

            if (!new File(scriptFileName).exists()) {
                try {
                    String jrubyHome = JRubyFile.create(System.getProperty("user.dir"), JRubyFile.getFileProperty("jruby.home")).getCanonicalPath();
                    scriptFileName = JRubyFile.create(jrubyHome + JRubyFile.separator + "bin", scriptName).getCanonicalPath();
                } catch (IOException io) {
                    MainExitException mee = new MainExitException(1, "jruby: Can't determine script filename");
                    mee.setUsageError(true);
                    throw mee;
                }
            }

            // route 'gem' through ruby code in case we're running out of the complete jar
            if (scriptName.equals("gem") || !new File(scriptFileName).exists()) {
                requiredLibraries.add("jruby/commands");
                inlineScript.append("JRuby::Commands." + scriptName);
                inlineScript.append("\n");
                hasInlineScript = true;
            }
            endOfArguments = true;
        }

        private String grabValue(String errorMessage) {
            characterIndex++;
            if (characterIndex < arguments[argumentIndex].length()) {
                return arguments[argumentIndex].substring(characterIndex);
            }
            argumentIndex++;
            if (argumentIndex < arguments.length) {
                return arguments[argumentIndex];
            }

            MainExitException mee = new MainExitException(1, errorMessage);
            mee.setUsageError(true);

            throw mee;
        }

        private String grabOptionalValue() {
            characterIndex++;
            if (characterIndex < arguments[argumentIndex].length()) {
                return arguments[argumentIndex].substring(characterIndex);
            }
            return null;
        }
    }

    public byte[] inlineScript() {
        return inlineScript.toString().getBytes();
    }

    public List<String> requiredLibraries() {
        return requiredLibraries;
    }

    public List<String> loadPaths() {
        return loadPaths;
    }

    public boolean shouldRunInterpreter() {
        if(isShowVersion() && (hasInlineScript || scriptFileName != null)) {
            return true;
        }
        return isShouldRunInterpreter();
    }

    public boolean shouldPrintUsage() {
        return shouldPrintUsage;
    }

    public boolean shouldPrintProperties() {
        return shouldPrintProperties;
    }

    private boolean isSourceFromStdin() {
        return getScriptFileName() == null;
    }

    public boolean isInlineScript() {
        return hasInlineScript;
    }

    public InputStream getScriptSource() {
        try {
            // KCode.NONE is used because KCODE does not affect parse in Ruby 1.8
            // if Ruby 2.0 encoding pragmas are implemented, this will need to change
            if (hasInlineScript) {
                return new ByteArrayInputStream(inlineScript());
            } else if (isSourceFromStdin()) {
                // can't use -v and stdin
                if (isShowVersion()) {
                    return null;
                }
                return getInput();
            } else {
                File file = JRubyFile.create(getCurrentDirectory(), getScriptFileName());
                return new BufferedInputStream(new FileInputStream(file));
            }
        } catch (IOException e) {
            throw new MainExitException(1, "Error opening script file: " + e.getMessage());
        }
    }

    public String displayedFileName() {
        if (hasInlineScript) {
            if (scriptFileName != null) {
                return scriptFileName;
            } else {
                return "-e";
            }
        } else if (isSourceFromStdin()) {
            return "-";
        } else {
            return getScriptFileName();
        }
    }

    private void setScriptFileName(String scriptFileName) {
        this.scriptFileName = scriptFileName;
    }

    public String getScriptFileName() {
        return scriptFileName;
    }

    public boolean isBenchmarking() {
        return benchmarking;
    }

    public boolean isAssumeLoop() {
        return assumeLoop;
    }

    public boolean isAssumePrinting() {
        return assumePrinting;
    }

    public boolean isProcessLineEnds() {
        return processLineEnds;
    }

    public boolean isSplit() {
        return split;
    }

    public boolean isVerbose() {
        return verbose == Boolean.TRUE;
    }

    public Boolean getVerbose() {
        return verbose;
    }

    public boolean isDebug() {
        return debug;
    }

    public boolean isShowVersion() {
        return showVersion;
    }
    
    public boolean isShowBytecode() {
        return showBytecode;
    }

    public boolean isShowCopyright() {
        return showCopyright;
    }

    protected void setShowVersion(boolean showVersion) {
        this.showVersion = showVersion;
    }
    
    protected void setShowBytecode(boolean showBytecode) {
        this.showBytecode = showBytecode;
    }

    protected void setShowCopyright(boolean showCopyright) {
        this.showCopyright = showCopyright;
    }

    public boolean isShouldRunInterpreter() {
        return shouldRunInterpreter;
    }

    public boolean isShouldCheckSyntax() {
        return shouldCheckSyntax;
    }

    public boolean isYARVEnabled() {
        return yarv;
    }

    public String getInputFieldSeparator() {
        return inputFieldSeparator;
    }

    public boolean isRubiniusEnabled() {
        return rubinius;
    }

    public boolean isYARVCompileEnabled() {
        return yarvCompile;
    }

    public KCode getKCode() {
        return kcode;
    }

    public String getRecordSeparator() {
        return recordSeparator;
    }

    public int getSafeLevel() {
        return safeLevel;
    }

    public void setRecordSeparator(String recordSeparator) {
        this.recordSeparator = recordSeparator;
    }

    public ClassCache getClassCache() {
        return classCache;
    }

    public void setClassCache(ClassCache classCache) {
        this.classCache = classCache;
    }

    public Map getOptionGlobals() {
        return optionGlobals;
    }
    
    public boolean isManagementEnabled() {
        return managementEnabled;
    }
    
    public Set getExcludedMethods() {
        return excludedMethods;
    }
    
}
/*
 **** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2002-2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2005 Charles O Nutter <headius@headius.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.runtime.Block;
import org.jruby.runtime.BlockBody;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;

/** Implementation of the Integer class.
 *
 * @author  jpetersen
 */
@JRubyClass(name="Integer", parent="Numeric", include="Precision")
public abstract class RubyInteger extends RubyNumeric { 

    public static RubyClass createIntegerClass(Ruby runtime) {
        RubyClass integer = runtime.defineClass("Integer", runtime.getNumeric(),
                ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
        runtime.setInteger(integer);
        integer.kindOf = new RubyModule.KindOf() {
                public boolean isKindOf(IRubyObject obj, RubyModule type) {
                    return obj instanceof RubyInteger;
                }
            };

        integer.getSingletonClass().undefineMethod("new");

        integer.includeModule(runtime.getPrecision());
        
        integer.defineAnnotatedMethods(RubyInteger.class);
        
        return integer;
    }

    public RubyInteger(Ruby runtime, RubyClass rubyClass) {
        super(runtime, rubyClass);
    }
    
    public RubyInteger(Ruby runtime, RubyClass rubyClass, boolean useObjectSpace) {
        super(runtime, rubyClass, useObjectSpace);
    }     

    public RubyInteger convertToInteger() {
    	return this;
    }

    // conversion
    protected RubyFloat toFloat() {
        return RubyFloat.newFloat(getRuntime(), getDoubleValue());
    }

    /*  ================
     *  Instance Methods
     *  ================ 
     */

    /** int_int_p
     * 
     */
    @JRubyMethod(name = "integer?")
    public IRubyObject integer_p() {
        return getRuntime().getTrue();
    }

    /** int_upto
     * 
     */
    @JRubyMethod(name = "upto", frame = true)
    public IRubyObject upto(ThreadContext context, IRubyObject to, Block block) {
        final Ruby runtime = getRuntime();

        if (this instanceof RubyFixnum && to instanceof RubyFixnum) {
            RubyFixnum toFixnum = (RubyFixnum) to;
            final long toValue = toFixnum.getLongValue();
            final long fromValue = getLongValue();

            if (block.getBody().getArgumentType() == BlockBody.ZERO_ARGS) {
                final IRubyObject nil = runtime.getNil();
                for (long i = fromValue; i <= toValue; i++) {
                    block.yield(context, nil);
                }
            } else {
                for (long i = fromValue; i <= toValue; i++) {
                    block.yield(context, RubyFixnum.newFixnum(runtime, i));
                }
            }
        } else {
            RubyNumeric i = this;

            while (true) {
                if (i.callMethod(context, MethodIndex.OP_GT, ">", to).isTrue()) {
                    break;
                }
                block.yield(context, i);
                i = (RubyNumeric) i.callMethod(context, MethodIndex.OP_PLUS, "+", RubyFixnum.one(runtime));
            }
        }
        return this;
    }

    /** int_downto
     * 
     */
    // TODO: Make callCoerced work in block context...then fix downto, step, and upto.
    @JRubyMethod(name = "downto", frame = true)
    public IRubyObject downto(ThreadContext context, IRubyObject to, Block block) {
        final Ruby runtime = getRuntime();

        if (this instanceof RubyFixnum && to instanceof RubyFixnum) {
            RubyFixnum toFixnum = (RubyFixnum) to;
            final long toValue = toFixnum.getLongValue();
            if (block.getBody().getArgumentType() == BlockBody.ZERO_ARGS) {
                final IRubyObject nil = runtime.getNil();
                for (long i = getLongValue(); i >= toValue; i--) {
                    block.yield(context, nil);
                }
            } else {
                for (long i = getLongValue(); i >= toValue; i--) {
                    block.yield(context, RubyFixnum.newFixnum(getRuntime(), i));
                }
            }
        } else {
            RubyNumeric i = this;

            while (true) {
                if (i.callMethod(context, MethodIndex.OP_LT, "<", to).isTrue()) {
                    break;
                }
                block.yield(context, i);
                i = (RubyNumeric) i.callMethod(context, MethodIndex.OP_MINUS, "-", RubyFixnum.one(getRuntime()));
            }
        }
        return this;
    }

    @JRubyMethod(name = "times", frame = true)
    public IRubyObject times(ThreadContext context, Block block) {
        final Ruby runtime = context.getRuntime();

        if (this instanceof RubyFixnum) {
            final long value = getLongValue();
            if (block.getBody().getArgumentType() == BlockBody.ZERO_ARGS) {
                final IRubyObject nil = runtime.getNil();
                for (long i = 0; i < value; i++) {
                    block.yield(context, nil);
                }
            } else {
                for (long i = 0; i < value; i++) {
                    block.yield(context, RubyFixnum.newFixnum(runtime, i));
                }
            }
        } else {
            RubyNumeric i = RubyFixnum.zero(runtime);
            while (true) {
                if (!i.callMethod(context, MethodIndex.OP_LT, "<", this).isTrue()) {
                    break;
                }
                block.yield(context, i);
                i = (RubyNumeric) i.callMethod(context, MethodIndex.OP_PLUS, "+", RubyFixnum.one(runtime));
            }
        }

        return this;
    }

    /** int_succ
     * 
     */
    @JRubyMethod(name = {"succ", "next"})
    public IRubyObject succ(ThreadContext context) {
        if (this instanceof RubyFixnum) {
            return RubyFixnum.newFixnum(getRuntime(), getLongValue() + 1L);
        } else {
            return callMethod(context, MethodIndex.OP_PLUS, "+", RubyFixnum.one(getRuntime()));
        }
    }

    /** int_chr
     * 
     */
    @JRubyMethod(name = "chr")
    public RubyString chr() {
        if (getLongValue() < 0 || getLongValue() > 0xff) {
            throw getRuntime().newRangeError(this.toString() + " out of char range");
        }
        return RubyString.newString(getRuntime(), new ByteList(new byte[]{(byte)getLongValue()}, false)); 
    }

    /** int_to_i
     * 
     */
    @JRubyMethod(name = {"to_i", "to_int", "floor", "ceil", "round", "truncate"})
    public RubyInteger to_i() {
        return this;
    }
    
    /** integer_to_r
     * 
     */
    @JRubyMethod(name = "to_r", compat = CompatVersion.RUBY1_9)
    public IRubyObject to_r(ThreadContext context) {
        return RubyRational.newRationalCanonicalize(context, this);
    }
    

    @JRubyMethod(name = {"odd?"})
    public static RubyBoolean odd_p(ThreadContext context, IRubyObject recv) {
        if(recv.callMethod(context, "%", recv.getRuntime().newFixnum(2)) != RubyFixnum.zero(recv.getRuntime())) {
            return recv.getRuntime().getTrue();
        }
        return recv.getRuntime().getFalse();
    }

    @JRubyMethod(name = {"even?"})
    public static RubyBoolean even_p(ThreadContext context, IRubyObject recv) {
        if(recv.callMethod(context, "%", recv.getRuntime().newFixnum(2)) == RubyFixnum.zero(recv.getRuntime())) {
            return recv.getRuntime().getTrue();
        }
        return recv.getRuntime().getFalse();
    }

    @JRubyMethod
    public static IRubyObject pred(ThreadContext context, IRubyObject recv) {
        return recv.callMethod(context, "-", recv.getRuntime().newFixnum(1));
    }


    /*  ================
     *  Singleton Methods
     *  ================ 
     */

    /** rb_int_induced_from
     * 
     */
    @JRubyMethod(name = "induced_from", meta = true)
    public static IRubyObject induced_from(ThreadContext context, IRubyObject recv, IRubyObject other) {
        if (other instanceof RubyFixnum || other instanceof RubyBignum) {
            return other;
        } else if (other instanceof RubyFloat || other instanceof RubyRational) {
            return other.callMethod(context, MethodIndex.TO_I, "to_i");
        } else {
            throw recv.getRuntime().newTypeError(
                    "failed to convert " + other.getMetaClass().getName() + " into Integer");
        }
    }
}
/*
 **** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2002-2006 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004-2006 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2006 Evan Buswell <ebuswell@gmail.com>
 * Copyright (C) 2007 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.io.EOFException;
import java.io.FileDescriptor;
import java.io.FilterInputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.nio.channels.Channel;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.Pipe;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import java.util.concurrent.atomic.AtomicInteger;
import org.jruby.anno.FrameField;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.common.IRubyWarnings.ID;
import org.jruby.exceptions.RaiseException;
import org.jruby.ext.posix.util.FieldAccess;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.CallType;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;
import org.jruby.util.io.Stream;
import org.jruby.util.io.ModeFlags;
import org.jruby.util.ShellLauncher;
import org.jruby.util.TypeConverter;
import org.jruby.util.io.BadDescriptorException;
import org.jruby.util.io.ChannelStream;
import org.jruby.util.io.InvalidValueException;
import org.jruby.util.io.PipeException;
import org.jruby.util.io.FileExistsException;
import org.jruby.util.io.STDIO;
import org.jruby.util.io.OpenFile;
import org.jruby.util.io.ChannelDescriptor;

import static org.jruby.CompatVersion.*;

/**
 * 
 * @author jpetersen
 */
@JRubyClass(name="IO", include="Enumerable")
public class RubyIO extends RubyObject {
    protected OpenFile openFile;
    protected List<RubyThread> blockingThreads;
    
    public void registerDescriptor(ChannelDescriptor descriptor) {
        getRuntime().getDescriptors().put(new Integer(descriptor.getFileno()), new WeakReference<ChannelDescriptor>(descriptor));
    }
    
    public void unregisterDescriptor(int aFileno) {
        getRuntime().getDescriptors().remove(new Integer(aFileno));
    }
    
    public ChannelDescriptor getDescriptorByFileno(int aFileno) {
        Reference<ChannelDescriptor> reference = getRuntime().getDescriptors().get(new Integer(aFileno));
        if (reference == null) {
            return null;
        }
        return reference.get();
    }
    
    // FIXME can't use static; would interfere with other runtimes in the same JVM
    protected static AtomicInteger filenoIndex = new AtomicInteger(2);
    
    public static int getNewFileno() {
        return filenoIndex.incrementAndGet();
    }

    // This should only be called by this and RubyFile.
    // It allows this object to be created without a IOHandler.
    public RubyIO(Ruby runtime, RubyClass type) {
        super(runtime, type);
        
        openFile = new OpenFile();
    }

    public RubyIO(Ruby runtime, OutputStream outputStream) {
        super(runtime, runtime.getIO());
        
        // We only want IO objects with valid streams (better to error now). 
        if (outputStream == null) {
            throw runtime.newIOError("Opening invalid stream");
        }
        
        openFile = new OpenFile();
        
        try {
            openFile.setMainStream(new ChannelStream(runtime, new ChannelDescriptor(Channels.newChannel(outputStream), getNewFileno(), new FileDescriptor())));
        } catch (InvalidValueException e) {
            throw getRuntime().newErrnoEINVALError();
        }
        
        openFile.setMode(OpenFile.WRITABLE | OpenFile.APPEND);
        
        registerDescriptor(openFile.getMainStream().getDescriptor());
    }
    
    public RubyIO(Ruby runtime, InputStream inputStream) {
        super(runtime, runtime.getIO());
        
        if (inputStream == null) {
            throw runtime.newIOError("Opening invalid stream");
        }
        
        openFile = new OpenFile();
        
        try {
            openFile.setMainStream(new ChannelStream(runtime, new ChannelDescriptor(Channels.newChannel(inputStream), getNewFileno(), new FileDescriptor())));
        } catch (InvalidValueException e) {
            throw getRuntime().newErrnoEINVALError();
        }
        
        openFile.setMode(OpenFile.READABLE);
        
        registerDescriptor(openFile.getMainStream().getDescriptor());
    }
    
    public RubyIO(Ruby runtime, Channel channel) {
        super(runtime, runtime.getIO());
        
        // We only want IO objects with valid streams (better to error now). 
        if (channel == null) {
            throw runtime.newIOError("Opening invalid stream");
        }
        
        openFile = new OpenFile();
        
        try {
            openFile.setMainStream(new ChannelStream(runtime, new ChannelDescriptor(channel, getNewFileno(), new FileDescriptor())));
        } catch (InvalidValueException e) {
            throw getRuntime().newErrnoEINVALError();
        }
        
        openFile.setMode(openFile.getMainStream().getModes().getOpenFileFlags());
        
        registerDescriptor(openFile.getMainStream().getDescriptor());
    }

    public RubyIO(Ruby runtime, ShellLauncher.POpenProcess process, ModeFlags modes) {
    	super(runtime, runtime.getIO());
        
        openFile = new OpenFile();
        
        openFile.setMode(modes.getOpenFileFlags() | OpenFile.SYNC);
        openFile.setProcess(process);

        try {
            if (openFile.isReadable()) {
                Channel inChannel;
                if (process.getInput() != null) {
                    // NIO-based
                    inChannel = process.getInput();
                } else {
                    // Stream-based
                    inChannel = Channels.newChannel(process.getInputStream());
                }
                
                ChannelDescriptor main = new ChannelDescriptor(
                        inChannel,
                        getNewFileno(),
                        new FileDescriptor());
                main.setCanBeSeekable(false);
                
                openFile.setMainStream(new ChannelStream(getRuntime(), main));
                registerDescriptor(main);
            }
            
            if (openFile.isWritable()) {
                Channel outChannel;
                if (process.getOutput() != null) {
                    // NIO-based
                    outChannel = process.getOutput();
                } else {
                    outChannel = Channels.newChannel(process.getOutputStream());
                }

                ChannelDescriptor pipe = new ChannelDescriptor(
                        outChannel,
                        getNewFileno(),
                        new FileDescriptor());
                pipe.setCanBeSeekable(false);
                
                if (openFile.getMainStream() != null) {
                    openFile.setPipeStream(new ChannelStream(getRuntime(), pipe));
                } else {
                    openFile.setMainStream(new ChannelStream(getRuntime(), pipe));
                }
                
                registerDescriptor(pipe);
            }
        } catch (InvalidValueException e) {
            throw getRuntime().newErrnoEINVALError();
        }
    }
    
    public RubyIO(Ruby runtime, STDIO stdio) {
        super(runtime, runtime.getIO());
        
        openFile = new OpenFile();

        try {
            switch (stdio) {
            case IN:
                openFile.setMainStream(
                        new ChannelStream(
                            runtime, 
                            // special constructor that accepts stream, not channel
                            new ChannelDescriptor(runtime.getIn(), 0, new ModeFlags(ModeFlags.RDONLY), FileDescriptor.in),
                            FileDescriptor.in));
                break;
            case OUT:
                openFile.setMainStream(
                        new ChannelStream(
                            runtime, 
                            new ChannelDescriptor(Channels.newChannel(runtime.getOut()), 1, new ModeFlags(ModeFlags.WRONLY | ModeFlags.APPEND), FileDescriptor.out),
                            FileDescriptor.out));
                openFile.getMainStream().setSync(true);
                break;
            case ERR:
                openFile.setMainStream(
                        new ChannelStream(
                            runtime, 
                            new ChannelDescriptor(Channels.newChannel(runtime.getErr()), 2, new ModeFlags(ModeFlags.WRONLY | ModeFlags.APPEND), FileDescriptor.err), 
                            FileDescriptor.err));
                openFile.getMainStream().setSync(true);
                break;
            }
        } catch (InvalidValueException ex) {
            throw getRuntime().newErrnoEINVALError();
        }
        
        openFile.setMode(openFile.getMainStream().getModes().getOpenFileFlags());
        
        registerDescriptor(openFile.getMainStream().getDescriptor());        
    }
    
    public static RubyIO newIO(Ruby runtime, Channel channel) {
        return new RubyIO(runtime, channel);
    }
    
    public OpenFile getOpenFile() {
        return openFile;
    }
    
    protected OpenFile getOpenFileChecked() {
        openFile.checkClosed(getRuntime());
        return openFile;
    }
    
    private static ObjectAllocator IO_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return new RubyIO(runtime, klass);
        }
    };

    public static RubyClass createIOClass(Ruby runtime) {
        RubyClass ioClass = runtime.defineClass("IO", runtime.getObject(), IO_ALLOCATOR);
        ioClass.kindOf = new RubyModule.KindOf() {
            @Override
            public boolean isKindOf(IRubyObject obj, RubyModule type) {
                return obj instanceof RubyIO;
            }
        };

        ioClass.includeModule(runtime.getEnumerable());
        
        // TODO: Implement tty? and isatty.  We have no real capability to
        // determine this from java, but if we could set tty status, then
        // we could invoke jruby differently to allow stdin to return true
        // on this.  This would allow things like cgi.rb to work properly.
        
        ioClass.defineAnnotatedMethods(RubyIO.class);

        // Constants for seek
        ioClass.fastSetConstant("SEEK_SET", runtime.newFixnum(Stream.SEEK_SET));
        ioClass.fastSetConstant("SEEK_CUR", runtime.newFixnum(Stream.SEEK_CUR));
        ioClass.fastSetConstant("SEEK_END", runtime.newFixnum(Stream.SEEK_END));

        return ioClass;
    }

    public OutputStream getOutStream() {
        return getOpenFileChecked().getMainStream().newOutputStream();
    }

    public InputStream getInStream() {
        return getOpenFileChecked().getMainStream().newInputStream();
    }

    public Channel getChannel() {
        if (getOpenFileChecked().getMainStream() instanceof ChannelStream) {
            return ((ChannelStream) openFile.getMainStream()).getDescriptor().getChannel();
        } else {
            return null;
        }
    }
    
    public Stream getHandler() {
        return getOpenFileChecked().getMainStream();
    }

    @JRubyMethod(name = "reopen", required = 1, optional = 1)
    public IRubyObject reopen(ThreadContext context, IRubyObject[] args) throws InvalidValueException {
        Ruby runtime = context.getRuntime();
        
    	if (args.length < 1) {
            throw runtime.newArgumentError("wrong number of arguments");
    	}
    	
    	IRubyObject tmp = TypeConverter.convertToTypeWithCheck(args[0], runtime.getIO(), 
                MethodIndex.getIndex("to_io"), "to_io");
        
    	if (!tmp.isNil()) {
            try {
                RubyIO ios = (RubyIO) tmp;

                if (ios.openFile == this.openFile) {
                    return this;
                }

                OpenFile originalFile = ios.getOpenFileChecked();
                OpenFile selfFile = getOpenFileChecked();

                long pos = 0;
                if (originalFile.isReadable()) {
                    pos = originalFile.getMainStream().fgetpos();
                }

                if (originalFile.getPipeStream() != null) {
                    originalFile.getPipeStream().fflush();
                } else if (originalFile.isWritable()) {
                    originalFile.getMainStream().fflush();
                }

                if (selfFile.isWritable()) {
                    selfFile.getWriteStream().fflush();
                }

                selfFile.setMode(originalFile.getMode());
                selfFile.setProcess(originalFile.getProcess());
                selfFile.setLineNumber(originalFile.getLineNumber());
                selfFile.setPath(originalFile.getPath());
                selfFile.setFinalizer(originalFile.getFinalizer());

                ChannelDescriptor selfDescriptor = selfFile.getMainStream().getDescriptor();
                ChannelDescriptor originalDescriptor = originalFile.getMainStream().getDescriptor();

                // confirm we're not reopening self's channel
                if (selfDescriptor.getChannel() != originalDescriptor.getChannel()) {
                    // check if we're a stdio IO, and ensure we're not badly mutilated
                    if (selfDescriptor.getFileno() >=0 && selfDescriptor.getFileno() <= 2) {
                        selfFile.getMainStream().clearerr();
                        
                        // dup2 new fd into self to preserve fileno and references to it
                        originalDescriptor.dup2Into(selfDescriptor);
                        
                        // re-register, since fileno points at something new now
                        registerDescriptor(selfDescriptor);
                    } else {
                        Stream pipeFile = selfFile.getPipeStream();
                        int mode = selfFile.getMode();
                        selfFile.getMainStream().fclose();
                        selfFile.setPipeStream(null);

                        // TODO: turn off readable? am I reading this right?
                        // This only seems to be used while duping below, since modes gets
                        // reset to actual modes afterward
                        //fptr->mode &= (m & FMODE_READABLE) ? ~FMODE_READABLE : ~FMODE_WRITABLE;

                        if (pipeFile != null) {
                            selfFile.setMainStream(ChannelStream.fdopen(runtime, originalDescriptor, new ModeFlags()));
                            selfFile.setPipeStream(pipeFile);
                        } else {
                            selfFile.setMainStream(
                                    new ChannelStream(
                                        runtime,
                                        originalDescriptor.dup2(selfDescriptor.getFileno())));
                            
                            // re-register the descriptor
                            registerDescriptor(selfFile.getMainStream().getDescriptor());
                            
                            // since we're not actually duping the incoming channel into our handler, we need to
                            // copy the original sync behavior from the other handler
                            selfFile.getMainStream().setSync(selfFile.getMainStream().isSync());
                        }
                        selfFile.setMode(mode);
                    }
                    
                    // TODO: anything threads attached to original fd are notified of the close...
                    // see rb_thread_fd_close
                    
                    if (originalFile.isReadable() && pos >= 0) {
                        selfFile.seek(pos, Stream.SEEK_SET);
                        originalFile.seek(pos, Stream.SEEK_SET);
                    }
                }

                if (selfFile.getPipeStream() != null && selfDescriptor.getFileno() != selfFile.getPipeStream().getDescriptor().getFileno()) {
                    int fd = selfFile.getPipeStream().getDescriptor().getFileno();
                    
                    if (originalFile.getPipeStream() == null) {
                        selfFile.getPipeStream().fclose();
                        selfFile.setPipeStream(null);
                    } else if (fd != originalFile.getPipeStream().getDescriptor().getFileno()) {
                        selfFile.getPipeStream().fclose();
                        ChannelDescriptor newFD2 = originalFile.getPipeStream().getDescriptor().dup2(fd);
                        selfFile.setPipeStream(ChannelStream.fdopen(runtime, newFD2, getIOModes(runtime, "w")));
                        
                        // re-register, since fileno points at something new now
                        registerDescriptor(newFD2);
                    }
                }
                
                // TODO: restore binary mode
    //            if (fptr->mode & FMODE_BINMODE) {
    //                rb_io_binmode(io);
    //            }
                
                // TODO: set our metaclass to target's class (i.e. scary!)

            } catch (IOException ex) { // TODO: better error handling
                throw runtime.newIOError("could not reopen: " + ex.getMessage());
            } catch (BadDescriptorException ex) {
                throw runtime.newIOError("could not reopen: " + ex.getMessage());
            } catch (PipeException ex) {
                throw runtime.newIOError("could not reopen: " + ex.getMessage());
            }
        } else {
            IRubyObject pathString = args[0].convertToString();
            
            // TODO: check safe, taint on incoming string
            
            if (openFile == null) {
                openFile = new OpenFile();
            }
            
            try {
                ModeFlags modes;
                if (args.length > 1) {
                    IRubyObject modeString = args[1].convertToString();
                    modes = getIOModes(runtime, modeString.toString());

                    openFile.setMode(modes.getOpenFileFlags());
                } else {
                    modes = getIOModes(runtime, "r");
                }

                String path = pathString.toString();
                
                // Ruby code frequently uses a platform check to choose "NUL:" on windows
                // but since that check doesn't work well on JRuby, we help it out
                
                openFile.setPath(path);
            
                if (openFile.getMainStream() == null) {
                    try {
                        openFile.setMainStream(ChannelStream.fopen(runtime, path, modes));
                    } catch (FileExistsException fee) {
                        throw runtime.newErrnoEEXISTError(path);
                    }
                    
                    registerDescriptor(openFile.getMainStream().getDescriptor());
                    if (openFile.getPipeStream() != null) {
                        openFile.getPipeStream().fclose();
                        unregisterDescriptor(openFile.getPipeStream().getDescriptor().getFileno());
                        openFile.setPipeStream(null);
                    }
                    return this;
                } else {
                    // TODO: This is an freopen in MRI, this is close, but not quite the same
                    openFile.getMainStream().freopen(path, getIOModes(runtime, openFile.getModeAsString(runtime)));

                    // re-register
                    registerDescriptor(openFile.getMainStream().getDescriptor());

                    if (openFile.getPipeStream() != null) {
                        // TODO: pipe handler to be reopened with path and "w" mode
                    }
                }
            } catch (PipeException pe) {
                throw runtime.newErrnoEPIPEError();
            } catch (IOException ex) {
                throw runtime.newIOErrorFromException(ex);
            } catch (BadDescriptorException ex) {
                throw runtime.newErrnoEBADFError();
            } catch (InvalidValueException e) {
            	throw runtime.newErrnoEINVALError();
            }
        }
        
        // A potentially previously close IO is being 'reopened'.
        return this;
    }
    
    public static ModeFlags getIOModes(Ruby runtime, String modesString) throws InvalidValueException {
        return new ModeFlags(getIOModesIntFromString(runtime, modesString));
    }
        
    public static int getIOModesIntFromString(Ruby runtime, String modesString) {
        int modes = 0;
        int length = modesString.length();

        if (length == 0) {
            throw runtime.newArgumentError("illegal access mode");
        }

        switch (modesString.charAt(0)) {
        case 'r' :
            modes |= ModeFlags.RDONLY;
            break;
        case 'a' :
            modes |= ModeFlags.APPEND | ModeFlags.WRONLY | ModeFlags.CREAT;
            break;
        case 'w' :
            modes |= ModeFlags.WRONLY | ModeFlags.TRUNC | ModeFlags.CREAT;
            break;
        default :
            throw runtime.newArgumentError("illegal access mode " + modes);
        }

        for (int n = 1; n < length; n++) {
            switch (modesString.charAt(n)) {
            case 'b':
                modes |= ModeFlags.BINARY;
                break;
            case '+':
                modes = (modes & ~ModeFlags.ACCMODE) | ModeFlags.RDWR;
                break;
            default:
                throw runtime.newArgumentError("illegal access mode " + modes);
            }
        }

        return modes;
    }

    private static ByteList getSeparatorFromArgs(Ruby runtime, IRubyObject[] args, int idx) {
        IRubyObject sepVal;

        if (args.length > idx) {
            sepVal = args[idx];
        } else {
            sepVal = runtime.getRecordSeparatorVar().get();
        }

        ByteList separator = sepVal.isNil() ? null : sepVal.convertToString().getByteList();

        if (separator != null && separator.realSize == 0) {
            separator = Stream.PARAGRAPH_DELIMETER;
        }

        return separator;
    }

    private ByteList getSeparatorForGets(Ruby runtime, IRubyObject[] args) {
        return getSeparatorFromArgs(runtime, args, 0);
    }

    public IRubyObject getline(Ruby runtime, ByteList separator) {
        try {
            OpenFile myOpenFile = getOpenFileChecked();

            myOpenFile.checkReadable(runtime);
            myOpenFile.setReadBuffered();

            boolean isParagraph = separator == Stream.PARAGRAPH_DELIMETER;
            separator = (separator == Stream.PARAGRAPH_DELIMETER) ?
                    Stream.PARAGRAPH_SEPARATOR : separator;
            
            if (isParagraph) {
                swallow('\n');
            }
            
            if (separator == null) {
                IRubyObject str = readAll(null);
                if (((RubyString)str).getByteList().length() == 0) {
                    return runtime.getNil();
                }
                incrementLineno(runtime, myOpenFile);
                return str;
            } else if (separator.length() == 1) {
                return getlineFast(runtime, separator.get(0));
            } else {
                Stream readStream = myOpenFile.getMainStream();
                int c = -1;
                int n = -1;
                int newline = separator.get(separator.length() - 1) & 0xFF;

                ByteList buf = new ByteList(0);
                boolean update = false;
                
                while (true) {
                    do {
                        readCheck(readStream);
                        readStream.clearerr();
                        
                        try {
                            n = readStream.getline(buf, (byte) newline);
                            c = buf.length() > 0 ? buf.get(buf.length() - 1) & 0xff : -1;
                        } catch (EOFException e) {
                            n = -1;
                        }

                        if (n == -1) {
                            if (!readStream.isBlocking() && (readStream instanceof ChannelStream)) {
                                if(!(waitReadable(((ChannelStream)readStream).getDescriptor()))) {
                                    throw runtime.newIOError("bad file descriptor: " + openFile.getPath());
                                }

                                continue;
                            } else {
                                break;
                            }
                        }
            
                        update = true;
                    } while (c != newline); // loop until we see the nth separator char
                    
                    // if we hit EOF, we're done
                    if (n == -1) {
                        break;
                    }
                    
                    // if we've found the last char of the separator,
                    // and we've found at least as many characters as separator length,
                    // and the last n characters of our buffer match the separator, we're done
                    if (c == newline && buf.length() >= separator.length() &&
                            0 == ByteList.memcmp(buf.unsafeBytes(), buf.begin + buf.realSize - separator.length(), separator.unsafeBytes(), separator.begin, separator.realSize)) {
                        break;
                    }
                }
                
                if (isParagraph) {
                    if (c != -1) {
                        swallow('\n');
                    }
                }
                
                if (!update) {
                    return runtime.getNil();
                } else {
                    incrementLineno(runtime, myOpenFile);
                    RubyString str = RubyString.newString(runtime, buf);
                    str.setTaint(true);

                    return str;
                }
            }
        } catch (PipeException ex) {
            throw runtime.newErrnoEPIPEError();
        } catch (InvalidValueException ex) {
            throw runtime.newErrnoEINVALError();
        } catch (EOFException e) {
            return runtime.getNil();
        } catch (BadDescriptorException e) {
            throw runtime.newErrnoEBADFError();
        } catch (IOException e) {
            throw runtime.newIOError(e.getMessage());
        }
    }

    private void incrementLineno(Ruby runtime, OpenFile myOpenFile) {
        int lineno = myOpenFile.getLineNumber() + 1;
        myOpenFile.setLineNumber(lineno);
        runtime.getGlobalVariables().set("$.", runtime.newFixnum(lineno));
        // this is for a range check, near as I can tell
        RubyNumeric.int2fix(runtime, myOpenFile.getLineNumber());
    }

    protected boolean swallow(int term) throws IOException, BadDescriptorException {
        Stream readStream = openFile.getMainStream();
        int c;
        
        do {
            readCheck(readStream);
            
            try {
                c = readStream.fgetc();
            } catch (EOFException e) {
                c = -1;
            }
            
            if (c != term) {
                readStream.ungetc(c);
                return true;
            }
        } while (c != -1);
        
        return false;
    }
    
    public IRubyObject getlineFast(Ruby runtime, int delim) throws IOException, BadDescriptorException {
        Stream readStream = openFile.getMainStream();
        int c = -1;

        ByteList buf = new ByteList(0);
        boolean update = false;
        do {
            readCheck(readStream);
            readStream.clearerr();
            int n;
            try {
                n = readStream.getline(buf, (byte) delim);
                c = buf.length() > 0 ? buf.get(buf.length() - 1) & 0xff : -1;
            } catch (EOFException e) {
                n = -1;
            }
            
            if (n == -1) {
                if (!readStream.isBlocking() && (readStream instanceof ChannelStream)) {
                    if(!(waitReadable(((ChannelStream)readStream).getDescriptor()))) {
                        throw runtime.newIOError("bad file descriptor: " + openFile.getPath());
                    }
                    continue;
                } else {
                    break;
                }
            }
            
            update = true;
        } while (c != delim);

        if (!update) {
            return runtime.getNil();
        } else {
            incrementLineno(runtime, openFile);
            RubyString str = RubyString.newString(runtime, buf);
            str.setTaint(true);
            return str;
        }
    }
    // IO class methods.

    @JRubyMethod(name = {"new", "for_fd"}, rest = true, frame = true, meta = true)
    public static IRubyObject newInstance(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        RubyClass klass = (RubyClass)recv;
        
        if (block.isGiven()) {
            String className = klass.getName();
            context.getRuntime().getWarnings().warn(
                    ID.BLOCK_NOT_ACCEPTED,
                    className + "::new() does not take block; use " + className + "::open() instead",
                    className + "::open()");
        }
        
        return klass.newInstance(context, args, block);
    }

    @JRubyMethod(name = "initialize", required = 1, optional = 1, frame = true, visibility = Visibility.PRIVATE)
    public IRubyObject initialize(IRubyObject[] args, Block unusedBlock) {
        int argCount = args.length;
        ModeFlags modes;
        
        int fileno = RubyNumeric.fix2int(args[0]);
        
        try {
            ChannelDescriptor descriptor = getDescriptorByFileno(fileno);
            
            if (descriptor == null) {
                throw getRuntime().newErrnoEBADFError();
            }
            
            descriptor.checkOpen();
            
            if (argCount == 2) {
                if (args[1] instanceof RubyFixnum) {
                    modes = new ModeFlags(RubyFixnum.fix2long(args[1]));
                } else {
                    modes = getIOModes(getRuntime(), args[1].convertToString().toString());
                }
            } else {
                // use original modes
                modes = descriptor.getOriginalModes();
            }

            openFile.setMode(modes.getOpenFileFlags());
        
            openFile.setMainStream(fdopen(descriptor, modes));
        } catch (BadDescriptorException ex) {
            throw getRuntime().newErrnoEBADFError();
        } catch (InvalidValueException ive) {
            throw getRuntime().newErrnoEINVALError();
        }
        
        return this;
    }
    
    protected Stream fdopen(ChannelDescriptor existingDescriptor, ModeFlags modes) throws InvalidValueException {
        // See if we already have this descriptor open.
        // If so then we can mostly share the handler (keep open
        // file, but possibly change the mode).
        
        if (existingDescriptor == null) {
            // redundant, done above as well
            
            // this seems unlikely to happen unless it's a totally bogus fileno
            // ...so do we even need to bother trying to create one?
            
            // IN FACT, we should probably raise an error, yes?
            throw getRuntime().newErrnoEBADFError();
            
//            if (mode == null) {
//                mode = "r";
//            }
//            
//            try {
//                openFile.setMainStream(streamForFileno(getRuntime(), fileno));
//            } catch (BadDescriptorException e) {
//                throw getRuntime().newErrnoEBADFError();
//            } catch (IOException e) {
//                throw getRuntime().newErrnoEBADFError();
//            }
//            //modes = new IOModes(getRuntime(), mode);
//            
//            registerStream(openFile.getMainStream());
        } else {
            // We are creating a new IO object that shares the same
            // IOHandler (and fileno).
            return ChannelStream.fdopen(getRuntime(), existingDescriptor, modes);
        }
    }

    @JRubyMethod(name = "open", required = 1, optional = 2, frame = true, meta = true)
    public static IRubyObject open(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        Ruby runtime = context.getRuntime();
        RubyClass klass = (RubyClass)recv;
        
        RubyIO io = (RubyIO)klass.newInstance(context, args, block);

        if (block.isGiven()) {
            try {
                return block.yield(context, io);
            } finally {
                try {
                    io.getMetaClass().invoke(context, io, "close", IRubyObject.NULL_ARRAY, CallType.FUNCTIONAL, Block.NULL_BLOCK);
                } catch (RaiseException re) {
                    RubyException rubyEx = re.getException();
                    if (rubyEx.kind_of_p(context, runtime.getStandardError()).isTrue()) {
                        // MRI behavior: swallow StandardErorrs
                    } else {
                        throw re;
                    }
                }
            }
        }

        return io;
    }

    // This appears to be some windows-only mode.  On a java platform this is a no-op
    @JRubyMethod(name = "binmode")
    public IRubyObject binmode() {
            return this;
    }
    
    /** @deprecated will be removed in 1.2 */
    protected void checkInitialized() {
        if (openFile == null) {
            throw getRuntime().newIOError("uninitialized stream");
        }
    }
    
    /** @deprecated will be removed in 1.2 */
    protected void checkClosed() {
        if (openFile.getMainStream() == null && openFile.getPipeStream() == null) {
            throw getRuntime().newIOError("closed stream");
        }
    }
    
    @JRubyMethod(name = "syswrite", required = 1)
    public IRubyObject syswrite(ThreadContext context, IRubyObject obj) {
        Ruby runtime = context.getRuntime();
        
        try {
            RubyString string = obj.asString();
            OpenFile myOpenFile = getOpenFileChecked();
            
            myOpenFile.checkWritable(runtime);
            
            Stream writeStream = myOpenFile.getWriteStream();
            
            if (myOpenFile.isWriteBuffered()) {
                runtime.getWarnings().warn(ID.SYSWRITE_BUFFERED_IO, "syswrite for buffered IO");
            }
            
            if (!writeStream.getDescriptor().isWritable()) {
                myOpenFile.checkClosed(runtime);
            }
            
            int read = writeStream.getDescriptor().write(string.getByteList());
            
            if (read == -1) {
                // TODO? I think this ends up propagating from normal Java exceptions
                // sys_fail(openFile.getPath())
            }
            
            return runtime.newFixnum(read);
        } catch (InvalidValueException ex) {
            throw runtime.newErrnoEINVALError();
        } catch (PipeException ex) {
            throw runtime.newErrnoEPIPEError();
        } catch (BadDescriptorException e) {
            throw runtime.newErrnoEBADFError();
        } catch (IOException e) {
            throw runtime.newSystemCallError(e.getMessage());
        }
    }
    
    @JRubyMethod(name = "write_nonblock", required = 1)
    public IRubyObject write_nonblock(ThreadContext context, IRubyObject obj) {
        // MRI behavior: always check whether the file is writable
        // or not, even if we are to write 0 bytes.
        OpenFile myOpenFile = getOpenFileChecked();
        try {
            myOpenFile.checkWritable(context.getRuntime());
        } catch (IOException ex) {
            throw context.getRuntime().newIOErrorFromException(ex);
        } catch (BadDescriptorException ex) {
            throw context.getRuntime().newErrnoEBADFError();
        } catch (InvalidValueException ex) {
            throw context.getRuntime().newErrnoEINVALError();
        }  catch (PipeException ex) {
            throw context.getRuntime().newErrnoEPIPEError();
        }

        // TODO: Obviously, we're not doing a non-blocking write here
        return write(context, obj);
    }
    
    /** io_write
     * 
     */
    @JRubyMethod(name = "write", required = 1)
    public IRubyObject write(ThreadContext context, IRubyObject obj) {
        Ruby runtime = context.getRuntime();
        
        runtime.secure(4);
        
        RubyString str = obj.asString();

        // TODO: Ruby reuses this logic for other "write" behavior by checking if it's an IO and calling write again
        
        if (str.getByteList().length() == 0) {
            return runtime.newFixnum(0);
        }

        try {
            OpenFile myOpenFile = getOpenFileChecked();
            
            myOpenFile.checkWritable(runtime);

            int written = fwrite(str.getByteList());

            if (written == -1) {
                // TODO: sys fail
            }

            // if not sync, we switch to write buffered mode
            if (!myOpenFile.isSync()) {
                myOpenFile.setWriteBuffered();
            }

            return runtime.newFixnum(written);
        } catch (IOException ex) {
            throw runtime.newIOErrorFromException(ex);
        } catch (BadDescriptorException ex) {
            throw runtime.newErrnoEBADFError();
        } catch (InvalidValueException ex) {
            throw runtime.newErrnoEINVALError();
        } catch (PipeException ex) {
            throw runtime.newErrnoEPIPEError();
        }
    }

    protected boolean waitWritable(ChannelDescriptor descriptor) throws IOException {
        Channel channel = descriptor.getChannel();
        if (channel == null || !(channel instanceof SelectableChannel)) {
            return false;
        }
       
        Selector selector = Selector.open();

        ((SelectableChannel) channel).configureBlocking(false);
        int real_ops = ((SelectableChannel) channel).validOps() & SelectionKey.OP_WRITE;
        SelectionKey key = ((SelectableChannel) channel).keyFor(selector);
       
        if (key == null) {
            ((SelectableChannel) channel).register(selector, real_ops, descriptor);
        } else {
            key.interestOps(key.interestOps()|real_ops);
        }

        while(selector.select() == 0);

        for (Iterator i = selector.selectedKeys().iterator(); i.hasNext(); ) {
            SelectionKey skey = (SelectionKey) i.next();
            if ((skey.interestOps() & skey.readyOps() & (SelectionKey.OP_WRITE)) != 0) {
                if(skey.attachment() == descriptor) {
                    return true;
                }
            }
        }
        return false;
    }

    protected boolean waitReadable(ChannelDescriptor descriptor) throws IOException {
        Channel channel = descriptor.getChannel();
        if (channel == null || !(channel instanceof SelectableChannel)) {
            return false;
        }
       
        Selector selector = Selector.open();

        ((SelectableChannel) channel).configureBlocking(false);
        int real_ops = ((SelectableChannel) channel).validOps() & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT);
        SelectionKey key = ((SelectableChannel) channel).keyFor(selector);
       
        if (key == null) {
            ((SelectableChannel) channel).register(selector, real_ops, descriptor);
        } else {
            key.interestOps(key.interestOps()|real_ops);
        }

        while(selector.select() == 0);

        for (Iterator i = selector.selectedKeys().iterator(); i.hasNext(); ) {
            SelectionKey skey = (SelectionKey) i.next();
            if ((skey.interestOps() & skey.readyOps() & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0) {
                if(skey.attachment() == descriptor) {
                    return true;
                }
            }
        }
        return false;
    }
    
    protected int fwrite(ByteList buffer) {
        int n, r, l, offset = 0;
        boolean eagain = false;
        Stream writeStream = openFile.getWriteStream();

        int len = buffer.length();
        
        if ((n = len) <= 0) return n;

        try {
            if (openFile.isSync()) {
                openFile.fflush(writeStream);

                // TODO: why is this guarded?
    //            if (!rb_thread_fd_writable(fileno(f))) {
    //                rb_io_check_closed(fptr);
    //            }
               
                while(offset<len) {
                    l = n;

                    // TODO: Something about pipe buffer length here

                    r = writeStream.getDescriptor().write(buffer,offset,l);

                    if(r == len) {
                        return len; //Everything written
                    }

                    if (0 <= r) {
                        offset += r;
                        n -= r;
                        eagain = true;
                    }

                    if(eagain && waitWritable(writeStream.getDescriptor())) {
                        openFile.checkClosed(getRuntime());
                        if(offset >= buffer.length()) {
                            return -1;
                        }
                        eagain = false;
                    } else {
                        return -1;
                    }
                }


                // TODO: all this stuff...some pipe logic, some async thread stuff
    //          retry:
    //            l = n;
    //            if (PIPE_BUF < l &&
    //                !rb_thread_critical &&
    //                !rb_thread_alone() &&
    //                wsplit_p(fptr)) {
    //                l = PIPE_BUF;
    //            }
    //            TRAP_BEG;
    //            r = write(fileno(f), RSTRING(str)->ptr+offset, l);
    //            TRAP_END;
    //            if (r == n) return len;
    //            if (0 <= r) {
    //                offset += r;
    //                n -= r;
    //                errno = EAGAIN;
    //            }
    //            if (rb_io_wait_writable(fileno(f))) {
    //                rb_io_check_closed(fptr);
    //                if (offset < RSTRING(str)->len)
    //                    goto retry;
    //            }
    //            return -1L;
            }

            // TODO: handle errors in buffered write by retrying until finished or file is closed
            return writeStream.fwrite(buffer);
    //        while (errno = 0, offset += (r = fwrite(RSTRING(str)->ptr+offset, 1, n, f)), (n -= r) > 0) {
    //            if (ferror(f)
    //            ) {
    //                if (rb_io_wait_writable(fileno(f))) {
    //                    rb_io_check_closed(fptr);
    //                    clearerr(f);
    //                    if (offset < RSTRING(str)->len)
    //                        continue;
    //                }
    //                return -1L;
    //            }
    //        }

//            return len - n;
        } catch (IOException ex) {
            throw getRuntime().newIOErrorFromException(ex);
        } catch (BadDescriptorException ex) {
            throw getRuntime().newErrnoEBADFError();
        }
    }

    /** rb_io_addstr
     * 
     */
    @JRubyMethod(name = "<<", required = 1)
    public IRubyObject op_append(ThreadContext context, IRubyObject anObject) {
        // Claims conversion is done via 'to_s' in docs.
        callMethod(context, "write", anObject);
        
        return this; 
    }

    @JRubyMethod(name = "fileno", alias = "to_i")
    public RubyFixnum fileno(ThreadContext context) {
        return context.getRuntime().newFixnum(getOpenFileChecked().getMainStream().getDescriptor().getFileno());
    }
    
    /** Returns the current line number.
     * 
     * @return the current line number.
     */
    @JRubyMethod(name = "lineno")
    public RubyFixnum lineno(ThreadContext context) {
        return context.getRuntime().newFixnum(getOpenFileChecked().getLineNumber());
    }

    /** Sets the current line number.
     * 
     * @param newLineNumber The new line number.
     */
    @JRubyMethod(name = "lineno=", required = 1)
    public RubyFixnum lineno_set(ThreadContext context, IRubyObject newLineNumber) {
        getOpenFileChecked().setLineNumber(RubyNumeric.fix2int(newLineNumber));

        return context.getRuntime().newFixnum(getOpenFileChecked().getLineNumber());
    }

    /** Returns the current sync mode.
     * 
     * @return the current sync mode.
     */
    @JRubyMethod(name = "sync")
    public RubyBoolean sync(ThreadContext context) {
        return context.getRuntime().newBoolean(getOpenFileChecked().getMainStream().isSync());
    }
    
    /**
     * <p>Return the process id (pid) of the process this IO object
     * spawned.  If no process exists (popen was not called), then
     * nil is returned.  This is not how it appears to be defined
     * but ruby 1.8 works this way.</p>
     * 
     * @return the pid or nil
     */
    @JRubyMethod(name = "pid")
    public IRubyObject pid(ThreadContext context) {
        OpenFile myOpenFile = getOpenFileChecked();
        
        if (myOpenFile.getProcess() == null) {
            return context.getRuntime().getNil();
        }
        
        // Of course this isn't particularly useful.
        int pid = myOpenFile.getProcess().hashCode();
        
        return context.getRuntime().newFixnum(pid); 
    }
    
    /**
     * @deprecated
     * @return
     */
    public boolean writeDataBuffered() {
        return openFile.getMainStream().writeDataBuffered();
    }
    
    @JRubyMethod(name = {"pos", "tell"})
    public RubyFixnum pos(ThreadContext context) {
        try {
            return context.getRuntime().newFixnum(getOpenFileChecked().getMainStream().fgetpos());
        } catch (InvalidValueException ex) {
            throw context.getRuntime().newErrnoEINVALError();
        } catch (BadDescriptorException bde) {
            throw context.getRuntime().newErrnoEBADFError();
        } catch (PipeException e) {
            throw context.getRuntime().newErrnoESPIPEError();
        } catch (IOException e) {
            throw context.getRuntime().newIOError(e.getMessage());
        }
    }
    
    @JRubyMethod(name = "pos=", required = 1)
    public RubyFixnum pos_set(ThreadContext context, IRubyObject newPosition) {
        long offset = RubyNumeric.num2long(newPosition);

        if (offset < 0) {
            throw context.getRuntime().newSystemCallError("Negative seek offset");
        }
        
        OpenFile myOpenFile = getOpenFileChecked();
        
        try {
            myOpenFile.getMainStream().lseek(offset, Stream.SEEK_SET);
        } catch (BadDescriptorException e) {
            throw context.getRuntime().newErrnoEBADFError();
        } catch (InvalidValueException e) {
            throw context.getRuntime().newErrnoEINVALError();
        } catch (PipeException e) {
            throw context.getRuntime().newErrnoESPIPEError();
        } catch (IOException e) {
            throw context.getRuntime().newIOError(e.getMessage());
        }
        
        myOpenFile.getMainStream().clearerr();
        
        return context.getRuntime().newFixnum(offset);
    }
    
    /** Print some objects to the stream.
     * 
     */
    @JRubyMethod(name = "print", rest = true, reads = FrameField.LASTLINE)
    public IRubyObject print(ThreadContext context, IRubyObject[] args) {
        if (args.length == 0) {
            args = new IRubyObject[] { context.getCurrentFrame().getLastLine() };
        }

        Ruby runtime = context.getRuntime();
        IRubyObject fs = runtime.getGlobalVariables().get("$,");
        IRubyObject rs = runtime.getGlobalVariables().get("$\\");
        
        for (int i = 0; i < args.length; i++) {
            if (i > 0 && !fs.isNil()) {
                callMethod(context, "write", fs);
            }
            if (args[i].isNil()) {
                callMethod(context, "write", runtime.newString("nil"));
            } else {
                callMethod(context, "write", args[i]);
            }
        }
        if (!rs.isNil()) {
            callMethod(context, "write", rs);
        }

        return runtime.getNil();
    }

    @JRubyMethod(name = "printf", required = 1, rest = true)
    public IRubyObject printf(ThreadContext context, IRubyObject[] args) {
        callMethod(context, "write", RubyKernel.sprintf(context, this, args));
        return context.getRuntime().getNil();
    }

    @JRubyMethod(name = "putc", required = 1, backtrace = true)
    public IRubyObject putc(ThreadContext context, IRubyObject object) {
        int c = RubyNumeric.num2chr(object);

        try {
            getOpenFileChecked().getMainStream().fputc(c);
        } catch (BadDescriptorException e) {
            return RubyFixnum.zero(context.getRuntime());
        } catch (IOException e) {
            return RubyFixnum.zero(context.getRuntime());
        }

        return object;
    }

    public RubyFixnum seek(ThreadContext context, IRubyObject[] args) {
        long offset = RubyNumeric.num2long(args[0]);
        int whence = Stream.SEEK_SET;
        
        if (args.length > 1) {
            whence = RubyNumeric.fix2int(args[1].convertToInteger());
        }
        
        return doSeek(context, offset, whence);
    }

    @JRubyMethod(name = "seek")
    public RubyFixnum seek(ThreadContext context, IRubyObject arg0) {
        long offset = RubyNumeric.num2long(arg0);
        int whence = Stream.SEEK_SET;
        
        return doSeek(context, offset, whence);
    }

    @JRubyMethod(name = "seek")
    public RubyFixnum seek(ThreadContext context, IRubyObject arg0, IRubyObject arg1) {
        long offset = RubyNumeric.num2long(arg0);
        int whence = RubyNumeric.fix2int(arg1.convertToInteger());
        
        return doSeek(context, offset, whence);
    }
    
    private RubyFixnum doSeek(ThreadContext context, long offset, int whence) {
        OpenFile myOpenFile = getOpenFileChecked();
        
        try {
            myOpenFile.seek(offset, whence);
        } catch (BadDescriptorException ex) {
            throw context.getRuntime().newErrnoEBADFError();
        } catch (InvalidValueException e) {
            throw context.getRuntime().newErrnoEINVALError();
        } catch (PipeException e) {
            throw context.getRuntime().newErrnoESPIPEError();
        } catch (IOException e) {
            throw context.getRuntime().newIOError(e.getMessage());
        }
        
        myOpenFile.getMainStream().clearerr();
        
        return RubyFixnum.zero(context.getRuntime());
    }
    
    // This was a getOpt with one mandatory arg, but it did not work
    // so I am parsing it for now.
    @JRubyMethod(name = "sysseek", required = 1, optional = 1)
    public RubyFixnum sysseek(ThreadContext context, IRubyObject[] args) {
        long offset = RubyNumeric.num2long(args[0]);
        long pos;
        int whence = Stream.SEEK_SET;
        
        if (args.length > 1) {
            whence = RubyNumeric.fix2int(args[1].convertToInteger());
        }
        
        OpenFile myOpenFile = getOpenFileChecked();
        
        try {
            
            if (myOpenFile.isReadable() && myOpenFile.isReadBuffered()) {
                throw context.getRuntime().newIOError("sysseek for buffered IO");
            }
            if (myOpenFile.isWritable() && myOpenFile.isWriteBuffered()) {
                context.getRuntime().getWarnings().warn(ID.SYSSEEK_BUFFERED_IO, "sysseek for buffered IO");
            }
            
            pos = myOpenFile.getMainStream().getDescriptor().lseek(offset, whence);
        } catch (BadDescriptorException ex) {
            throw context.getRuntime().newErrnoEBADFError();
        } catch (InvalidValueException e) {
            throw context.getRuntime().newErrnoEINVALError();
        } catch (PipeException e) {
            throw context.getRuntime().newErrnoESPIPEError();
        } catch (IOException e) {
            throw context.getRuntime().newIOError(e.getMessage());
        }
        
        myOpenFile.getMainStream().clearerr();
        
        return context.getRuntime().newFixnum(pos);
    }

    @JRubyMethod(name = "rewind")
    public RubyFixnum rewind(ThreadContext context) {
        OpenFile myOpenfile = getOpenFileChecked();
        
        try {
            myOpenfile.getMainStream().lseek(0L, Stream.SEEK_SET);
            myOpenfile.getMainStream().clearerr();
            
            // TODO: This is some goofy global file value from MRI..what to do?
//            if (io == current_file) {
//                gets_lineno -= fptr->lineno;
//            }
        } catch (BadDescriptorException e) {
            throw context.getRuntime().newErrnoEBADFError();
        } catch (InvalidValueException e) {
            throw context.getRuntime().newErrnoEINVALError();
        } catch (PipeException e) {
            throw context.getRuntime().newErrnoESPIPEError();
        } catch (IOException e) {
            throw context.getRuntime().newIOError(e.getMessage());
        }

        // Must be back on first line on rewind.
        myOpenfile.setLineNumber(0);
        
        return RubyFixnum.zero(context.getRuntime());
    }
    
    @JRubyMethod(name = "fsync")
    public RubyFixnum fsync(ThreadContext context) {
        Ruby runtime = context.getRuntime();
        
        try {
            OpenFile myOpenFile = getOpenFileChecked();
            
            myOpenFile.checkWritable(runtime);
        
            myOpenFile.getWriteStream().sync();
        } catch (InvalidValueException ex) {
            throw runtime.newErrnoEINVALError();
        } catch (PipeException ex) {
            throw runtime.newErrnoEPIPEError();
        } catch (IOException e) {
            throw runtime.newIOError(e.getMessage());
        } catch (BadDescriptorException e) {
            throw runtime.newErrnoEBADFError();
        }

        return RubyFixnum.zero(runtime);
    }

    /** Sets the current sync mode.
     * 
     * @param newSync The new sync mode.
     */
    @JRubyMethod(name = "sync=", required = 1)
    public IRubyObject sync_set(IRubyObject newSync) {
        getOpenFileChecked().setSync(newSync.isTrue());
        getOpenFileChecked().getMainStream().setSync(newSync.isTrue());

        return this;
    }

    @JRubyMethod(name = {"eof?", "eof"})
    public RubyBoolean eof_p(ThreadContext context) {
        Ruby runtime = context.getRuntime();
        
        try {
            OpenFile myOpenFile = getOpenFileChecked();

            myOpenFile.checkReadable(runtime);
            myOpenFile.setReadBuffered();

            if (myOpenFile.getMainStream().feof()) {
                return runtime.getTrue();
            }
            
            if (myOpenFile.getMainStream().readDataBuffered()) {
                return runtime.getFalse();
            }
            
            readCheck(myOpenFile.getMainStream());
            
            myOpenFile.getMainStream().clearerr();
            
            int c = myOpenFile.getMainStream().fgetc();
            
            if (c != -1) {
                myOpenFile.getMainStream().ungetc(c);
                return runtime.getFalse();
            }
            
            myOpenFile.checkClosed(runtime);
            
            myOpenFile.getMainStream().clearerr();
            
            return runtime.getTrue();
        } catch (PipeException ex) {
            throw runtime.newErrnoEPIPEError();
        } catch (InvalidValueException ex) {
            throw runtime.newErrnoEINVALError();
        } catch (BadDescriptorException e) {
            throw runtime.newErrnoEBADFError();
        } catch (IOException e) {
            throw runtime.newIOError(e.getMessage());
        }
    }

    @JRubyMethod(name = {"tty?", "isatty"})
    public RubyBoolean tty_p(ThreadContext context) {
        return context.getRuntime().newBoolean(context.getRuntime().getPosix().isatty(getOpenFileChecked().getMainStream().getDescriptor().getFileDescriptor()));
    }
    
    @JRubyMethod(name = "initialize_copy", required = 1)
    @Override
    public IRubyObject initialize_copy(IRubyObject original){
        Ruby runtime = getRuntime();
        
        if (this == original) return this;

        RubyIO originalIO = (RubyIO) TypeConverter.convertToTypeWithCheck(original, runtime.getIO(), MethodIndex.TO_IO, "to_io");
        
        OpenFile originalFile = originalIO.getOpenFileChecked();
        OpenFile newFile = openFile;
        
        try {
            // TODO: I didn't see where MRI has this check, but it seems to be the right place
            originalFile.checkClosed(runtime);
            
            if (originalFile.getPipeStream() != null) {
                originalFile.getPipeStream().fflush();
                originalFile.getMainStream().lseek(0, Stream.SEEK_CUR);
            } else if (originalFile.isWritable()) {
                originalFile.getMainStream().fflush();
            } else {
                originalFile.getMainStream().lseek(0, Stream.SEEK_CUR);
            }

            newFile.setMode(originalFile.getMode());
            newFile.setProcess(originalFile.getProcess());
            newFile.setLineNumber(originalFile.getLineNumber());
            newFile.setPath(originalFile.getPath());
            newFile.setFinalizer(originalFile.getFinalizer());
            
            ModeFlags modes;
            if (newFile.isReadable()) {
                if (newFile.isWritable()) {
                    if (newFile.getPipeStream() != null) {
                        modes = new ModeFlags(ModeFlags.RDONLY);
                    } else {
                        modes = new ModeFlags(ModeFlags.RDWR);
                    }
                } else {
                    modes = new ModeFlags(ModeFlags.RDONLY);
                }
            } else {
                if (newFile.isWritable()) {
                    modes = new ModeFlags(ModeFlags.WRONLY);
                } else {
                    modes = originalFile.getMainStream().getModes();
                }
            }
            
            ChannelDescriptor descriptor = originalFile.getMainStream().getDescriptor().dup();

            newFile.setMainStream(ChannelStream.fdopen(runtime, descriptor, modes));
            
            // TODO: the rest of this...seeking to same position is unnecessary since we share a channel
            // but some of this may be needed?
            
//    fseeko(fptr->f, ftello(orig->f), SEEK_SET);
//    if (orig->f2) {
//	if (fileno(orig->f) != fileno(orig->f2)) {
//	    fd = ruby_dup(fileno(orig->f2));
//	}
//	fptr->f2 = rb_fdopen(fd, "w");
//	fseeko(fptr->f2, ftello(orig->f2), SEEK_SET);
//    }
//    if (fptr->mode & FMODE_BINMODE) {
//	rb_io_binmode(dest);
//    }
            
            // Register the new descriptor
            registerDescriptor(newFile.getMainStream().getDescriptor());
        } catch (IOException ex) {
            throw runtime.newIOError("could not init copy: " + ex);
        } catch (BadDescriptorException ex) {
            throw runtime.newIOError("could not init copy: " + ex);
        } catch (PipeException ex) {
            throw runtime.newIOError("could not init copy: " + ex);
        } catch (InvalidValueException ex) {
            throw runtime.newIOError("could not init copy: " + ex);
        }
        
        return this;
    }
    
    /** Closes the IO.
     * 
     * @return The IO.
     */
    @JRubyMethod(name = "closed?")
    public RubyBoolean closed_p(ThreadContext context) {
        return context.getRuntime().newBoolean(openFile.getMainStream() == null && openFile.getPipeStream() == null);
    }

    /** 
     * <p>Closes all open resources for the IO.  It also removes
     * it from our magical all open file descriptor pool.</p>
     * 
     * @return The IO.
     */
    @JRubyMethod(name = "close")
    public IRubyObject close() {
        Ruby runtime = getRuntime();
        
        if (runtime.getSafeLevel() >= 4 && isTaint()) {
            throw runtime.newSecurityError("Insecure: can't close");
        }
        
        openFile.checkClosed(runtime);
        return close2(runtime);
    }
        
    protected IRubyObject close2(Ruby runtime) {
        if (openFile == null) return runtime.getNil();
        
        // These would be used when we notify threads...if we notify threads
        interruptBlockingThreads();
        
        ChannelDescriptor main, pipe;
        if (openFile.getPipeStream() != null) {
            pipe = openFile.getPipeStream().getDescriptor();
        } else {
            if (openFile.getMainStream() == null) {
                return runtime.getNil();
            }
            pipe = null;
        }
        
        main = openFile.getMainStream().getDescriptor();
        
        // cleanup, raising errors if any
        openFile.cleanup(runtime, true);
        
        // TODO: notify threads waiting on descriptors/IO? probably not...
        
        if (openFile.getProcess() != null) {
            try {
                IRubyObject processResult = RubyProcess.RubyStatus.newProcessStatus(runtime, openFile.getProcess().waitFor());
                runtime.getGlobalVariables().set("$?", processResult);
            } catch (InterruptedException ie) {
                // TODO: do something here?
            }
        }
        
        return runtime.getNil();
    }

    @JRubyMethod(name = "close_write")
    public IRubyObject close_write(ThreadContext context) throws BadDescriptorException {
        try {
            if (context.getRuntime().getSafeLevel() >= 4 && isTaint()) {
                throw context.getRuntime().newSecurityError("Insecure: can't close");
            }
            
            OpenFile myOpenFile = getOpenFileChecked();
            
            if (myOpenFile.getPipeStream() == null && myOpenFile.isReadable()) {
                throw context.getRuntime().newIOError("closing non-duplex IO for writing");
            }
            
            if (myOpenFile.getPipeStream() == null) {
                close();
            } else{
                myOpenFile.getPipeStream().fclose();
                myOpenFile.setPipeStream(null);
                myOpenFile.setMode(myOpenFile.getMode() & ~OpenFile.WRITABLE);
                // TODO
                // n is result of fclose; but perhaps having a SysError below is enough?
                // if (n != 0) rb_sys_fail(fptr->path);
            }
        } catch (IOException ioe) {
            // hmmmm
        }
        return this;
    }

    @JRubyMethod(name = "close_read")
    public IRubyObject close_read(ThreadContext context) throws BadDescriptorException {
        Ruby runtime = context.getRuntime();
        
        try {
            if (runtime.getSafeLevel() >= 4 && isTaint()) {
                throw runtime.newSecurityError("Insecure: can't close");
            }
            
            OpenFile myOpenFile = getOpenFileChecked();
            
            if (myOpenFile.getPipeStream() == null && myOpenFile.isWritable()) {
                throw runtime.newIOError("closing non-duplex IO for reading");
            }
            
            if (myOpenFile.getPipeStream() == null) {
                close();
            } else{
                myOpenFile.getMainStream().fclose();
                myOpenFile.setMode(myOpenFile.getMode() & ~OpenFile.READABLE);
                myOpenFile.setMainStream(myOpenFile.getPipeStream());
                myOpenFile.setPipeStream(null);
                // TODO
                // n is result of fclose; but perhaps having a SysError below is enough?
                // if (n != 0) rb_sys_fail(fptr->path);
            }
        } catch (IOException ioe) {
            // I believe Ruby bails out with a "bug" if closing fails
            throw runtime.newIOErrorFromException(ioe);
        }
        return this;
    }

    /** Flushes the IO output stream.
     * 
     * @return The IO.
     */
    @JRubyMethod(name = "flush")
    public RubyIO flush() {
        try { 
            getOpenFileChecked().getWriteStream().fflush();
        } catch (BadDescriptorException e) {
            throw getRuntime().newErrnoEBADFError();
        } catch (IOException e) {
            throw getRuntime().newIOError(e.getMessage());
        }

        return this;
    }

    /** Read a line.
     * 
     */
    @JRubyMethod(name = "gets", optional = 1, writes = FrameField.LASTLINE)
    public IRubyObject gets(ThreadContext context, IRubyObject[] args) {
        Ruby runtime = context.getRuntime();
        ByteList separator = getSeparatorForGets(runtime, args);
        
        IRubyObject result = getline(runtime, separator);

        if (!result.isNil()) context.getCurrentFrame().setLastLine(result);

        return result;
    }

    public boolean getBlocking() {
        return ((ChannelStream) openFile.getMainStream()).isBlocking();
    }

    @JRubyMethod(name = "fcntl", required = 2)
    public IRubyObject fcntl(ThreadContext context, IRubyObject cmd, IRubyObject arg) {
        // TODO: This version differs from ioctl by checking whether fcntl exists
        // and raising notimplemented if it doesn't; perhaps no difference for us?
        return ctl(context.getRuntime(), cmd, arg);
    }

    @JRubyMethod(name = "ioctl", required = 1, optional = 1)
    public IRubyObject ioctl(ThreadContext context, IRubyObject[] args) {
        IRubyObject cmd = args[0];
        IRubyObject arg;
        
        if (args.length == 2) {
            arg = args[1];
        } else {
            arg = context.getRuntime().getNil();
        }
        
        return ctl(context.getRuntime(), cmd, arg);
    }

    public IRubyObject ctl(Ruby runtime, IRubyObject cmd, IRubyObject arg) {
        long realCmd = cmd.convertToInteger().getLongValue();
        long nArg = 0;
        
        // FIXME: Arg may also be true, false, and nil and still be valid.  Strangely enough, 
        // protocol conversion is not happening in Ruby on this arg?
        if (arg.isNil() || arg == runtime.getFalse()) {
            nArg = 0;
        } else if (arg instanceof RubyFixnum) {
            nArg = RubyFixnum.fix2long(arg);
        } else if (arg == runtime.getTrue()) {
            nArg = 1;
        } else {
            throw runtime.newNotImplementedError("JRuby does not support string for second fcntl/ioctl argument yet");
        }
        
        OpenFile myOpenFile = getOpenFileChecked();

        // Fixme: Only F_SETFL is current supported
        if (realCmd == 1L) {  // cmd is F_SETFL
            boolean block = true;
            
            if ((nArg & ModeFlags.NONBLOCK) == ModeFlags.NONBLOCK) {
                block = false;
            }

            try {
                myOpenFile.getMainStream().setBlocking(block);
            } catch (IOException e) {
                throw runtime.newIOError(e.getMessage());
            }
        } else {
            throw runtime.newNotImplementedError("JRuby only supports F_SETFL for fcntl/ioctl currently");
        }
        
        return runtime.newFixnum(0);
    }
    
    private static final ByteList NIL_BYTELIST = ByteList.create("nil");
    private static final ByteList RECURSIVE_BYTELIST = ByteList.create("[...]");

    @JRubyMethod(name = "puts", rest = true)
    public IRubyObject puts(ThreadContext context, IRubyObject[] args) {
        Ruby runtime = context.getRuntime();
        assert runtime.getGlobalVariables().getDefaultSeparator() instanceof RubyString;
        RubyString separator = (RubyString) runtime.getGlobalVariables().getDefaultSeparator();
        
        if (args.length == 0) {
            write(context, separator.getByteList());
            return runtime.getNil();
        }

        for (int i = 0; i < args.length; i++) {
            ByteList line;
            
            if (args[i].isNil()) {
                line = NIL_BYTELIST;
            } else if (runtime.isInspecting(args[i])) {
                line = RECURSIVE_BYTELIST;
            } else if (args[i] instanceof RubyArray) {
                inspectPuts(context, (RubyArray) args[i]);
                continue;
            } else {
                line = args[i].asString().getByteList();
            }
            
            write(context, line);
            
            if (line.length() == 0 || !line.endsWith(separator.getByteList())) {
                write(context, separator.getByteList());
            }
        }
        return runtime.getNil();
    }

    protected void write(ThreadContext context, ByteList byteList) {
        callMethod(context, "write", RubyString.newStringShared(context.getRuntime(), byteList));
    }

    private IRubyObject inspectPuts(ThreadContext context, RubyArray array) {
        try {
            context.getRuntime().registerInspecting(array);
            return puts(context, array.toJavaArray());
        } finally {
            context.getRuntime().unregisterInspecting(array);
        }
    }

    /** Read a line.
     * 
     */
    @JRubyMethod(name = "readline", optional = 1, writes = FrameField.LASTLINE)
    public IRubyObject readline(ThreadContext context, IRubyObject[] args) {
        IRubyObject line = gets(context, args);

        if (line.isNil()) throw context.getRuntime().newEOFError();
        
        return line;
    }

    /** Read a byte. On EOF returns nil.
     * 
     */
    @JRubyMethod(name = "getc")
    public IRubyObject getc() {
        try {
            OpenFile myOpenFile = getOpenFileChecked();

            myOpenFile.checkReadable(getRuntime());
            myOpenFile.setReadBuffered();

            Stream stream = myOpenFile.getMainStream();
            
            readCheck(stream);
            stream.clearerr();
        
            int c = myOpenFile.getMainStream().fgetc();
            
            if (c == -1) {
                // TODO: check for ferror, clear it, and try once more up above readCheck
//                if (ferror(f)) {
//                    clearerr(f);
//                    if (!rb_io_wait_readable(fileno(f)))
//                        rb_sys_fail(fptr->path);
//                    goto retry;
//                }
                return getRuntime().getNil();
            }
        
            return getRuntime().newFixnum(c);
        } catch (PipeException ex) {
            throw getRuntime().newErrnoEPIPEError();
        } catch (InvalidValueException ex) {
            throw getRuntime().newErrnoEINVALError();
        } catch (BadDescriptorException e) {
            throw getRuntime().newErrnoEBADFError();
        } catch (EOFException e) {
            throw getRuntime().newEOFError();
        } catch (IOException e) {
            throw getRuntime().newIOError(e.getMessage());
        }
    }
    
    private void readCheck(Stream stream) {
        if (!stream.readDataBuffered()) {
            openFile.checkClosed(getRuntime());
        }
    }
    
    /** 
     * <p>Pushes char represented by int back onto IOS.</p>
     * 
     * @param number to push back
     */
    @JRubyMethod(name = "ungetc", required = 1)
    public IRubyObject ungetc(IRubyObject number) {
        int ch = RubyNumeric.fix2int(number);
        
        OpenFile myOpenFile = getOpenFileChecked();
        
        if (!myOpenFile.isReadBuffered()) {
            throw getRuntime().newIOError("unread stream");
        }
        
        try {
            myOpenFile.checkReadable(getRuntime());
            myOpenFile.setReadBuffered();

            if (myOpenFile.getMainStream().ungetc(ch) == -1 && ch != -1) {
                throw getRuntime().newIOError("ungetc failed");
            }
        } catch (PipeException ex) {
            throw getRuntime().newErrnoEPIPEError();
        } catch (InvalidValueException ex) {
            throw getRuntime().newErrnoEINVALError();
        } catch (BadDescriptorException e) {
            throw getRuntime().newErrnoEBADFError();
        } catch (EOFException e) {
            throw getRuntime().newEOFError();
        } catch (IOException e) {
            throw getRuntime().newIOError(e.getMessage());
        }

        return getRuntime().getNil();
    }
    
    @JRubyMethod(name = "read_nonblock", required = 1, optional = 1)
    public IRubyObject read_nonblock(ThreadContext context, IRubyObject[] args) {
        Ruby runtime = context.getRuntime();

        openFile.checkClosed(runtime);

        if(!(openFile.getMainStream() instanceof ChannelStream)) {
            // cryptic for the uninitiated...
            throw runtime.newNotImplementedError("read_nonblock only works with Nio based handlers");
        }
        try {
            int maxLength = RubyNumeric.fix2int(args[0]);
            if (maxLength < 0) {
                throw runtime.newArgumentError("negative length " + maxLength + " given");
            }
            ByteList buf = ((ChannelStream)openFile.getMainStream()).readnonblock(RubyNumeric.fix2int(args[0]));
            IRubyObject strbuf = RubyString.newString(runtime, buf == null ? new ByteList(ByteList.NULL_ARRAY) : buf);
            if(args.length > 1) {
                args[1].callMethod(context, MethodIndex.OP_LSHIFT, "<<", strbuf);
                return args[1];
            }

            return strbuf;
        } catch (BadDescriptorException e) {
            throw runtime.newErrnoEBADFError();
        } catch (EOFException e) {
            return runtime.getNil();
        } catch (IOException e) {
            throw runtime.newIOError(e.getMessage());
        }
    }
    
    @JRubyMethod(name = "readpartial", required = 1, optional = 1)
    public IRubyObject readpartial(ThreadContext context, IRubyObject[] args) {
        Ruby runtime = context.getRuntime();

        openFile.checkClosed(runtime);

        if(!(openFile.getMainStream() instanceof ChannelStream)) {
            // cryptic for the uninitiated...
            throw runtime.newNotImplementedError("readpartial only works with Nio based handlers");
        }
        try {
            int maxLength = RubyNumeric.fix2int(args[0]);
            if (maxLength < 0) {
                throw runtime.newArgumentError("negative length " + maxLength + " given");
            }
            ByteList buf = ((ChannelStream)openFile.getMainStream()).readpartial(RubyNumeric.fix2int(args[0]));
            IRubyObject strbuf = RubyString.newString(runtime, buf == null ? new ByteList(ByteList.NULL_ARRAY) : buf);
            if(args.length > 1) {
                args[1].callMethod(context, MethodIndex.OP_LSHIFT, "<<", strbuf);
                return args[1];
            }

            return strbuf;
        } catch (BadDescriptorException e) {
            throw runtime.newErrnoEBADFError();
        } catch (EOFException e) {
            return runtime.getNil();
        } catch (IOException e) {
            throw runtime.newIOError(e.getMessage());
        }
    }

    @JRubyMethod(name = "sysread", required = 1, optional = 1)
    public IRubyObject sysread(ThreadContext context, IRubyObject[] args) {
        int len = (int)RubyNumeric.num2long(args[0]);
        if (len < 0) throw getRuntime().newArgumentError("Negative size");

        try {
            RubyString str;
            ByteList buffer;
            if (args.length == 1 || args[1].isNil()) {
                if (len == 0) {
                    return RubyString.newStringShared(getRuntime(), ByteList.EMPTY_BYTELIST);
                }
                
                buffer = new ByteList(len);
                str = RubyString.newString(getRuntime(), buffer);
            } else {
                str = args[1].convertToString();
                str.modify(len);
                
                if (len == 0) {
                    return str;
                }
                
                buffer = str.getByteList();
            }
            
            OpenFile myOpenFile = getOpenFileChecked();
            
            myOpenFile.checkReadable(getRuntime());
            
            if (myOpenFile.getMainStream().readDataBuffered()) {
                throw getRuntime().newIOError("sysread for buffered IO");
            }
            
            // TODO: Ruby locks the string here
            
            context.getThread().beforeBlockingCall();
            myOpenFile.checkClosed(getRuntime());
            
            // TODO: Ruby re-checks that the buffer string hasn't been modified
            
            int bytesRead = myOpenFile.getMainStream().getDescriptor().read(len, str.getByteList());
            
            // TODO: Ruby unlocks the string here
            
            // TODO: Ruby truncates string to specific size here, but our bytelist should handle this already?
            
            if (bytesRead == -1 || (bytesRead == 0 && len > 0)) {
                throw getRuntime().newEOFError();
            }
            
            str.setTaint(true);
            
            return str;
        } catch (BadDescriptorException e) {
            throw getRuntime().newErrnoEBADFError();
        } catch (InvalidValueException e) {
            throw getRuntime().newErrnoEINVALError();
        } catch (PipeException e) {
            throw getRuntime().newErrnoEPIPEError();
        } catch (EOFException e) {
            throw getRuntime().newEOFError();
    	} catch (IOException e) {
            // All errors to sysread should be SystemCallErrors, but on a closed stream
            // Ruby returns an IOError.  Java throws same exception for all errors so
            // we resort to this hack...
            if ("File not open".equals(e.getMessage())) {
                    throw getRuntime().newIOError(e.getMessage());
            }
    	    throw getRuntime().newSystemCallError(e.getMessage());
    	} finally {
            context.getThread().afterBlockingCall();
        }
    }
    
    public IRubyObject read(IRubyObject[] args) {
        ThreadContext context = getRuntime().getCurrentContext();
        
        switch (args.length) {
        case 0: return read(context);
        case 1: return read(context, args[0]);
        case 2: return read(context, args[0], args[1]);
        default: throw getRuntime().newArgumentError(args.length, 2);
        }
    }
    
    @JRubyMethod(name = "read")
    public IRubyObject read(ThreadContext context) {
        Ruby runtime = context.getRuntime();
        OpenFile myOpenFile = getOpenFileChecked();
        
        try {
            myOpenFile.checkReadable(runtime);
            myOpenFile.setReadBuffered();

            return readAll(getRuntime().getNil());
        } catch (PipeException ex) {
            throw getRuntime().newErrnoEPIPEError();
        } catch (InvalidValueException ex) {
            throw getRuntime().newErrnoEINVALError();
        } catch (EOFException ex) {
            throw getRuntime().newEOFError();
        } catch (IOException ex) {
            throw getRuntime().newIOErrorFromException(ex);
        } catch (BadDescriptorException ex) {
            throw getRuntime().newErrnoEBADFError();
        }
    }
    
    @JRubyMethod(name = "read")
    public IRubyObject read(ThreadContext context, IRubyObject arg0) {
        if (arg0.isNil()) {
            return read(context);
        }
        
        OpenFile myOpenFile = getOpenFileChecked();
        
        int length = RubyNumeric.num2int(arg0);
        
        if (length < 0) {
            throw getRuntime().newArgumentError("negative length " + length + " given");
        }
        
        RubyString str = null;

        return readNotAll(context, myOpenFile, length, str);
    }
    
    @JRubyMethod(name = "read")
    public IRubyObject read(ThreadContext context, IRubyObject arg0, IRubyObject arg1) {
        OpenFile myOpenFile = getOpenFileChecked();
        
        if (arg0.isNil()) {
            try {
                myOpenFile.checkReadable(getRuntime());
                myOpenFile.setReadBuffered();

                return readAll(arg1);
            } catch (PipeException ex) {
                throw getRuntime().newErrnoEPIPEError();
            } catch (InvalidValueException ex) {
                throw getRuntime().newErrnoEINVALError();
            } catch (EOFException ex) {
                throw getRuntime().newEOFError();
            } catch (IOException ex) {
                throw getRuntime().newIOErrorFromException(ex);
            } catch (BadDescriptorException ex) {
                throw getRuntime().newErrnoEBADFError();
            }
        }
        
        int length = RubyNumeric.num2int(arg0);
        
        if (length < 0) {
            throw getRuntime().newArgumentError("negative length " + length + " given");
        }
        
        RubyString str = null;
//        ByteList buffer = null;
        if (arg1.isNil()) {
//            buffer = new ByteList(length);
//            str = RubyString.newString(getRuntime(), buffer);
        } else {
            str = arg1.convertToString();
            str.modify(length);

            if (length == 0) {
                return str;
            }

//            buffer = str.getByteList();
        }
        
        return readNotAll(context, myOpenFile, length, str);
    }
    
    private IRubyObject readNotAll(ThreadContext context, OpenFile myOpenFile, int length, RubyString str) {
        Ruby runtime = context.getRuntime();
        
        try {
            myOpenFile.checkReadable(runtime);
            myOpenFile.setReadBuffered();

            if (myOpenFile.getMainStream().feof()) {
                return runtime.getNil();
            }

            // TODO: Ruby locks the string here

            // READ_CHECK from MRI io.c
            readCheck(myOpenFile.getMainStream());

            // TODO: check buffer length again?
    //        if (RSTRING(str)->len != len) {
    //            rb_raise(rb_eRuntimeError, "buffer string modified");
    //        }

            // TODO: read into buffer using all the fread logic
    //        int read = openFile.getMainStream().fread(buffer);
            ByteList newBuffer = myOpenFile.getMainStream().fread(length);

            // TODO: Ruby unlocks the string here

            // TODO: change this to check number read into buffer once that's working
    //        if (read == 0) {
            
            if (newBuffer == null || newBuffer.length() == 0) {
                if (myOpenFile.getMainStream() == null) {
                    return runtime.getNil();
                }

                if (myOpenFile.getMainStream().feof()) {
                    // truncate buffer string to zero, if provided
                    if (str != null) {
                        str.setValue(ByteList.EMPTY_BYTELIST.dup());
                    }
                
                    return runtime.getNil();
                }

                // Removed while working on JRUBY-2386, since fixes for that
                // modified EOF logic such that this check is not really valid.
                // We expect that an EOFException will be thrown now in EOF
                // cases.
//                if (length > 0) {
//                    // I think this is only partly correct; sys fail based on errno in Ruby
//                    throw getRuntime().newEOFError();
//                }
            }


            // TODO: Ruby truncates string to specific size here, but our bytelist should handle this already?

            // FIXME: I don't like the null checks here
            if (str == null) {
                if (newBuffer == null) {
                    str = RubyString.newEmptyString(runtime);
                } else {
                    str = RubyString.newString(runtime, newBuffer);
                }
            } else {
                if (newBuffer == null) {
                    str.empty();
                } else {
                    str.setValue(newBuffer);
                }
            }
            str.setTaint(true);

            return str;
        } catch (EOFException ex) {
            throw runtime.newEOFError();
        } catch (PipeException ex) {
            throw runtime.newErrnoEPIPEError();
        } catch (InvalidValueException ex) {
            throw runtime.newErrnoEINVALError();
        } catch (IOException ex) {
            throw runtime.newIOErrorFromException(ex);
        } catch (BadDescriptorException ex) {
            throw runtime.newErrnoEBADFError();
        }
    }
    
    protected IRubyObject readAll(IRubyObject buffer) throws BadDescriptorException, EOFException, IOException {
        Ruby runtime = getRuntime();
        // TODO: handle writing into original buffer better
        
        RubyString str = null;
        if (buffer instanceof RubyString) {
            str = (RubyString)buffer;
        }
        
        // TODO: ruby locks the string here
        
        // READ_CHECK from MRI io.c
        if (openFile.getMainStream().readDataBuffered()) {
            openFile.checkClosed(runtime);
        }
        
        ByteList newBuffer = openFile.getMainStream().readall();

        // TODO same zero-length checks as file above

        if (str == null) {
            if (newBuffer == null) {
                str = RubyString.newEmptyString(runtime);
            } else {
                str = RubyString.newString(runtime, newBuffer);
            }
        } else {
            if (newBuffer == null) {
                str.empty();
            } else {
                str.setValue(newBuffer);
            }
        }

        str.taint(runtime.getCurrentContext());

        return str;
//        long bytes = 0;
//        long n;
//
//        if (siz == 0) siz = BUFSIZ;
//        if (NIL_P(str)) {
//            str = rb_str_new(0, siz);
//        }
//        else {
//            rb_str_resize(str, siz);
//        }
//        for (;;) {
//            rb_str_locktmp(str);
//            READ_CHECK(fptr->f);
//            n = io_fread(RSTRING(str)->ptr+bytes, siz-bytes, fptr);
//            rb_str_unlocktmp(str);
//            if (n == 0 && bytes == 0) {
//                if (!fptr->f) break;
//                if (feof(fptr->f)) break;
//                if (!ferror(fptr->f)) break;
//                rb_sys_fail(fptr->path);
//            }
//            bytes += n;
//            if (bytes < siz) break;
//            siz += BUFSIZ;
//            rb_str_resize(str, siz);
//        }
//        if (bytes != siz) rb_str_resize(str, bytes);
//        OBJ_TAINT(str);
//
//        return str;
    }
    
    // TODO: There's a lot of complexity here due to error handling and
    // nonblocking IO; much of this goes away, but for now I'm just
    // having read call ChannelStream.fread directly.
//    protected int fread(int len, ByteList buffer) {
//        long n = len;
//        int c;
//        int saved_errno;
//
//        while (n > 0) {
//            c = read_buffered_data(ptr, n, fptr->f);
//            if (c < 0) goto eof;
//            if (c > 0) {
//                ptr += c;
//                if ((n -= c) <= 0) break;
//            }
//            rb_thread_wait_fd(fileno(fptr->f));
//            rb_io_check_closed(fptr);
//            clearerr(fptr->f);
//            TRAP_BEG;
//            c = getc(fptr->f);
//            TRAP_END;
//            if (c == EOF) {
//              eof:
//                if (ferror(fptr->f)) {
//                    switch (errno) {
//                      case EINTR:
//    #if defined(ERESTART)
//                      case ERESTART:
//    #endif
//                        clearerr(fptr->f);
//                        continue;
//                      case EAGAIN:
//    #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
//                      case EWOULDBLOCK:
//    #endif
//                        if (len > n) {
//                            clearerr(fptr->f);
//                        }
//                        saved_errno = errno;
//                        rb_warning("nonblocking IO#read is obsolete; use IO#readpartial or IO#sysread");
//                        errno = saved_errno;
//                    }
//                    if (len == n) return 0;
//                }
//                break;
//            }
//            *ptr++ = c;
//            n--;
//        }
//        return len - n;
//        
//    }

    /** Read a byte. On EOF throw EOFError.
     * 
     */
    @JRubyMethod(name = "readchar")
    public IRubyObject readchar() {
        IRubyObject c = getc();
        
        if (c.isNil()) throw getRuntime().newEOFError();
        
        return c;
    }
    
    @JRubyMethod
    public IRubyObject stat(ThreadContext context) {
        openFile.checkClosed(context.getRuntime());
        return context.getRuntime().newFileStat(getOpenFileChecked().getMainStream().getDescriptor().getFileDescriptor());
    }

    /** 
     * <p>Invoke a block for each byte.</p>
     */
    @JRubyMethod(name = "each_byte", frame = true)
    public IRubyObject each_byte(ThreadContext context, Block block) {
        Ruby runtime = context.getRuntime();
        
    	try {
            OpenFile myOpenFile = getOpenFileChecked();
            
            while (true) {
                myOpenFile.checkReadable(runtime);
                myOpenFile.setReadBuffered();

                // TODO: READ_CHECK from MRI
                
                int c = myOpenFile.getMainStream().fgetc();
                
                if (c == -1) {
                    // TODO: check for error, clear it, and wait until readable before trying once more
//                    if (ferror(f)) {
//                        clearerr(f);
//                        if (!rb_io_wait_readable(fileno(f)))
//                            rb_sys_fail(fptr->path);
//                        continue;
//                    }
                    break;
                }
                
                assert c < 256;
                block.yield(context, getRuntime().newFixnum(c));
            }

            // TODO: one more check for error
//            if (ferror(f)) rb_sys_fail(fptr->path);
            return this;
        } catch (PipeException ex) {
            throw runtime.newErrnoEPIPEError();
        } catch (InvalidValueException ex) {
            throw runtime.newErrnoEINVALError();
        } catch (BadDescriptorException e) {
            throw runtime.newErrnoEBADFError();
        } catch (EOFException e) {
            return runtime.getNil();
    	} catch (IOException e) {
    	    throw runtime.newIOError(e.getMessage());
        }
    }

    /** 
     * <p>Invoke a block for each line.</p>
     */
    @JRubyMethod(name = {"each_line", "each"}, optional = 1, frame = true)
    public RubyIO each_line(ThreadContext context, IRubyObject[] args, Block block) {
        Ruby runtime = context.getRuntime();
        ByteList separator = getSeparatorForGets(runtime, args);
        
        for (IRubyObject line = getline(runtime, separator); !line.isNil(); 
        	line = getline(runtime, separator)) {
            block.yield(context, line);
        }
        
        return this;
    }


    @JRubyMethod(name = "readlines", optional = 1)
    public RubyArray readlines(ThreadContext context, IRubyObject[] args) {
        Ruby runtime = context.getRuntime();
        IRubyObject[] separatorArgs = args.length > 0 ? new IRubyObject[] { args[0] } : IRubyObject.NULL_ARRAY;
        ByteList separator = getSeparatorForGets(runtime, separatorArgs);
        RubyArray result = runtime.newArray();
        IRubyObject line;
        
        while (! (line = getline(runtime, separator)).isNil()) {
            result.append(line);
        }
        return result;
    }
    
    @JRubyMethod(name = "to_io")
    public RubyIO to_io() {
    	return this;
    }

    @Override
    public String toString() {
        return "RubyIO(" + openFile.getMode() + ", " + openFile.getMainStream().getDescriptor().getFileno() + ")";
    }
    
    /* class methods for IO */
    
    /** rb_io_s_foreach
    *
    */
    @JRubyMethod(name = "foreach", required = 1, optional = 1, frame = true, meta = true)
    public static IRubyObject foreach(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        Ruby runtime = context.getRuntime();
        int count = args.length;
        IRubyObject filename = args[0].convertToString();
        runtime.checkSafeString(filename);
       
        ByteList separator = getSeparatorFromArgs(runtime, args, 1);

        RubyIO io = (RubyIO)RubyFile.open(context, runtime.getFile(), new IRubyObject[] { filename }, Block.NULL_BLOCK);
        
        if (!io.isNil()) {
            try {
                IRubyObject str = io.getline(runtime, separator);
                while (!str.isNil()) {
                    block.yield(context, str);
                    str = io.getline(runtime, separator);
                }
            } finally {
                io.close();
            }
        }
       
        return runtime.getNil();
    }

    private static RubyIO convertToIO(ThreadContext context, IRubyObject obj) {
        return (RubyIO)TypeConverter.convertToType(obj, context.getRuntime().getIO(), MethodIndex.TO_IO, "to_io");
    }
   
    private static boolean registerSelect(ThreadContext context, Selector selector, IRubyObject obj, RubyIO ioObj, int ops) throws IOException {
       Channel channel = ioObj.getChannel();
       if (channel == null || !(channel instanceof SelectableChannel)) {
           return false;
       }
       
       ((SelectableChannel) channel).configureBlocking(false);
       int real_ops = ((SelectableChannel) channel).validOps() & ops;
       SelectionKey key = ((SelectableChannel) channel).keyFor(selector);
       
       if (key == null) {
           ((SelectableChannel) channel).register(selector, real_ops, obj);
       } else {
           key.interestOps(key.interestOps()|real_ops);
       }
       
       return true;
    }
   
    @JRubyMethod(name = "select", required = 1, optional = 3, meta = true)
    public static IRubyObject select(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        return select_static(context, context.getRuntime(), args);
    }

    private static void checkArrayType(Ruby runtime, IRubyObject obj) {
        if (!(obj instanceof RubyArray)) {
            throw runtime.newTypeError("wrong argument type "
                    + obj.getMetaClass().getName() + " (expected Array)");
        }
    }

    public static IRubyObject select_static(ThreadContext context, Ruby runtime, IRubyObject[] args) {
       try {
           Set pending = new HashSet();
           Set unselectable_reads = new HashSet();
           Set unselectable_writes = new HashSet();
           Selector selector = Selector.open();
           if (!args[0].isNil()) {
               // read
               checkArrayType(runtime, args[0]);
               for (Iterator i = ((RubyArray) args[0]).getList().iterator(); i.hasNext(); ) {
                   IRubyObject obj = (IRubyObject) i.next();
                   RubyIO ioObj = convertToIO(context, obj);
                   if (registerSelect(context, selector, obj, ioObj, SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) {
                       if (ioObj.writeDataBuffered()) {
                           pending.add(obj);
                       }
                   } else {
                       if (( ioObj.openFile.getMode() & OpenFile.READABLE ) != 0) {
                           unselectable_reads.add(obj);
                       }
                   }
               }
           }

           if (args.length > 1 && !args[1].isNil()) {
               // write
               checkArrayType(runtime, args[1]);
               for (Iterator i = ((RubyArray) args[1]).getList().iterator(); i.hasNext(); ) {
                   IRubyObject obj = (IRubyObject) i.next();
                   RubyIO ioObj = convertToIO(context, obj);
                   if (!registerSelect(context, selector, obj, ioObj, SelectionKey.OP_WRITE)) {
                       if (( ioObj.openFile.getMode() & OpenFile.WRITABLE ) != 0) {
                           unselectable_writes.add(obj);
                       }
                   }
               }
           }

           if (args.length > 2 && !args[2].isNil()) {
               checkArrayType(runtime, args[2]);
               // Java's select doesn't do anything about this, so we leave it be.
           }

           final boolean has_timeout = ( args.length > 3 && !args[3].isNil() );
           long timeout = 0;
           if(has_timeout) {
               IRubyObject timeArg = args[3];
               if (timeArg instanceof RubyFloat) {
                   timeout = Math.round(((RubyFloat) timeArg).getDoubleValue() * 1000);
               } else if (timeArg instanceof RubyFixnum) {
                   timeout = Math.round(((RubyFixnum) timeArg).getDoubleValue() * 1000);
               } else { // TODO: MRI also can hadle Bignum here
                   throw runtime.newTypeError("can't convert "
                           + timeArg.getMetaClass().getName() + " into time interval");
               }

               if (timeout < 0) {
                   throw runtime.newArgumentError("negative timeout given");
               }
           }
           
           if (pending.isEmpty() && unselectable_reads.isEmpty() && unselectable_writes.isEmpty()) {
               if (has_timeout) {
                   if (timeout==0) {
                       selector.selectNow();
                   } else {
                       selector.select(timeout);                       
                   }
               } else {
                   selector.select();
               }
           } else {
               selector.selectNow();               
           }
           
           List r = new ArrayList();
           List w = new ArrayList();
           List e = new ArrayList();
           for (Iterator i = selector.selectedKeys().iterator(); i.hasNext(); ) {
               SelectionKey key = (SelectionKey) i.next();
               if ((key.interestOps() & key.readyOps()
                       & (SelectionKey.OP_READ|SelectionKey.OP_ACCEPT|SelectionKey.OP_CONNECT)) != 0) {
                   r.add(key.attachment());
                   pending.remove(key.attachment());
               }
               if ((key.interestOps() & key.readyOps() & (SelectionKey.OP_WRITE)) != 0) {
                   w.add(key.attachment());
               }
           }
           r.addAll(pending);
           r.addAll(unselectable_reads);
           w.addAll(unselectable_writes);
           
           // make all sockets blocking as configured again
           for (Iterator i = selector.keys().iterator(); i.hasNext(); ) {
               SelectionKey key = (SelectionKey) i.next();
               SelectableChannel channel = key.channel();
               synchronized(channel.blockingLock()) {
                   RubyIO originalIO = (RubyIO) TypeConverter.convertToType(
                           (IRubyObject) key.attachment(), runtime.getIO(),
                           MethodIndex.TO_IO, "to_io");
                   boolean blocking = originalIO.getBlocking();
                   key.cancel();
                   channel.configureBlocking(blocking);
               }
           }
           selector.close();
           
           if (r.size() == 0 && w.size() == 0 && e.size() == 0) {
               return runtime.getNil();
           }
           
           List ret = new ArrayList();
           
           ret.add(RubyArray.newArray(runtime, r));
           ret.add(RubyArray.newArray(runtime, w));
           ret.add(RubyArray.newArray(runtime, e));
           
           return RubyArray.newArray(runtime, ret);
       } catch(IOException e) {
           throw runtime.newIOError(e.getMessage());
       }
   }
   
    public static IRubyObject read(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        switch (args.length) {
        case 0: throw context.getRuntime().newArgumentError(0, 1);
        case 1: return read(context, recv, args[0], block);
        case 2: return read(context, recv, args[0], args[1], block);
        case 3: return read(context, recv, args[0], args[1], args[2], block);
        default: throw context.getRuntime().newArgumentError(args.length, 3);
        }
   }
   
    @JRubyMethod(name = "read", meta = true)
    public static IRubyObject read(ThreadContext context, IRubyObject recv, IRubyObject arg0, Block block) {
       IRubyObject[] fileArguments = new IRubyObject[] {arg0};
       RubyIO file = (RubyIO) RubyKernel.open(context, recv, fileArguments, block);
       
       try {
           return file.read(context);
       } finally {
           file.close();
       }
   }
   
    @JRubyMethod(name = "read", meta = true)
    public static IRubyObject read(ThreadContext context, IRubyObject recv, IRubyObject arg0, IRubyObject arg1, Block block) {
       IRubyObject[] fileArguments = new IRubyObject[] {arg0};
       RubyIO file = (RubyIO) RubyKernel.open(context, recv, fileArguments, block);
       
        try {
            if (!arg1.isNil()) {
                return file.read(context, arg1);
            } else {
                return file.read(context);
            }
        } finally  {
            file.close();
        }
   }
   
    @JRubyMethod(name = "read", meta = true)
    public static IRubyObject read(ThreadContext context, IRubyObject recv, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Block block) {
        IRubyObject[] fileArguments = new IRubyObject[]{arg0};
        RubyIO file = (RubyIO) RubyKernel.open(context, recv, fileArguments, block);

        if (!arg2.isNil()) {
            file.seek(context, arg2);
        }

        try {
            if (!arg1.isNil()) {
                return file.read(context, arg1);
            } else {
                return file.read(context);
            }
        } finally  {
            file.close();
        }
    }
   
    @JRubyMethod(name = "readlines", required = 1, optional = 1, meta = true)
    public static RubyArray readlines(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        int count = args.length;

        IRubyObject[] fileArguments = new IRubyObject[]{ args[0].convertToString() };
        IRubyObject[] separatorArguments = count >= 2 ? new IRubyObject[]{args[1]} : IRubyObject.NULL_ARRAY;
        RubyIO file = (RubyIO) RubyKernel.open(context, recv, fileArguments, block);
        try {
            return file.readlines(context, separatorArguments);
        } finally {
            file.close();
        }
    }
   
    @JRubyMethod(name = "popen", required = 1, optional = 1, meta = true)
    public static IRubyObject popen(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        Ruby runtime = context.getRuntime();
        int mode;

        IRubyObject cmdObj = args[0].convertToString();
        runtime.checkSafeString(cmdObj);

        if ("-".equals(cmdObj.toString())) {
            throw runtime.newNotImplementedError("popen(\"-\") is unimplemented");
        }

        try {
            if (args.length == 1) {
                mode = ModeFlags.RDONLY;
            } else if (args[1] instanceof RubyFixnum) {
                mode = RubyFixnum.num2int(args[1]);
            } else {
                mode = getIOModesIntFromString(runtime, args[1].convertToString().toString());
            }

            ModeFlags modes = new ModeFlags(mode);
        
            ShellLauncher.POpenProcess process = ShellLauncher.popen(runtime, cmdObj, modes);
            RubyIO io = new RubyIO(runtime, process, modes);

            if (block.isGiven()) {
                try {
                    return block.yield(context, io);
                } finally {
                    if (io.openFile.isOpen()) {
                        io.close();
                    }
                    runtime.getGlobalVariables().set("$?", RubyProcess.RubyStatus.newProcessStatus(runtime, (process.waitFor() * 256)));
                }
            }
            return io;
        } catch (InvalidValueException ex) {
            throw runtime.newErrnoEINVALError();
        } catch (IOException e) {
            throw runtime.newIOErrorFromException(e);
        } catch (InterruptedException e) {
            throw runtime.newThreadError("unexpected interrupt");
        }
    }

    // NIO based pipe
    @JRubyMethod(name = "pipe", meta = true)
    public static IRubyObject pipe(ThreadContext context, IRubyObject recv) throws Exception {
        // TODO: This isn't an exact port of MRI's pipe behavior, so revisit
       Ruby runtime = context.getRuntime();
       Pipe pipe = Pipe.open();
       
       RubyIO source = new RubyIO(runtime, pipe.source());
       RubyIO sink = new RubyIO(runtime, pipe.sink());
       
       sink.openFile.getMainStream().setSync(true);
       return runtime.newArrayNoCopy(new IRubyObject[] { source, sink });
   }
    
    @JRubyMethod(name = "copy_stream", meta = true, compat = RUBY1_9)
    public static IRubyObject copy_stream(ThreadContext context, IRubyObject recv, 
            IRubyObject stream1, IRubyObject stream2) throws IOException {
        RubyIO io1 = (RubyIO)stream1;
        RubyIO io2 = (RubyIO)stream2;

        ChannelDescriptor d1 = io1.openFile.getMainStream().getDescriptor();
        if (!d1.isSeekable()) {
            throw context.getRuntime().newTypeError("only supports file-to-file copy");
        }
        ChannelDescriptor d2 = io2.openFile.getMainStream().getDescriptor();
        if (!d2.isSeekable()) {
            throw context.getRuntime().newTypeError("only supports file-to-file copy");
        }

        FileChannel f1 = (FileChannel)d1.getChannel();
        FileChannel f2 = (FileChannel)d2.getChannel();

        long size = f1.size();

        f1.transferTo(f2.position(), size, f2);

        return context.getRuntime().newFixnum(size);
    }
    
    /**
     * Add a thread to the list of blocking threads for this IO.
     * 
     * @param thread A thread blocking on this IO
     */
    public synchronized void addBlockingThread(RubyThread thread) {
        if (blockingThreads == null) {
            blockingThreads = new ArrayList<RubyThread>(1);
        }
        blockingThreads.add(thread);
    }
    
    /**
     * Remove a thread from the list of blocking threads for this IO.
     * 
     * @param thread A thread blocking on this IO
     */
    public synchronized void removeBlockingThread(RubyThread thread) {
        if (blockingThreads == null) {
            return;
        }
        for (int i = 0; i < blockingThreads.size(); i++) {
            if (blockingThreads.get(i) == thread) {
                // not using remove(Object) here to avoid the equals() call
                blockingThreads.remove(i);
            }
        }
    }
    
    /**
     * Fire an IOError in all threads blocking on this IO object
     */
    protected synchronized void interruptBlockingThreads() {
        if (blockingThreads == null) {
            return;
        }
        for (int i = 0; i < blockingThreads.size(); i++) {
            RubyThread thread = blockingThreads.get(i);
            
            // raise will also wake the thread from selection
            thread.raise(new IRubyObject[] {getRuntime().newIOError("stream closed").getException()}, Block.NULL_BLOCK);
        }
    }
}
/*
 **** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2005 Thomas E Enebo <enebo@acm.org>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;
import org.jruby.anno.JRubyClass;

import org.jruby.ast.ArgsNode;
import org.jruby.ast.ArgumentNode;
import org.jruby.ast.ListNode;
import org.jruby.ast.LocalAsgnNode;
import org.jruby.javasupport.Java;
import org.jruby.javasupport.JavaObject;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.load.Library;
import org.jruby.internal.runtime.methods.DynamicMethod;

import org.jruby.ast.Node;
import org.jruby.compiler.ASTInspector;
import org.jruby.compiler.ASTCompiler;
import org.jruby.compiler.impl.StandardASMCompiler;
import org.jruby.internal.runtime.methods.MethodArgs;
import org.jruby.javasupport.JavaUtil;
import org.jruby.runtime.InterpretedBlock;
import org.jruby.runtime.ThreadContext;
import org.jruby.util.TypeConverter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.util.TraceClassVisitor;

/**
 * Module which defines JRuby-specific methods for use. 
 */
@JRubyModule(name="JRuby")
public class RubyJRuby {
    public static RubyModule createJRuby(Ruby runtime) {
        ThreadContext context = runtime.getCurrentContext();
        runtime.getKernel().callMethod(context, "require", runtime.newString("java"));
        RubyModule jrubyModule = runtime.defineModule("JRuby");
        
        jrubyModule.defineAnnotatedMethods(RubyJRuby.class);

        RubyClass compiledScriptClass = jrubyModule.defineClassUnder("CompiledScript",runtime.getObject(), runtime.getObject().getAllocator());

        compiledScriptClass.attr_accessor(context, new IRubyObject[]{runtime.newSymbol("name"), runtime.newSymbol("class_name"), runtime.newSymbol("original_script"), runtime.newSymbol("code")});
        compiledScriptClass.defineAnnotatedMethods(JRubyCompiledScript.class);

        return jrubyModule;
    }

    public static RubyModule createJRubyExt(Ruby runtime) {
        runtime.getKernel().callMethod(runtime.getCurrentContext(),"require", runtime.newString("java"));
        RubyModule mJRubyExt = runtime.getOrCreateModule("JRuby").defineModuleUnder("Extensions");
        
        mJRubyExt.defineAnnotatedMethods(JRubyExtensions.class);

        runtime.getObject().includeModule(mJRubyExt);

        return mJRubyExt;
    }

    public static class ExtLibrary implements Library {
        public void load(Ruby runtime, boolean wrap) throws IOException {
            RubyJRuby.createJRubyExt(runtime);
            
            runtime.getMethod().defineAnnotatedMethods(MethodExtensions.class);
        }
    }
    
    public static class TypeLibrary implements Library {
        public void load(Ruby runtime, boolean wrap) throws IOException {
            RubyModule jrubyType = runtime.defineModule("Type");
            jrubyType.defineAnnotatedMethods(TypeLibrary.class);
        }
        
        @JRubyMethod(module = true)
        public static IRubyObject coerce_to(ThreadContext context, IRubyObject self, IRubyObject object, IRubyObject clazz, IRubyObject method) {
            Ruby ruby = object.getRuntime();
            
            if (!(clazz instanceof RubyClass)) {
                throw ruby.newTypeError(clazz, ruby.getClassClass());
            }
            if (!(method instanceof RubySymbol)) {
                throw ruby.newTypeError(method, ruby.getSymbol());
            }
            
            RubyClass rubyClass = (RubyClass)clazz;
            RubySymbol methodSym = (RubySymbol)method;
            
            return TypeConverter.convertToTypeOrRaise(object, rubyClass, methodSym.asJavaString());
        }
    }
    
    @JRubyMethod(name = "runtime", frame = true, module = true)
    public static IRubyObject runtime(IRubyObject recv, Block unusedBlock) {
        return Java.java_to_ruby(recv, JavaObject.wrap(recv.getRuntime(), recv.getRuntime()), Block.NULL_BLOCK);
    }

    @JRubyMethod(name = "objectspace", frame = true, module = true)
    public static IRubyObject getObjectSpaceEnabled(IRubyObject recv, Block b) {
        Ruby runtime = recv.getRuntime();
        return RubyBoolean.newBoolean(
                runtime, runtime.isObjectSpaceEnabled());
    }

    @JRubyMethod(name = "objectspace=", required = 1, frame = true, module = true)
    public static IRubyObject setObjectSpaceEnabled(
            IRubyObject recv, IRubyObject arg, Block b) {
        Ruby runtime = recv.getRuntime();
        runtime.setObjectSpaceEnabled(arg.isTrue());
        return runtime.getNil();
    }

    @JRubyMethod(name = {"parse", "ast_for"}, optional = 3, frame = true, module = true)
    public static IRubyObject parse(IRubyObject recv, IRubyObject[] args, Block block) {
        if(block.isGiven()) {
            if(block.getBody() instanceof org.jruby.runtime.CompiledBlock) {
                throw new RuntimeException("Cannot compile an already compiled block. Use -J-Djruby.jit.enabled=false to avoid this problem.");
            }
            Arity.checkArgumentCount(recv.getRuntime(),args,0,0);
            return Java.java_to_ruby(recv, JavaObject.wrap(recv.getRuntime(), ((InterpretedBlock)block.getBody()).getIterNode().getBodyNode()), Block.NULL_BLOCK);
        } else {
            Arity.checkArgumentCount(recv.getRuntime(),args,1,3);
            String filename = "-";
            boolean extraPositionInformation = false;
            RubyString content = args[0].convertToString();
            if(args.length>1) {
                filename = args[1].convertToString().toString();
                if(args.length>2) {
                    extraPositionInformation = args[2].isTrue();
                }
            }
            return Java.java_to_ruby(recv, JavaObject.wrap(recv.getRuntime(), 
               recv.getRuntime().parse(content.getByteList(), filename, null, 0, extraPositionInformation)), Block.NULL_BLOCK);
        }
    }

    @JRubyMethod(name = "compile", optional = 3, frame = true, module = true)
    public static IRubyObject compile(IRubyObject recv, IRubyObject[] args, Block block) {
        Node node;
        String filename;
        RubyString content;
        if(block.isGiven()) {
            Arity.checkArgumentCount(recv.getRuntime(),args,0,0);
            if(block.getBody() instanceof org.jruby.runtime.CompiledBlock) {
                throw new RuntimeException("Cannot compile an already compiled block. Use -J-Djruby.jit.enabled=false to avoid this problem.");
            }
            content = RubyString.newEmptyString(recv.getRuntime());
            Node bnode = ((InterpretedBlock)block.getBody()).getIterNode().getBodyNode();
            node = new org.jruby.ast.RootNode(bnode.getPosition(), block.getBinding().getDynamicScope(), bnode);
            filename = "__block_" + node.getPosition().getFile();
        } else {
            Arity.checkArgumentCount(recv.getRuntime(),args,1,3);
            filename = "-";
            boolean extraPositionInformation = false;
            content = args[0].convertToString();
            if(args.length>1) {
                filename = args[1].convertToString().toString();
                if(args.length>2) {
                    extraPositionInformation = args[2].isTrue();
                }
            }

            node = recv.getRuntime().parse(content.getByteList(), filename, null, 0, extraPositionInformation);
        }

        String classname;
        if (filename.equals("-e")) {
            classname = "__dash_e__";
        } else {
            classname = filename.replace('\\', '/').replaceAll(".rb", "").replaceAll("-","_dash_");
        }

        ASTInspector inspector = new ASTInspector();
        inspector.inspect(node);
            
        StandardASMCompiler asmCompiler = new StandardASMCompiler(classname, filename);
        ASTCompiler compiler = new ASTCompiler();
        compiler.compileRoot(node, asmCompiler, inspector);
        byte[] bts = asmCompiler.getClassByteArray();

        IRubyObject compiledScript = ((RubyModule)recv).fastGetConstant("CompiledScript").callMethod(recv.getRuntime().getCurrentContext(),"new");
        compiledScript.callMethod(recv.getRuntime().getCurrentContext(), "name=", recv.getRuntime().newString(filename));
        compiledScript.callMethod(recv.getRuntime().getCurrentContext(), "class_name=", recv.getRuntime().newString(classname));
        compiledScript.callMethod(recv.getRuntime().getCurrentContext(), "original_script=", content);
        compiledScript.callMethod(recv.getRuntime().getCurrentContext(), "code=", Java.java_to_ruby(recv, JavaObject.wrap(recv.getRuntime(), bts), Block.NULL_BLOCK));

        return compiledScript;
    }

    @JRubyMethod(name = "reference", required = 1, module = true)
    public static IRubyObject reference(IRubyObject recv, IRubyObject obj) {
        return Java.wrap(recv.getRuntime().getJavaSupport().getJavaUtilitiesModule(),
                JavaObject.wrap(recv.getRuntime(), obj));
    }

    @JRubyMethod(name = "dereference", required = 1, module = true)
    public static IRubyObject dereference(ThreadContext context, IRubyObject recv, IRubyObject obj) {
        if (!(obj.dataGetStruct() instanceof JavaObject)) {
            throw context.getRuntime().newTypeError("got " + obj + ", expected wrapped Java object");
        }
        
        Object unwrapped = JavaUtil.unwrapJavaObject(obj);

        if (!(unwrapped instanceof IRubyObject)) {
            throw context.getRuntime().newTypeError("got " + obj + ", expected Java-wrapped Ruby object");
        }

        return (IRubyObject)unwrapped;
    }

    @JRubyClass(name="JRuby::CompiledScript")
    public static class JRubyCompiledScript {
        @JRubyMethod(name = "to_s")
        public static IRubyObject compiled_script_to_s(IRubyObject recv) {
            return recv.getInstanceVariables().fastGetInstanceVariable("@original_script");
        }

        @JRubyMethod(name = "inspect")
        public static IRubyObject compiled_script_inspect(IRubyObject recv) {
            return recv.getRuntime().newString("#<JRuby::CompiledScript " + recv.getInstanceVariables().fastGetInstanceVariable("@name") + ">");
        }

        @JRubyMethod(name = "inspect_bytecode")
        public static IRubyObject compiled_script_inspect_bytecode(IRubyObject recv) {
            StringWriter sw = new StringWriter();
            ClassReader cr = new ClassReader((byte[])org.jruby.javasupport.JavaUtil.convertRubyToJava(recv.getInstanceVariables().fastGetInstanceVariable("@code"),byte[].class));
            TraceClassVisitor cv = new TraceClassVisitor(new PrintWriter(sw));
            cr.accept(cv, ClassReader.SKIP_DEBUG);
            return recv.getRuntime().newString(sw.toString());
        }
    }

    @JRubyModule(name="JRubyExtensions")
    public static class JRubyExtensions {
        @JRubyMethod(name = "steal_method", required = 2, module = true)
        public static IRubyObject steal_method(IRubyObject recv, IRubyObject type, IRubyObject methodName) {
            RubyModule to_add = null;
            if(recv instanceof RubyModule) {
                to_add = (RubyModule)recv;
            } else {
                to_add = recv.getSingletonClass();
            }
            String name = methodName.toString();
            if(!(type instanceof RubyModule)) {
                throw recv.getRuntime().newArgumentError("First argument must be a module/class");
            }

            DynamicMethod method = ((RubyModule)type).searchMethod(name);
            if(method == null || method.isUndefined()) {
                throw recv.getRuntime().newArgumentError("No such method " + name + " on " + type);
            }

            to_add.addMethod(name, method);
            return recv.getRuntime().getNil();
        }

        @JRubyMethod(name = "steal_methods", required = 1, rest = true, module = true)
        public static IRubyObject steal_methods(IRubyObject recv, IRubyObject[] args) {
            IRubyObject type = args[0];
            for(int i=1;i<args.length;i++) {
                steal_method(recv, type, args[i]);
            }
            return recv.getRuntime().getNil();
        }
    }
    
    public static class MethodExtensions {
        @JRubyMethod(name = "args")
        public static IRubyObject methodArgs(IRubyObject recv) {
            Ruby ruby = recv.getRuntime();
            RubyMethod rubyMethod = (RubyMethod)recv;
            
            DynamicMethod method = rubyMethod.method;
            
            if (method instanceof MethodArgs) {
                MethodArgs interpMethod = (MethodArgs)method;
                ArgsNode args = interpMethod.getArgsNode();
                RubyArray argsArray = RubyArray.newArray(ruby);
                
                RubyArray reqArray = RubyArray.newArray(ruby);
                ListNode requiredArgs = args.getArgs();
                for (int i = 0; requiredArgs != null && i < requiredArgs.size(); i++) {
                    ArgumentNode arg = (ArgumentNode)requiredArgs.get(i);
                    reqArray.append(RubySymbol.newSymbol(ruby, arg.getName()));
                }
                argsArray.append(reqArray);
                
                RubyArray optArray = RubyArray.newArray(ruby);
                ListNode optArgs = args.getOptArgs();
                for (int i = 0; optArgs != null && i < optArgs.size(); i++) {
                    LocalAsgnNode arg = (LocalAsgnNode)optArgs.get(i);
                    optArray.append(RubySymbol.newSymbol(ruby, arg.getName()));
                }
                argsArray.append(optArray);
                
                if (args.getRestArgNode() != null) {
                    argsArray.append(RubySymbol.newSymbol(ruby, args.getRestArgNode().getName()));
                } else {
                    argsArray.append(ruby.getNil());
                }
                
                if (args.getBlockArgNode() != null) {
                    argsArray.append(RubySymbol.newSymbol(ruby, args.getBlockArgNode().getName()));
                } else {
                    argsArray.append(ruby.getNil());
                }
                
                return argsArray;
            }
            
            throw ruby.newTypeError("Method args are only available for standard interpreted or jitted methods");
        }
    }
}
/*
 ***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Chad Fowler <chadfowler@chadfowler.com>
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2006 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004-2005 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2005 Kiel Hodges <jruby-devel@selfsosoft.com>
 * Copyright (C) 2006 Evan Buswell <evan@heron.sytes.net>
 * Copyright (C) 2006 Ola Bini <ola@ologix.com>
 * Copyright (C) 2006 Michael Studman <codehaus@michaelstudman.com>
 * Copyright (C) 2006 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * Copyright (C) 2007 Nick Sieger <nicksieger@gmail.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import static org.jruby.anno.FrameField.*;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;

import org.jruby.ast.util.ArgsUtil;
import org.jruby.common.IRubyWarnings.ID;
import org.jruby.evaluator.ASTInterpreter;
import org.jruby.exceptions.JumpException;
import org.jruby.exceptions.MainExitException;
import org.jruby.exceptions.RaiseException;
import org.jruby.internal.runtime.JumpTarget;
import org.jruby.javasupport.util.RuntimeHelpers;
import org.jruby.runtime.Block;
import org.jruby.runtime.CallType;
import org.jruby.runtime.Frame;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import static org.jruby.runtime.Visibility.*;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.load.IAutoloadMethod;
import org.jruby.runtime.load.LoadService;
import org.jruby.util.IdUtil;
import org.jruby.util.ShellLauncher;
import org.jruby.util.TypeConverter;

/**
 * Note: For CVS history, see KernelModule.java.
 */
@JRubyModule(name="Kernel")
public class RubyKernel {
    public final static Class<?> IRUBY_OBJECT = IRubyObject.class;

    public static RubyModule createKernelModule(Ruby runtime) {
        RubyModule module = runtime.defineModule("Kernel");
        runtime.setKernel(module);

        module.defineAnnotatedMethods(RubyKernel.class);
        module.defineAnnotatedMethods(RubyObject.class);
        
        runtime.setRespondToMethod(module.searchMethod("respond_to?"));
        
        module.setFlag(RubyObject.USER7_F, false); //Kernel is the only Module that doesn't need an implementor

        return module;
    }

    @JRubyMethod(name = "at_exit", frame = true, module = true, visibility = PRIVATE)
    public static IRubyObject at_exit(ThreadContext context, IRubyObject recv, Block block) {
        return context.getRuntime().pushExitBlock(context.getRuntime().newProc(Block.Type.PROC, block));
    }

    @JRubyMethod(name = "autoload?", required = 1, module = true, visibility = PRIVATE)
    public static IRubyObject autoload_p(ThreadContext context, final IRubyObject recv, IRubyObject symbol) {
        Ruby runtime = context.getRuntime();
        RubyModule module = recv instanceof RubyModule ? (RubyModule) recv : runtime.getObject();
        String name = module.getName() + "::" + symbol.asJavaString();
        
        IAutoloadMethod autoloadMethod = runtime.getLoadService().autoloadFor(name);
        if (autoloadMethod == null) return runtime.getNil();

        return runtime.newString(autoloadMethod.file());
    }

    @JRubyMethod(name = "autoload", required = 2, frame = true, module = true, visibility = PRIVATE)
    public static IRubyObject autoload(final IRubyObject recv, IRubyObject symbol, final IRubyObject file) {
        Ruby runtime = recv.getRuntime(); 
        final LoadService loadService = runtime.getLoadService();
        String nonInternedName = symbol.asJavaString();
        
        if (!IdUtil.isValidConstantName(nonInternedName)) {
            throw runtime.newNameError("autoload must be constant name", nonInternedName);
        }
        
        RubyString fileString = file.convertToString();
        
        if (fileString.isEmpty()) {
            throw runtime.newArgumentError("empty file name");
        }
        
        final String baseName = symbol.asJavaString().intern(); // interned, OK for "fast" methods
        final RubyModule module = recv instanceof RubyModule ? (RubyModule) recv : runtime.getObject();
        String nm = module.getName() + "::" + baseName;
        
        IRubyObject existingValue = module.fastFetchConstant(baseName); 
        if (existingValue != null && existingValue != RubyObject.UNDEF) return runtime.getNil();
        
        module.fastStoreConstant(baseName, RubyObject.UNDEF);
        
        loadService.addAutoload(nm, new IAutoloadMethod() {
            public String file() {
                return file.toString();
            }
            /**
             * @see org.jruby.runtime.load.IAutoloadMethod#load(Ruby, String)
             */
            public IRubyObject load(Ruby runtime, String name) {
                boolean required = loadService.require(file());
                
                // File to be loaded by autoload has already been or is being loaded.
                if (!required) return null;
                
                return module.fastGetConstant(baseName);
            }
        });
        return runtime.getNil();
    }

    @JRubyMethod(name = "method_missing", rest = true, frame = true, module = true, visibility = PRIVATE)
    public static IRubyObject method_missing(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        Ruby runtime = context.getRuntime();

        if (args.length == 0 || !(args[0] instanceof RubySymbol)) throw runtime.newArgumentError("no id given");

        Visibility lastVis = context.getLastVisibility();
        CallType lastCallType = context.getLastCallType();

        // create a lightweight thunk
        IRubyObject msg = new RubyNameError.RubyNameErrorMessage(runtime, 
                                                                 recv,
                                                                 args[0],
                                                                 lastVis,
                                                                 lastCallType);
        final IRubyObject[]exArgs;
        final RubyClass exc;
        if (lastCallType != CallType.VARIABLE) {
            exc = runtime.getNoMethodError();
            exArgs = new IRubyObject[]{msg, args[0], RubyArray.newArrayNoCopy(runtime, args, 1)};
        } else {
            exc = runtime.getNameError();
            exArgs = new IRubyObject[]{msg, args[0]};
        }
        
        throw new RaiseException((RubyException)exc.newInstance(context, exArgs, Block.NULL_BLOCK));
    }

    @JRubyMethod(name = "open", required = 1, optional = 2, frame = true, module = true, visibility = PRIVATE)
    public static IRubyObject open(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        String arg = args[0].convertToString().toString();
        Ruby runtime = context.getRuntime();

        if (arg.startsWith("|")) {
            String command = arg.substring(1);
            // exec process, create IO with process
            return RubyIO.popen(context, runtime.getIO(), new IRubyObject[] {runtime.newString(command)}, block);
        } 

        return RubyFile.open(context, runtime.getFile(), args, block);
    }

    @JRubyMethod(name = "getc", module = true, visibility = PRIVATE)
    public static IRubyObject getc(ThreadContext context, IRubyObject recv) {
        context.getRuntime().getWarnings().warn(ID.DEPRECATED_METHOD, "getc is obsolete; use STDIN.getc instead", "getc", "STDIN.getc");
        IRubyObject defin = context.getRuntime().getGlobalVariables().get("$stdin");
        return defin.callMethod(context, "getc");
    }

    @JRubyMethod(name = "gets", optional = 1, module = true, visibility = PRIVATE)
    public static IRubyObject gets(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        return RubyArgsFile.gets(context, context.getRuntime().getGlobalVariables().get("$<"), args);
    }

    @JRubyMethod(name = "abort", optional = 1, module = true, visibility = PRIVATE)
    public static IRubyObject abort(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        if(args.length == 1) {
            context.getRuntime().getGlobalVariables().get("$stderr").callMethod(context,"puts",args[0]);
        }
        throw new MainExitException(1,true);
    }

    @JRubyMethod(name = "Array", required = 1, module = true, visibility = PRIVATE)
    public static IRubyObject new_array(ThreadContext context, IRubyObject recv, IRubyObject object) {
        IRubyObject value = object.checkArrayType();

        if (value.isNil()) {
            if (object.getMetaClass().searchMethod("to_a").getImplementationClass() != context.getRuntime().getKernel()) {
                value = object.callMethod(context, MethodIndex.TO_A, "to_a");
                if (!(value instanceof RubyArray)) throw context.getRuntime().newTypeError("`to_a' did not return Array");
                return value;
            } else {
                return context.getRuntime().newArray(object);
            }
        }
        return value;
    }

    @JRubyMethod(name = "Complex", module = true, visibility = PRIVATE, compat = CompatVersion.RUBY1_9)
    public static IRubyObject new_complex(ThreadContext context, IRubyObject recv) {
        return RuntimeHelpers.invoke(context, context.getRuntime().getComplex(), "convert");
    }
    @JRubyMethod(name = "Complex", module = true, visibility = PRIVATE, compat = CompatVersion.RUBY1_9)
    public static IRubyObject new_complex(ThreadContext context, IRubyObject recv, IRubyObject arg) {
        return RuntimeHelpers.invoke(context, context.getRuntime().getComplex(), "convert", arg);
    }
    @JRubyMethod(name = "Complex", module = true, visibility = PRIVATE, compat = CompatVersion.RUBY1_9)
    public static IRubyObject new_complex(ThreadContext context, IRubyObject recv, IRubyObject arg0, IRubyObject arg1) {
        return RuntimeHelpers.invoke(context, context.getRuntime().getComplex(), "convert", arg0, arg1);
    }
    
    @JRubyMethod(name = "Rational", module = true, visibility = PRIVATE, compat = CompatVersion.RUBY1_9)
    public static IRubyObject new_rational(ThreadContext context, IRubyObject recv) {
        return RuntimeHelpers.invoke(context, context.getRuntime().getRational(), "convert");
    }
    @JRubyMethod(name = "Rational", module = true, visibility = PRIVATE, compat = CompatVersion.RUBY1_9)
    public static IRubyObject new_rational(ThreadContext context, IRubyObject recv, IRubyObject arg) {
        return RuntimeHelpers.invoke(context, context.getRuntime().getRational(), "convert", arg);
    }
    @JRubyMethod(name = "Rational", module = true, visibility = PRIVATE, compat = CompatVersion.RUBY1_9)
    public static IRubyObject new_rational(ThreadContext context, IRubyObject recv, IRubyObject arg0, IRubyObject arg1) {
        return RuntimeHelpers.invoke(context, context.getRuntime().getRational(), "convert", arg0, arg1);
    }

    @JRubyMethod(name = "Float", module = true, visibility = PRIVATE)
    public static IRubyObject new_float(IRubyObject recv, IRubyObject object) {
        if(object instanceof RubyFixnum){
            return RubyFloat.newFloat(object.getRuntime(), ((RubyFixnum)object).getDoubleValue());
        }else if(object instanceof RubyFloat){
            return object;
        }else if(object instanceof RubyBignum){
            return RubyFloat.newFloat(object.getRuntime(), RubyBignum.big2dbl((RubyBignum)object));
        }else if(object instanceof RubyString){
            if(((RubyString)object).getByteList().realSize == 0){ // rb_cstr_to_dbl case
                throw recv.getRuntime().newArgumentError("invalid value for Float(): " + object.inspect());
            }
            return RubyNumeric.str2fnum(recv.getRuntime(),(RubyString)object,true);
        }else if(object.isNil()){
            throw recv.getRuntime().newTypeError("can't convert nil into Float");
        } else {
            RubyFloat rFloat = (RubyFloat)TypeConverter.convertToType(object, recv.getRuntime().getFloat(), MethodIndex.TO_F, "to_f");
            if (Double.isNaN(rFloat.getDoubleValue())) throw recv.getRuntime().newArgumentError("invalid value for Float()");
            return rFloat;
        }
    }

    @JRubyMethod(name = "Integer", required = 1, module = true, visibility = PRIVATE)
    public static IRubyObject new_integer(ThreadContext context, IRubyObject recv, IRubyObject object) {
        if (object instanceof RubyFloat) {
            double val = ((RubyFloat)object).getDoubleValue(); 
            if (val > (double) RubyFixnum.MAX && val < (double) RubyFixnum.MIN) {
                return RubyNumeric.dbl2num(context.getRuntime(),((RubyFloat)object).getDoubleValue());
            }
        } else if (object instanceof RubyFixnum || object instanceof RubyBignum) {
            return object;
        } else if (object instanceof RubyString) {
            return RubyNumeric.str2inum(context.getRuntime(),(RubyString)object,0,true);
        }
        
        IRubyObject tmp = TypeConverter.convertToType(object, context.getRuntime().getInteger(), MethodIndex.TO_INT, "to_int", false);
        if (tmp.isNil()) return object.convertToInteger(MethodIndex.TO_I, "to_i");
        return tmp;
    }

    @JRubyMethod(name = "String", required = 1, module = true, visibility = PRIVATE)
    public static IRubyObject new_string(ThreadContext context, IRubyObject recv, IRubyObject object) {
        return TypeConverter.convertToType(object, context.getRuntime().getString(), MethodIndex.TO_S, "to_s");
    }

    @JRubyMethod(name = "p", rest = true, module = true, visibility = PRIVATE)
    public static IRubyObject p(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        Ruby runtime = context.getRuntime();
        IRubyObject defout = runtime.getGlobalVariables().get("$>");

        for (int i = 0; i < args.length; i++) {
            if (args[i] != null) {
                defout.callMethod(context, "write", RubyObject.inspect(context, args[i]));
                defout.callMethod(context, "write", runtime.newString("\n"));
            }
        }
        
        if (defout instanceof RubyFile) {
            ((RubyFile)defout).flush();
        }
        
        return context.getRuntime().getNil();
    }

    /** rb_f_putc
     */
    @JRubyMethod(name = "putc", required = 1, module = true, visibility = PRIVATE)
    public static IRubyObject putc(ThreadContext context, IRubyObject recv, IRubyObject ch) {
        IRubyObject defout = context.getRuntime().getGlobalVariables().get("$>");
        return defout.callMethod(context, "putc", ch);
    }

    @JRubyMethod(name = "puts", rest = true, module = true, visibility = PRIVATE)
    public static IRubyObject puts(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        IRubyObject defout = context.getRuntime().getGlobalVariables().get("$>");
        
        defout.callMethod(context, "puts", args);

        return context.getRuntime().getNil();
    }

    @JRubyMethod(name = "print", rest = true, module = true, visibility = PRIVATE)
    public static IRubyObject print(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        IRubyObject defout = context.getRuntime().getGlobalVariables().get("$>");

        defout.callMethod(context, "print", args);

        return context.getRuntime().getNil();
    }

    @JRubyMethod(name = "printf", rest = true, module = true, visibility = PRIVATE)
    public static IRubyObject printf(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        if (args.length != 0) {
            IRubyObject defout = context.getRuntime().getGlobalVariables().get("$>");

            if (!(args[0] instanceof RubyString)) {
                defout = args[0];
                args = ArgsUtil.popArray(args);
            }

            defout.callMethod(context, "write", RubyKernel.sprintf(recv, args));
        }

        return context.getRuntime().getNil();
    }

    @JRubyMethod(name = "readline", optional = 1, module = true, visibility = PRIVATE)
    public static IRubyObject readline(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        IRubyObject line = gets(context, recv, args);

        if (line.isNil()) throw context.getRuntime().newEOFError();

        return line;
    }

    @JRubyMethod(name = "readlines", optional = 1, module = true, visibility = PRIVATE)
    public static RubyArray readlines(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        return RubyArgsFile.readlines(context, context.getRuntime().getGlobalVariables().get("$<"), args);
    }

    /** Returns value of $_.
     *
     * @throws TypeError if $_ is not a String or nil.
     * @return value of $_ as String.
     */
    private static RubyString getLastlineString(ThreadContext context, Ruby runtime) {
        IRubyObject line = context.getPreviousFrame().getLastLine();

        if (line.isNil()) {
            throw runtime.newTypeError("$_ value need to be String (nil given).");
        } else if (!(line instanceof RubyString)) {
            throw runtime.newTypeError("$_ value need to be String (" + line.getMetaClass().getName() + " given).");
        } else {
            return (RubyString) line;
        }
    }

    /**
     * Variable-arity version for compatibility. Not bound to Ruby.
     * @deprecated Use the one or two-arg versions.
     */
    public static IRubyObject sub_bang(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        return getLastlineString(context, context.getRuntime()).sub_bang(context, args, block);
    }

    @JRubyMethod(name = "sub!", frame = true, module = true, visibility = PRIVATE, reads = LASTLINE)
    public static IRubyObject sub_bang(ThreadContext context, IRubyObject recv, IRubyObject arg0, Block block) {
        return getLastlineString(context, context.getRuntime()).sub_bang(context, arg0, block);
    }

    @JRubyMethod(name = "sub!", frame = true, module = true, visibility = PRIVATE, reads = LASTLINE)
    public static IRubyObject sub_bang(ThreadContext context, IRubyObject recv, IRubyObject arg0, IRubyObject arg1, Block block) {
        return getLastlineString(context, context.getRuntime()).sub_bang(context, arg0, arg1, block);
    }

    /**
     * Variable-arity version for compatibility. Not bound to Ruby.
     * @deprecated Use the one or two-arg versions.
     */
    public static IRubyObject sub(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        RubyString str = (RubyString) getLastlineString(context, context.getRuntime()).dup();

        if (!str.sub_bang(context, args, block).isNil()) {
            context.getPreviousFrame().setLastLine(str);
        }

        return str;
    }

    @JRubyMethod(name = "sub", frame = true, module = true, visibility = PRIVATE, reads = LASTLINE, writes = LASTLINE)
    public static IRubyObject sub(ThreadContext context, IRubyObject recv, IRubyObject arg0, Block block) {
        RubyString str = (RubyString) getLastlineString(context, context.getRuntime()).dup();

        if (!str.sub_bang(context, arg0, block).isNil()) {
            context.getPreviousFrame().setLastLine(str);
        }

        return str;
    }

    @JRubyMethod(name = "sub", frame = true, module = true, visibility = PRIVATE, reads = LASTLINE, writes = LASTLINE)
    public static IRubyObject sub(ThreadContext context, IRubyObject recv, IRubyObject arg0, IRubyObject arg1, Block block) {
        RubyString str = (RubyString) getLastlineString(context, context.getRuntime()).dup();

        if (!str.sub_bang(context, arg0, arg1, block).isNil()) {
            context.getPreviousFrame().setLastLine(str);
        }

        return str;
    }

    /**
     * Variable-arity version for compatibility. Not bound to Ruby.
     * @deprecated Use the one or two-arg versions.
     */
    public static IRubyObject gsub_bang(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        return getLastlineString(context, context.getRuntime()).gsub_bang(context, args, block);
    }

    @JRubyMethod(name = "gsub!", frame = true, module = true, visibility = PRIVATE, reads = LASTLINE, writes = LASTLINE)
    public static IRubyObject gsub_bang(ThreadContext context, IRubyObject recv, IRubyObject arg0, Block block) {
        return getLastlineString(context, context.getRuntime()).gsub_bang(context, arg0, block);
    }

    @JRubyMethod(name = "gsub!", frame = true, module = true, visibility = PRIVATE, reads = LASTLINE, writes = LASTLINE)
    public static IRubyObject gsub_bang(ThreadContext context, IRubyObject recv, IRubyObject arg0, IRubyObject arg1, Block block) {
        return getLastlineString(context, context.getRuntime()).gsub_bang(context, arg0, arg1, block);
    }

    /**
     * Variable-arity version for compatibility. Not bound to Ruby.
     * @deprecated Use the one or two-arg versions.
     */
    public static IRubyObject gsub(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        RubyString str = (RubyString) getLastlineString(context, context.getRuntime()).dup();

        if (!str.gsub_bang(context, args, block).isNil()) {
            context.getPreviousFrame().setLastLine(str);
        }

        return str;
    }

    @JRubyMethod(name = "gsub", frame = true, module = true, visibility = PRIVATE, reads = LASTLINE, writes = LASTLINE)
    public static IRubyObject gsub(ThreadContext context, IRubyObject recv, IRubyObject arg0, Block block) {
        RubyString str = (RubyString) getLastlineString(context, context.getRuntime()).dup();

        if (!str.gsub_bang(context, arg0, block).isNil()) {
            context.getPreviousFrame().setLastLine(str);
        }

        return str;
    }

    @JRubyMethod(name = "gsub", frame = true, module = true, visibility = PRIVATE, reads = LASTLINE, writes = LASTLINE)
    public static IRubyObject gsub(ThreadContext context, IRubyObject recv, IRubyObject arg0, IRubyObject arg1, Block block) {
        RubyString str = (RubyString) getLastlineString(context, context.getRuntime()).dup();

        if (!str.gsub_bang(context, arg0, arg1, block).isNil()) {
            context.getPreviousFrame().setLastLine(str);
        }

        return str;
    }

    @JRubyMethod(name = "chop!", frame = true, module = true, visibility = PRIVATE, reads = LASTLINE, writes = LASTLINE)
    public static IRubyObject chop_bang(ThreadContext context, IRubyObject recv, Block block) {
        return getLastlineString(context, context.getRuntime()).chop_bang();
    }

    @JRubyMethod(name = "chop", frame = true, module = true, visibility = PRIVATE, reads = LASTLINE, writes = LASTLINE)
    public static IRubyObject chop(ThreadContext context, IRubyObject recv, Block block) {
        RubyString str = getLastlineString(context, context.getRuntime());

        if (str.getByteList().realSize > 0) {
            str = (RubyString) str.dup();
            str.chop_bang();
            context.getPreviousFrame().setLastLine(str);
        }

        return str;
    }

    /**
     * Variable-arity version for compatibility. Not bound to Ruby.
     * @deprecated Use the zero or one-arg versions.
     */
    public static IRubyObject chomp_bang(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        return getLastlineString(context, context.getRuntime()).chomp_bang(args);
    }

    @JRubyMethod(name = "chomp!", frame = true, module = true, visibility = PRIVATE, reads = LASTLINE, writes = LASTLINE)
    public static IRubyObject chomp_bang(ThreadContext context, IRubyObject recv) {
        return getLastlineString(context, context.getRuntime()).chomp_bang();
    }

    @JRubyMethod(name = "chomp!", frame = true, module = true, visibility = PRIVATE, reads = LASTLINE, writes = LASTLINE)
    public static IRubyObject chomp_bang(ThreadContext context, IRubyObject recv, IRubyObject arg0) {
        return getLastlineString(context, context.getRuntime()).chomp_bang(arg0);
    }

    /**
     * Variable-arity version for compatibility. Not bound to Ruby.
     * @deprecated Use the zero or one-arg versions.
     */
    public static IRubyObject chomp(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        RubyString str = getLastlineString(context, context.getRuntime());
        RubyString dup = (RubyString) str.dup();

        if (dup.chomp_bang(args).isNil()) {
            return str;
        } 

        context.getPreviousFrame().setLastLine(dup);
        return dup;
    }

    @JRubyMethod(name = "chomp", frame = true, module = true, visibility = PRIVATE, reads = LASTLINE, writes = LASTLINE)
    public static IRubyObject chomp(ThreadContext context, IRubyObject recv) {
        RubyString str = getLastlineString(context, context.getRuntime());
        RubyString dup = (RubyString) str.dup();

        if (dup.chomp_bang().isNil()) {
            return str;
        } 

        context.getPreviousFrame().setLastLine(dup);
        return dup;
    }

    @JRubyMethod(name = "chomp", frame = true, module = true, visibility = PRIVATE, reads = LASTLINE, writes = LASTLINE)
    public static IRubyObject chomp(ThreadContext context, IRubyObject recv, IRubyObject arg0) {
        RubyString str = getLastlineString(context, context.getRuntime());
        RubyString dup = (RubyString) str.dup();

        if (dup.chomp_bang(arg0).isNil()) {
            return str;
        } 

        context.getPreviousFrame().setLastLine(dup);
        return dup;
    }

    /**
     * Variable arity version for compatibility. Not bound to a Ruby method.
     * 
     * @param context The thread context for the current thread
     * @param recv The receiver of the method (usually a class that has included Kernel)
     * @return
     * @deprecated Use the versions with zero, one, or two args.
     */
    public static IRubyObject split(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        return getLastlineString(context, context.getRuntime()).split(context, args);
    }

    @JRubyMethod(name = "split", frame = true, module = true, visibility = PRIVATE, reads = LASTLINE, writes = {LASTLINE, BACKREF})
    public static IRubyObject split(ThreadContext context, IRubyObject recv) {
        return getLastlineString(context, context.getRuntime()).split(context);
    }

    @JRubyMethod(name = "split", frame = true, module = true, visibility = PRIVATE, reads = LASTLINE, writes = {LASTLINE, BACKREF})
    public static IRubyObject split(ThreadContext context, IRubyObject recv, IRubyObject arg0) {
        return getLastlineString(context, context.getRuntime()).split(context, arg0);
    }

    @JRubyMethod(name = "split", frame = true, module = true, visibility = PRIVATE, reads = LASTLINE, writes = {LASTLINE, BACKREF})
    public static IRubyObject split(ThreadContext context, IRubyObject recv, IRubyObject arg0, IRubyObject arg1) {
        return getLastlineString(context, context.getRuntime()).split(context, arg0, arg1);
    }

    @JRubyMethod(name = "scan", required = 1, frame = true, module = true, visibility = PRIVATE, reads = {LASTLINE, BACKREF}, writes = {LASTLINE, BACKREF})
    public static IRubyObject scan(ThreadContext context, IRubyObject recv, IRubyObject pattern, Block block) {
        return getLastlineString(context, context.getRuntime()).scan(context, pattern, block);
    }

    @JRubyMethod(name = "select", required = 1, optional = 3, module = true, visibility = PRIVATE)
    public static IRubyObject select(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        return RubyIO.select_static(context, context.getRuntime(), args);
    }

    @JRubyMethod(name = "sleep", optional = 1, module = true, visibility = PRIVATE)
    public static IRubyObject sleep(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        long milliseconds;

        if (args.length == 0) {
            // Zero sleeps forever
            milliseconds = 0;
        } else {
            if (!(args[0] instanceof RubyNumeric)) {
                throw context.getRuntime().newTypeError("can't convert " + args[0].getMetaClass().getName() + "into time interval");
            }
            milliseconds = (long) (args[0].convertToFloat().getDoubleValue() * 1000);
            if (milliseconds < 0) {
                throw context.getRuntime().newArgumentError("time interval must be positive");
            } else if (milliseconds == 0) {
                // Explicit zero in MRI returns immediately
                return context.getRuntime().newFixnum(0);
            }
        }
        long startTime = System.currentTimeMillis();
        
        RubyThread rubyThread = context.getThread();
        
        do {
            long loopStartTime = System.currentTimeMillis();
            try {
                rubyThread.sleep(milliseconds);
            } catch (InterruptedException iExcptn) {
            }
            milliseconds -= (System.currentTimeMillis() - loopStartTime);
        } while (milliseconds > 0);

        return context.getRuntime().newFixnum(Math.round((System.currentTimeMillis() - startTime) / 1000.0));
    }

    // FIXME: Add at_exit and finalizers to exit, then make exit_bang not call those.
    @JRubyMethod(name = "exit", optional = 1, module = true, visibility = PRIVATE)
    public static IRubyObject exit(IRubyObject recv, IRubyObject[] args) {
        exit(recv.getRuntime(), args, false);
        return recv.getRuntime().getNil(); // not reached
    }

    @JRubyMethod(name = "exit!", optional = 1, module = true, visibility = PRIVATE)
    public static IRubyObject exit_bang(IRubyObject recv, IRubyObject[] args) {
        exit(recv.getRuntime(), args, true);
        return recv.getRuntime().getNil(); // not reached
    }
    
    private static void exit(Ruby runtime, IRubyObject[] args, boolean hard) {
        runtime.secure(4);

        int status = 1;
        if (args.length > 0) {
            RubyObject argument = (RubyObject)args[0];
            if (argument instanceof RubyFixnum) {
                status = RubyNumeric.fix2int(argument);
            } else {
                status = argument.isFalse() ? 1 : 0;
            }
        }

        if (hard) {
            throw new MainExitException(status, true);
        } else {
            throw runtime.newSystemExit(status);
        }
    }


    /** Returns an Array with the names of all global variables.
     *
     */
    @JRubyMethod(name = "global_variables", module = true, visibility = PRIVATE)
    public static RubyArray global_variables(ThreadContext context, IRubyObject recv) {
        Ruby runtime = context.getRuntime();
        RubyArray globalVariables = runtime.newArray();

        for (String globalVariableName : runtime.getGlobalVariables().getNames()) {
            globalVariables.append(runtime.newString(globalVariableName));
        }

        return globalVariables;
    }

    /** Returns an Array with the names of all local variables.
     *
     */
    @JRubyMethod(name = "local_variables", module = true, visibility = PRIVATE)
    public static RubyArray local_variables(ThreadContext context, IRubyObject recv) {
        final Ruby runtime = context.getRuntime();
        RubyArray localVariables = runtime.newArray();
        
        for (String name: context.getCurrentScope().getAllNamesInScope()) {
            if (IdUtil.isLocal(name)) localVariables.append(runtime.newString(name));
        }

        return localVariables;
    }

    @JRubyMethod(name = "binding", frame = true, module = true, visibility = PRIVATE)
    public static RubyBinding binding(ThreadContext context, IRubyObject recv, Block block) {
        return RubyBinding.newBinding(context.getRuntime());
    }

    @JRubyMethod(name = {"block_given?", "iterator?"}, frame = true, module = true, visibility = PRIVATE)
    public static RubyBoolean block_given_p(ThreadContext context, IRubyObject recv, Block block) {
        return context.getRuntime().newBoolean(context.getPreviousFrame().getBlock().isGiven());
    }


    @Deprecated
    public static IRubyObject sprintf(IRubyObject recv, IRubyObject[] args) {
        return sprintf(recv.getRuntime().getCurrentContext(), recv, args);
    }

    @JRubyMethod(name = {"sprintf", "format"}, required = 1, rest = true, module = true, visibility = PRIVATE)
    public static IRubyObject sprintf(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        if (args.length == 0) {
            throw context.getRuntime().newArgumentError("sprintf must have at least one argument");
        }

        RubyString str = RubyString.stringValue(args[0]);

        RubyArray newArgs = context.getRuntime().newArrayNoCopy(args);
        newArgs.shift();

        return str.op_format(context, newArgs);
    }

    @JRubyMethod(name = {"raise", "fail"}, optional = 3, frame = true, module = true, visibility = PRIVATE)
    public static IRubyObject raise(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        // FIXME: Pass block down?
        Ruby runtime = context.getRuntime();

        if (args.length == 0) {
            IRubyObject lastException = runtime.getGlobalVariables().get("$!");
            if (lastException.isNil()) {
                throw new RaiseException(runtime, runtime.getRuntimeError(), "", false);
            } 
            throw new RaiseException((RubyException) lastException);
        }

        IRubyObject exception;
        
        if (args.length == 1) {
            if (args[0] instanceof RubyString) {
                throw new RaiseException((RubyException)runtime.getRuntimeError().newInstance(context, args, block));
            }
            
            if (!args[0].respondsTo("exception")) {
                throw runtime.newTypeError("exception class/object expected");
            }
            exception = args[0].callMethod(context, "exception");
        } else {
            if (!args[0].respondsTo("exception")) {
                throw runtime.newTypeError("exception class/object expected");
            }

            exception = args[0].callMethod(context, "exception", args[1]);
        }
        
        if (!runtime.fastGetClass("Exception").isInstance(exception)) {
            throw runtime.newTypeError("exception object expected");
        }
        
        if (args.length == 3) {
            ((RubyException) exception).set_backtrace(args[2]);
        }

        if (runtime.getDebug().isTrue()) {
            printExceptionSummary(context, runtime, (RubyException) exception);
        }

        throw new RaiseException((RubyException) exception);
    }

    private static void printExceptionSummary(ThreadContext context, Ruby runtime, RubyException rEx) {
        Frame currentFrame = context.getCurrentFrame();

        String msg = String.format("Exception `%s' at %s:%s - %s\n",
                rEx.getMetaClass(),
                currentFrame.getFile(), currentFrame.getLine() + 1,
                rEx.to_s());

        IRubyObject errorStream = runtime.getGlobalVariables().get("$stderr");
        errorStream.callMethod(context, "write", runtime.newString(msg));
    }

    /**
     * Require.
     * MRI allows to require ever .rb files or ruby extension dll (.so or .dll depending on system).
     * we allow requiring either .rb files or jars.
     * @param recv ruby object used to call require (any object will do and it won't be used anyway).
     * @param name the name of the file to require
     **/
    @JRubyMethod(name = "require", required = 1, frame = true, module = true, visibility = PRIVATE)
    public static IRubyObject require(IRubyObject recv, IRubyObject name, Block block) {
        Ruby runtime = recv.getRuntime();
        
        if (runtime.getLoadService().require(name.convertToString().toString())) {
            return runtime.getTrue();
        }
        return runtime.getFalse();
    }

    @JRubyMethod(name = "load", required = 1, optional = 1, frame = true, module = true, visibility = PRIVATE)
    public static IRubyObject load(IRubyObject recv, IRubyObject[] args, Block block) {
        Ruby runtime = recv.getRuntime();
        RubyString file = args[0].convertToString();
        boolean wrap = args.length == 2 ? args[1].isTrue() : false;

        runtime.getLoadService().load(file.getByteList().toString(), wrap);
        
        return runtime.getTrue();
    }

    @JRubyMethod(name = "eval", required = 1, optional = 3, frame = true, module = true, visibility = PRIVATE)
    public static IRubyObject eval(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        Ruby runtime = context.getRuntime();
            
        // string to eval
        RubyString src = args[0].convertToString();
        runtime.checkSafeString(src);
        
        IRubyObject scope = args.length > 1 && !args[1].isNil() ? args[1] : null;
        String file;
        if (args.length > 2) {
            file = args[2].convertToString().toString();
        } else if (scope == null) {
            file = "(eval)";
        } else {
            file = null;
        }
        int line;
        if (args.length > 3) {
            line = (int) args[3].convertToInteger().getLongValue();
        } else if (scope == null) {
            line = 0;
        } else {
            line = -1;
        }
        if (scope == null) scope = RubyBinding.newBindingForEval(context);
        
        return ASTInterpreter.evalWithBinding(context, src, scope, file, line);
    }

    @JRubyMethod(name = "callcc", frame = true, module = true, visibility = PRIVATE)
    public static IRubyObject callcc(ThreadContext context, IRubyObject recv, Block block) {
        Ruby runtime = context.getRuntime();
        runtime.getWarnings().warn(ID.EMPTY_IMPLEMENTATION, "Kernel#callcc: Continuations are not implemented in JRuby and will not work", "Kernel#callcc");
        IRubyObject cc = runtime.getContinuation().callMethod(context, "new");
        cc.dataWrapStruct(block);
        return block.yield(context, cc);
    }

    @JRubyMethod(name = "caller", optional = 1, frame = true, module = true, visibility = PRIVATE)
    public static IRubyObject caller(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        int level = args.length > 0 ? RubyNumeric.fix2int(args[0]) : 1;

        if (level < 0) {
            throw context.getRuntime().newArgumentError("negative level(" + level + ')');
        }

        return context.createCallerBacktrace(context.getRuntime(), level);
    }

    @JRubyMethod(name = "catch", required = 1, frame = true, module = true, visibility = PRIVATE)
    public static IRubyObject rbCatch(ThreadContext context, IRubyObject recv, IRubyObject tag, Block block) {
        CatchTarget target = new CatchTarget(tag.asJavaString());
        try {
            context.pushCatch(target);
            return block.yield(context, tag);
        } catch (JumpException.ThrowJump tj) {
            if (tj.getTarget() == target) return (IRubyObject) tj.getValue();
            
            throw tj;
        } finally {
            context.popCatch();
        }
    }
    
    public static class CatchTarget implements JumpTarget {
        private final String tag;
        public CatchTarget(String tag) { this.tag = tag; }
        public String getTag() { return tag; }
    }

    @JRubyMethod(name = "throw", required = 1, frame = true, optional = 1, module = true, visibility = PRIVATE)
    public static IRubyObject rbThrow(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        Ruby runtime = context.getRuntime();

        String tag = args[0].asJavaString();
        CatchTarget[] catches = context.getActiveCatches();

        String message = "uncaught throw `" + tag + "'";

        // Ordering of array traversal not important, just intuitive
        for (int i = catches.length - 1 ; i >= 0 ; i--) {
            if (tag.equals(catches[i].getTag())) {
                //Catch active, throw for catch to handle
                throw new JumpException.ThrowJump(catches[i], args.length > 1 ? args[1] : runtime.getNil());
            }
        }

        // No catch active for this throw
        RubyThread currentThread = context.getThread();
        if (currentThread == runtime.getThreadService().getMainThread()) {
            throw runtime.newNameError(message, tag);
        } else {
            throw runtime.newThreadError(message + " in thread 0x" + Integer.toHexString(RubyInteger.fix2int(currentThread.id())));
        }
    }

    @JRubyMethod(name = "trap", required = 1, frame = true, optional = 1, module = true, visibility = PRIVATE)
    public static IRubyObject trap(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        context.getRuntime().getLoadService().require("jsignal");
        return RuntimeHelpers.invoke(context, recv, "__jtrap", args, block);
    }
    
    @JRubyMethod(name = "warn", required = 1, module = true, visibility = PRIVATE)
    public static IRubyObject warn(ThreadContext context, IRubyObject recv, IRubyObject message) {
        Ruby runtime = context.getRuntime();
        
        if (!runtime.getVerbose().isNil()) {
            IRubyObject out = runtime.getGlobalVariables().get("$stderr");
            RuntimeHelpers.invoke(context, out, "puts", message);
        }
        return runtime.getNil();
    }

    @JRubyMethod(name = "set_trace_func", required = 1, frame = true, module = true, visibility = PRIVATE)
    public static IRubyObject set_trace_func(ThreadContext context, IRubyObject recv, IRubyObject trace_func, Block block) {
        if (trace_func.isNil()) {
            context.getRuntime().setTraceFunction(null);
        } else if (!(trace_func instanceof RubyProc)) {
            throw context.getRuntime().newTypeError("trace_func needs to be Proc.");
        } else {
            context.getRuntime().setTraceFunction((RubyProc) trace_func);
        }
        return trace_func;
    }

    @JRubyMethod(name = "trace_var", required = 1, optional = 1, frame = true, module = true, visibility = PRIVATE)
    public static IRubyObject trace_var(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        if (args.length == 0) throw context.getRuntime().newArgumentError(0, 1);
        RubyProc proc = null;
        String var = args.length > 1 ? args[0].toString() : null;
        // ignore if it's not a global var
        if (var.charAt(0) != '$') return context.getRuntime().getNil();
        if (args.length == 1) proc = RubyProc.newProc(context.getRuntime(), block, Block.Type.PROC);
        if (args.length == 2) {
            proc = (RubyProc)TypeConverter.convertToType(args[1], context.getRuntime().getProc(), 0, "to_proc", true);
        }
        
        context.getRuntime().getGlobalVariables().setTraceVar(var, proc);
        
        return context.getRuntime().getNil();
    }

    @JRubyMethod(name = "untrace_var", required = 1, optional = 1, frame = true, module = true, visibility = PRIVATE)
    public static IRubyObject untrace_var(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        if (args.length == 0) throw context.getRuntime().newArgumentError(0, 1);
        String var = args.length >= 1 ? args[0].toString() : null;

        // ignore if it's not a global var
        if (var.charAt(0) != '$') return context.getRuntime().getNil();
        
        if (args.length > 1) {
            ArrayList<IRubyObject> success = new ArrayList<IRubyObject>();
            for (int i = 1; i < args.length; i++) {
                if (context.getRuntime().getGlobalVariables().untraceVar(var, args[i])) {
                    success.add(args[i]);
                }
            }
            return RubyArray.newArray(context.getRuntime(), success);
        } else {
            context.getRuntime().getGlobalVariables().untraceVar(var);
        }
        
        return context.getRuntime().getNil();
    }

    @JRubyMethod(name = "singleton_method_added", required = 1, frame = true, module = true, visibility = PRIVATE)
    public static IRubyObject singleton_method_added(ThreadContext context, IRubyObject recv, IRubyObject symbolId, Block block) {
        return context.getRuntime().getNil();
    }

    @JRubyMethod(name = "singleton_method_removed", required = 1, frame = true, module = true, visibility = PRIVATE)
    public static IRubyObject singleton_method_removed(ThreadContext context, IRubyObject recv, IRubyObject symbolId, Block block) {
        return context.getRuntime().getNil();
    }

    @JRubyMethod(name = "singleton_method_undefined", required = 1, frame = true, module = true, visibility = PRIVATE)
    public static IRubyObject singleton_method_undefined(ThreadContext context, IRubyObject recv, IRubyObject symbolId, Block block) {
        return context.getRuntime().getNil();
    }
    
    @JRubyMethod(name = {"proc", "lambda"}, frame = true, module = true, visibility = PRIVATE, compat = CompatVersion.RUBY1_8)
    public static RubyProc proc(ThreadContext context, IRubyObject recv, Block block) {
        return context.getRuntime().newProc(Block.Type.LAMBDA, block);
    }
    
    @Deprecated
    public static RubyProc proc(IRubyObject recv, Block block) {
        return recv.getRuntime().newProc(Block.Type.LAMBDA, block);
    }
    
    @JRubyMethod(name = {"lambda"}, frame = true, module = true, visibility = PRIVATE, compat = CompatVersion.RUBY1_9)
    public static RubyProc lambda(ThreadContext context, IRubyObject recv, Block block) {
        return context.getRuntime().newProc(Block.Type.LAMBDA, block);
    }
    
    @JRubyMethod(name = {"proc"}, frame = true, module = true, visibility = PRIVATE, compat = CompatVersion.RUBY1_9)
    public static RubyProc proc_1_9(ThreadContext context, IRubyObject recv, Block block) {
        return context.getRuntime().newProc(Block.Type.PROC, block);
    }

    @JRubyMethod(name = "loop", frame = true, module = true, visibility = PRIVATE)
    public static IRubyObject loop(ThreadContext context, IRubyObject recv, Block block) {
        while (true) {
            block.yield(context, context.getRuntime().getNil());

            context.pollThreadEvents();
        }
    }
    
    @JRubyMethod(name = "test", required = 2, optional = 1, module = true, visibility = PRIVATE)
    public static IRubyObject test(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        if (args.length == 0) throw context.getRuntime().newArgumentError("wrong number of arguments");

        int cmd;
        if (args[0] instanceof RubyFixnum) {
            cmd = (int)((RubyFixnum) args[0]).getLongValue();
        } else if (args[0] instanceof RubyString &&
                ((RubyString) args[0]).getByteList().length() > 0) {
            // MRI behavior: use first byte of string value if len > 0
            cmd = ((RubyString) args[0]).getByteList().charAt(0);
        } else {
            cmd = (int) args[0].convertToInteger().getLongValue();
        }
        
        // MRI behavior: raise ArgumentError for 'unknown command' before
        // checking number of args.
        switch(cmd) {
        case 'A': case 'b': case 'c': case 'C': case 'd': case 'e': case 'f': case 'g': case 'G': 
        case 'k': case 'M': case 'l': case 'o': case 'O': case 'p': case 'r': case 'R': case 's':
        case 'S': case 'u': case 'w': case 'W': case 'x': case 'X': case 'z': case '=': case '<':
        case '>': case '-':
            break;
        default:
            throw context.getRuntime().newArgumentError("unknown command ?" + (char) cmd);
        }

        // MRI behavior: now check arg count

        switch(cmd) {
        case '-': case '=': case '<': case '>':
            if (args.length != 3) throw context.getRuntime().newArgumentError(args.length, 3);
            break;
        default:
            if (args.length != 2) throw context.getRuntime().newArgumentError(args.length, 2);
            break;
        }
        
        switch (cmd) {
        case 'A': // ?A  | Time    | Last access time for file1
            return context.getRuntime().newFileStat(args[1].convertToString().toString(), false).atime();
        case 'b': // ?b  | boolean | True if file1 is a block device
            return RubyFileTest.blockdev_p(recv, args[1]);
        case 'c': // ?c  | boolean | True if file1 is a character device
            return RubyFileTest.chardev_p(recv, args[1]);
        case 'C': // ?C  | Time    | Last change time for file1
            return context.getRuntime().newFileStat(args[1].convertToString().toString(), false).ctime();
        case 'd': // ?d  | boolean | True if file1 exists and is a directory
            return RubyFileTest.directory_p(recv, args[1]);
        case 'e': // ?e  | boolean | True if file1 exists
            return RubyFileTest.exist_p(recv, args[1]);
        case 'f': // ?f  | boolean | True if file1 exists and is a regular file
            return RubyFileTest.file_p(recv, args[1]);
        case 'g': // ?g  | boolean | True if file1 has the \CF{setgid} bit
            return RubyFileTest.setgid_p(recv, args[1]);
        case 'G': // ?G  | boolean | True if file1 exists and has a group ownership equal to the caller's group
            return RubyFileTest.grpowned_p(recv, args[1]);
        case 'k': // ?k  | boolean | True if file1 exists and has the sticky bit set
            return RubyFileTest.sticky_p(recv, args[1]);
        case 'M': // ?M  | Time    | Last modification time for file1
            return context.getRuntime().newFileStat(args[1].convertToString().toString(), false).mtime();
        case 'l': // ?l  | boolean | True if file1 exists and is a symbolic link
            return RubyFileTest.symlink_p(recv, args[1]);
        case 'o': // ?o  | boolean | True if file1 exists and is owned by the caller's effective uid
            return RubyFileTest.owned_p(recv, args[1]);
        case 'O': // ?O  | boolean | True if file1 exists and is owned by the caller's real uid 
            return RubyFileTest.rowned_p(recv, args[1]);
        case 'p': // ?p  | boolean | True if file1 exists and is a fifo
            return RubyFileTest.pipe_p(recv, args[1]);
        case 'r': // ?r  | boolean | True if file1 is readable by the effective uid/gid of the caller
            return RubyFileTest.readable_p(recv, args[1]);
        case 'R': // ?R  | boolean | True if file is readable by the real uid/gid of the caller
            // FIXME: Need to implement an readable_real_p in FileTest
            return RubyFileTest.readable_p(recv, args[1]);
        case 's': // ?s  | int/nil | If file1 has nonzero size, return the size, otherwise nil
            return RubyFileTest.size_p(recv, args[1]);
        case 'S': // ?S  | boolean | True if file1 exists and is a socket
            return RubyFileTest.socket_p(recv, args[1]);
        case 'u': // ?u  | boolean | True if file1 has the setuid bit set
            return RubyFileTest.setuid_p(recv, args[1]);
        case 'w': // ?w  | boolean | True if file1 exists and is writable by effective uid/gid
            return RubyFileTest.writable_p(recv, args[1]);
        case 'W': // ?W  | boolean | True if file1 exists and is writable by the real uid/gid
            // FIXME: Need to implement an writable_real_p in FileTest
            return RubyFileTest.writable_p(recv, args[1]);
        case 'x': // ?x  | boolean | True if file1 exists and is executable by the effective uid/gid
            return RubyFileTest.executable_p(recv, args[1]);
        case 'X': // ?X  | boolean | True if file1 exists and is executable by the real uid/gid
            return RubyFileTest.executable_real_p(recv, args[1]);
        case 'z': // ?z  | boolean | True if file1 exists and has a zero length
            return RubyFileTest.zero_p(recv, args[1]);
        case '=': // ?=  | boolean | True if the modification times of file1 and file2 are equal
            return context.getRuntime().newFileStat(args[1].convertToString().toString(), false).mtimeEquals(args[2]);
        case '<': // ?<  | boolean | True if the modification time of file1 is prior to that of file2
            return context.getRuntime().newFileStat(args[1].convertToString().toString(), false).mtimeLessThan(args[2]);
        case '>': // ?>  | boolean | True if the modification time of file1 is after that of file2
            return context.getRuntime().newFileStat(args[1].convertToString().toString(), false).mtimeGreaterThan(args[2]);
        case '-': // ?-  | boolean | True if file1 and file2 are identical
            return RubyFileTest.identical_p(recv, args[1], args[2]);
        default:
            throw new InternalError("unreachable code reached!");
        }
    }

    @JRubyMethod(name = "`", required = 1, module = true, visibility = PRIVATE)
    public static IRubyObject backquote(ThreadContext context, IRubyObject recv, IRubyObject aString) {
        Ruby runtime = context.getRuntime();
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        
        RubyString string = aString.convertToString();
        int resultCode = ShellLauncher.runAndWait(runtime, new IRubyObject[] {string}, output);
        
        runtime.getGlobalVariables().set("$?", RubyProcess.RubyStatus.newProcessStatus(runtime, resultCode));
        
        return RubyString.newString(runtime, output.toByteArray());
    }
    
    @JRubyMethod(name = "srand", optional = 1, module = true, visibility = PRIVATE)
    public static RubyInteger srand(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        Ruby runtime = context.getRuntime();
        long oldRandomSeed = runtime.getRandomSeed();

        if (args.length > 0) {
            RubyInteger integerSeed = args[0].convertToInteger(MethodIndex.TO_INT, "to_int");
            runtime.setRandomSeed(integerSeed.getLongValue());
        } else {
            // Not sure how well this works, but it works much better than
            // just currentTimeMillis by itself.
            runtime.setRandomSeed(System.currentTimeMillis() ^
              recv.hashCode() ^ runtime.incrementRandomSeedSequence() ^
              runtime.getRandom().nextInt(Math.max(1, Math.abs((int)runtime.getRandomSeed()))));
        }
        runtime.getRandom().setSeed(runtime.getRandomSeed());
        return runtime.newFixnum(oldRandomSeed);
    }

    @JRubyMethod(name = "rand", optional = 1, module = true, visibility = PRIVATE)
    public static RubyNumeric rand(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        Ruby runtime = context.getRuntime();
        long ceil;
        if (args.length == 0) {
            ceil = 0;
        } else if (args.length == 1) {
            if (args[0] instanceof RubyBignum) {
                byte[] bigCeilBytes = ((RubyBignum) args[0]).getValue().toByteArray();
                BigInteger bigCeil = new BigInteger(bigCeilBytes).abs();
                
                byte[] randBytes = new byte[bigCeilBytes.length];
                runtime.getRandom().nextBytes(randBytes);
                
                BigInteger result = new BigInteger(randBytes).abs().mod(bigCeil);
                
                return new RubyBignum(runtime, result); 
            }
             
            RubyInteger integerCeil = (RubyInteger)RubyKernel.new_integer(context, recv, args[0]); 
            ceil = Math.abs(integerCeil.getLongValue());
        } else {
            throw runtime.newArgumentError("wrong # of arguments(" + args.length + " for 1)");
        }

        if (ceil == 0) {
            return RubyFloat.newFloat(runtime, runtime.getRandom().nextDouble()); 
        }
        if (ceil > Integer.MAX_VALUE) {
            return runtime.newFixnum(Math.abs(runtime.getRandom().nextLong()) % ceil);
        }
            
        return runtime.newFixnum(runtime.getRandom().nextInt((int) ceil));
    }
    
    @JRubyMethod(name = "syscall", required = 1, optional = 9, module = true, visibility = PRIVATE)
    public static IRubyObject syscall(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        throw context.getRuntime().newNotImplementedError("Kernel#syscall is not implemented in JRuby");
    }

    @JRubyMethod(name = {"system"}, required = 1, rest = true, module = true, visibility = PRIVATE)
    public static RubyBoolean system(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        Ruby runtime = context.getRuntime();
        int resultCode;
        try {
            resultCode = ShellLauncher.runAndWait(runtime, args);
        } catch (Exception e) {
            resultCode = 127;
        }
        runtime.getGlobalVariables().set("$?", RubyProcess.RubyStatus.newProcessStatus(runtime, resultCode));
        return runtime.newBoolean(resultCode == 0);
    }
    
    @JRubyMethod(name = {"exec"}, required = 1, rest = true, module = true, visibility = PRIVATE)
    public static IRubyObject exec(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        Ruby runtime = context.getRuntime();
        int resultCode;
        try {
            // TODO: exec should replace the current process.
            // This could be possible with JNA. 
            resultCode = ShellLauncher.execAndWait(runtime, args);
        } catch (Exception e) {
            throw runtime.newErrnoENOENTError("cannot execute");
        }
        
        return exit(recv, new IRubyObject[] {runtime.newFixnum(resultCode)});
    }

    @JRubyMethod(name = "fork", module = true, visibility = PRIVATE)
    public static IRubyObject fork(ThreadContext context, IRubyObject recv, Block block) {
        Ruby runtime = context.getRuntime();
        
        if (!RubyInstanceConfig.FORK_ENABLED) {
            throw runtime.newNotImplementedError("fork is unsafe and disabled by default on JRuby");
        }
        
        if (block.isGiven()) {
            int pid = runtime.getPosix().fork();
            
            if (pid == 0) {
                try {
                    block.yield(context, runtime.getNil());
                } catch (RaiseException re) {
                    if (re.getException() instanceof RubySystemExit) {
                        throw re;
                    }
                    return exit_bang(recv, new IRubyObject[] {RubyFixnum.minus_one(runtime)});
                } catch (Throwable t) {
                    return exit_bang(recv, new IRubyObject[] {RubyFixnum.minus_one(runtime)});
                }
                return exit_bang(recv, new IRubyObject[] {RubyFixnum.zero(runtime)});
            } else {
                return runtime.newFixnum(pid);
            }
        } else {
            int result = runtime.getPosix().fork();
        
            if (result == -1) {
                return runtime.getNil();
            }

            return runtime.newFixnum(result);
        }
    }

    @JRubyMethod(frame = true, module = true)
    public static IRubyObject tap(ThreadContext context, IRubyObject recv, Block block) {
        block.yield(context, recv);
        return recv;
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2007 Charles O Nutter <headius@headius.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/

package org.jruby;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.builtin.IRubyObject;

@JRubyClass(name="LocalJumpError",parent="StandardError")
public class RubyLocalJumpError extends RubyException {
    private static ObjectAllocator LOCALJUMPERROR_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return new RubyLocalJumpError(runtime, klass);
        }
    };

    public static RubyClass createLocalJumpErrorClass(Ruby runtime, RubyClass standardErrorClass) {
        RubyClass nameErrorClass = runtime.defineClass("LocalJumpError", standardErrorClass, LOCALJUMPERROR_ALLOCATOR);
        
        nameErrorClass.defineAnnotatedMethods(RubyLocalJumpError.class);

        return nameErrorClass;
    }
    
    private RubyLocalJumpError(Ruby runtime, RubyClass exceptionClass) {
        super(runtime, exceptionClass);
    }

    public RubyLocalJumpError(Ruby runtime, RubyClass exceptionClass, String message, String reason, IRubyObject exitValue) {
        super(runtime, exceptionClass, message);
        fastSetInternalVariable("reason", runtime.newSymbol(reason));
        fastSetInternalVariable("exit_value", exitValue);
    }

    @JRubyMethod(name = "reason")
    public IRubyObject reason() {
        return fastGetInternalVariable("reason");
    }
    
    @JRubyMethod(name = "exit_value")
    public IRubyObject exit_value() {
        return fastGetInternalVariable("exit_value");
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2002 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2007 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2003 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004-2005 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;

import org.jruby.javasupport.util.RuntimeHelpers;
import org.jruby.runtime.Block;
import org.jruby.runtime.Constants;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.marshal.MarshalStream;
import org.jruby.runtime.marshal.UnmarshalStream;

import org.jruby.util.ByteList;
import org.jruby.util.IOInputStream;
import org.jruby.util.IOOutputStream;

/**
 * Marshal module
 *
 * @author Anders
 */
@JRubyModule(name="Marshal")
public class RubyMarshal {

    public static RubyModule createMarshalModule(Ruby runtime) {
        RubyModule module = runtime.defineModule("Marshal");
        runtime.setMarshal(module);

        module.defineAnnotatedMethods(RubyMarshal.class);
        module.defineConstant("MAJOR_VERSION", runtime.newFixnum(Constants.MARSHAL_MAJOR));
        module.defineConstant("MINOR_VERSION", runtime.newFixnum(Constants.MARSHAL_MINOR));

        return module;
    }

    @JRubyMethod(name = "dump", required = 1, optional = 2, frame = true, module = true)
    public static IRubyObject dump(IRubyObject recv, IRubyObject[] args, Block unusedBlock) {
        if (args.length < 1) {
            throw recv.getRuntime().newArgumentError("wrong # of arguments(at least 1)");
        }
        IRubyObject objectToDump = args[0];

        IRubyObject io = null;
        int depthLimit = -1;

        if (args.length >= 2) {
            if (args[1].respondsTo("write")) {
                io = args[1];
            } else if (args[1] instanceof RubyFixnum) {
                depthLimit = (int) ((RubyFixnum) args[1]).getLongValue();
            } else {
                throw recv.getRuntime().newTypeError("Instance of IO needed");
            }
            if (args.length == 3) {
                depthLimit = (int) ((RubyFixnum) args[2]).getLongValue();
            }
        }

        try {
            if (io != null) {
                dumpToStream(objectToDump, outputStream(io), depthLimit);
                return io;
            }
			ByteArrayOutputStream stringOutput = new ByteArrayOutputStream();
			dumpToStream(objectToDump, stringOutput, depthLimit);

            return RubyString.newString(recv.getRuntime(), new ByteList(stringOutput.toByteArray(),false));

        } catch (IOException ioe) {
            throw recv.getRuntime().newIOErrorFromException(ioe);
        }

    }

    private static OutputStream outputStream(IRubyObject out) {
        setBinmodeIfPossible(out);
        if (out instanceof RubyIO) {
            return ((RubyIO) out).getOutStream();
        }
        return new IOOutputStream(out);
    }

    private static void setBinmodeIfPossible(IRubyObject io) {
        if (io.respondsTo("binmode")) {
            io.callMethod(io.getRuntime().getCurrentContext(), "binmode");
        }
    }

    @JRubyMethod(name = {"load", "restore"}, required = 1, optional = 1, frame = true, module = true)
    public static IRubyObject load(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block unusedBlock) {
        try {
            if (args.length < 1) {
                throw recv.getRuntime().newArgumentError("wrong number of arguments (0 for 1)");
            }
            
            if (args.length > 2) {
            	throw recv.getRuntime().newArgumentError("wrong number of arguments (" + args.length + " for 2)");
            }
            
            IRubyObject in = null;
            IRubyObject proc = null;

            switch (args.length) {
            case 2:
            	proc = args[1];
            case 1:
            	in = args[0];
            }

            InputStream rawInput;
            if (in != null && in.respondsTo("read")) {
                rawInput = inputStream(in);
            } else if (in != null && in.respondsTo("to_str")) {
                RubyString inString = (RubyString) RuntimeHelpers.invoke(context, in, "to_str");
                ByteList bytes = inString.getByteList();
                rawInput = new ByteArrayInputStream(bytes.unsafeBytes(), bytes.begin(), bytes.length());
            } else {
                throw recv.getRuntime().newTypeError("instance of IO needed");
            }
            
            UnmarshalStream input = new UnmarshalStream(recv.getRuntime(), rawInput, proc);

            return input.unmarshalObject();

        } catch (EOFException ee) {
            throw recv.getRuntime().newEOFError();
        } catch (IOException ioe) {
            throw recv.getRuntime().newIOErrorFromException(ioe);
        }
    }

    private static InputStream inputStream(IRubyObject in) {
        setBinmodeIfPossible(in);
        if (in instanceof RubyIO) {
            return ((RubyIO) in).getInStream();
        }
        return new IOInputStream(in);
    }

    private static void dumpToStream(IRubyObject object, OutputStream rawOutput, int depthLimit)
        throws IOException
    {
        MarshalStream output = new MarshalStream(object.getRuntime(), rawOutput, depthLimit);
        output.dumpObject(object);
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.util.Iterator;

import org.joni.NameEntry;
import org.joni.Regex;
import org.joni.Region;
import org.joni.exception.JOniException;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;

/**
 * @author olabini
 */
@JRubyClass(name="MatchData")
public class RubyMatchData extends RubyObject {
    Region regs;        // captures
    int begin;          // begin and end are used when not groups defined
    int end;
    RubyString str;
    Regex pattern;
    
    public static RubyClass createMatchDataClass(Ruby runtime) {
        // TODO: Is NOT_ALLOCATABLE_ALLOCATOR ok here, since you can't actually instantiate MatchData directly?
        RubyClass matchDataClass = runtime.defineClass("MatchData", runtime.getObject(), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
        runtime.setMatchData(matchDataClass);
        runtime.defineGlobalConstant("MatchingData", matchDataClass);
        matchDataClass.kindOf = new RubyModule.KindOf() {
            public boolean isKindOf(IRubyObject obj, RubyModule type) {
                return obj instanceof RubyMatchData;
            }
        };

        matchDataClass.getMetaClass().undefineMethod("new");
        
        matchDataClass.defineAnnotatedMethods(RubyMatchData.class);

        return matchDataClass;
    }

    public RubyMatchData(Ruby runtime) {
        super(runtime, runtime.getMatchData());
    }

    public final static int MATCH_BUSY = USER2_F;

    // rb_match_busy
    public final void use() {
        flags |= MATCH_BUSY; 
    }

    public final boolean used() {
        return (flags & MATCH_BUSY) != 0;
    }

    private RubyArray match_array(Ruby runtime, int start) {
        if (regs == null) {
            if (start != 0) return runtime.newEmptyArray();
            if (begin == -1) {
                return getRuntime().newArray(runtime.getNil());
            } else {
                RubyString ss = str.makeShared(runtime, begin, end - begin);
                if (isTaint()) ss.setTaint(true);
                return getRuntime().newArray(ss);
            }
        } else {
            RubyArray arr = getRuntime().newArray(regs.numRegs - start);
            for (int i=start; i<regs.numRegs; i++) {
                if (regs.beg[i] == -1) {
                    arr.append(getRuntime().getNil());
                } else {
                    RubyString ss = str.makeShared(runtime, regs.beg[i], regs.end[i] - regs.beg[i]);
                    if (isTaint()) ss.setTaint(true); 
                    arr.append(ss);
                }
            }
            return arr;
        }
        
    }

    public IRubyObject group(long n) {
        return RubyRegexp.nth_match((int)n, this);
    }

    public IRubyObject group(int n) {
        return RubyRegexp.nth_match(n, this);
    }

    @JRubyMethod(name = "inspect")
    public IRubyObject inspect() {
        if (pattern == null) return anyToString();

        RubyString result = getRuntime().newString();
        result.cat((byte)'#').cat((byte)'<');
        result.append(getMetaClass().getRealClass().to_s());

        NameEntry[]names = new NameEntry[regs == null ? 1 : regs.numRegs];

        if (pattern.numberOfNames() > 0) {
            for (Iterator<NameEntry> i = pattern.namedBackrefIterator(); i.hasNext();) {
                NameEntry e = i.next();
                for (int num : e.getBackRefs()) names[num] = e;
            }
        }

        for (int i=0; i<names.length; i++) {
            result.cat((byte)' ');
            if (i > 0) {
                NameEntry e = names[i];
                if (e != null) {
                    result.cat(e.name, e.nameP, e.nameEnd - e.nameP);
                } else {
                    result.cat((byte)('0' + i));
                }
                result.cat((byte)':');
            }
            IRubyObject v = RubyRegexp.nth_match(i, this);
            if (v.isNil()) {
                result.cat("nil".getBytes());
            } else {
                result.append(v.inspect());
            }
        }

        return result.cat((byte)'>');
    }

    /** match_to_a
     *
     */
    @JRubyMethod(name = "to_a")
    @Override
    public RubyArray to_a() {
        return match_array(getRuntime(), 0);
    }

    @JRubyMethod(name = "values_at", required = 1, rest = true)
    public IRubyObject values_at(IRubyObject[] args) {
        return to_a().values_at(args);
    }

    @JRubyMethod(name = "select", frame = true)
    public IRubyObject select(ThreadContext context, Block block) {
        return block.yield(context, to_a());
    }

    /** match_captures
     *
     */
    @JRubyMethod(name = "captures")
    public IRubyObject captures(ThreadContext context) {
        return match_array(context.getRuntime(), 1);
    }

    private int nameToBackrefNumber(RubyString str) {
        ByteList value = str.getByteList();
        try {
            return pattern.nameToBackrefNumber(value.bytes, value.begin, value.begin + value.realSize, regs);
        } catch (JOniException je) {
            throw getRuntime().newIndexError(je.getMessage());
        }
    }

    final int backrefNumber(IRubyObject obj) {
        if (obj instanceof RubySymbol) {
            return nameToBackrefNumber((RubyString)((RubySymbol)obj).id2name());
        } else if (obj instanceof RubyString) {
            return nameToBackrefNumber((RubyString)obj);
        } else {
            return RubyNumeric.num2int(obj);
        }
    }

    /**
     * Variable arity version for compatibility. Not bound to a Ruby method.
     * @deprecated Use the versions with zero, one, or two args.
     */
    public IRubyObject op_aref(IRubyObject[] args) {
        switch (args.length) {
        case 1:
            return op_aref(args[0]);
        case 2:
            return op_aref(args[0], args[1]);
        default:
            Arity.raiseArgumentError(getRuntime(), args.length, 1, 2);
            return null; // not reached
        }
    }

    /** match_aref
     *
     */
    @JRubyMethod(name = "[]")
    public IRubyObject op_aref(IRubyObject idx) {
        IRubyObject result = op_arefCommon(idx);
        return result == null ? ((RubyArray)to_a()).aref(idx) : result;
    }

    /** match_aref
    *
    */
    @JRubyMethod(name = "[]")
    public IRubyObject op_aref(IRubyObject idx, IRubyObject rest) {
        IRubyObject result;
        return !rest.isNil() || (result = op_arefCommon(idx)) == null ? ((RubyArray)to_a()).aref(idx, rest) : result;
    }

    private IRubyObject op_arefCommon(IRubyObject idx) {
        if (idx instanceof RubyFixnum) {
            int num = RubyNumeric.fix2int(idx);
            if (num >= 0) return RubyRegexp.nth_match(num, this);
        } else {
            if (idx instanceof RubySymbol) {
                return RubyRegexp.nth_match(nameToBackrefNumber((RubyString)((RubySymbol)idx).id2name()), this);
            } else if (idx instanceof RubyString) {
                return RubyRegexp.nth_match(nameToBackrefNumber((RubyString)idx), this);
            }
        }
        return null;
    }

    /** match_size
     *
     */
    @JRubyMethod(name = {"size", "length"})
    public IRubyObject size() {
        return regs == null ? RubyFixnum.one(getRuntime()) : RubyFixnum.newFixnum(getRuntime(), regs.numRegs);
    }

    /** match_begin
     *
     */
    @JRubyMethod(name = "begin", required = 1)
    public IRubyObject begin(IRubyObject index) {
        int i = backrefNumber(index);

        if (regs == null) {
            if (i != 0) throw getRuntime().newIndexError("index " + i + " out of matches");
            if (begin < 0) return getRuntime().getNil();
            return RubyFixnum.newFixnum(getRuntime(), begin);
        } else {
            if (i < 0 || regs.numRegs <= i) throw getRuntime().newIndexError("index " + i + " out of matches");
            if (regs.beg[i] < 0) return getRuntime().getNil();
            return RubyFixnum.newFixnum(getRuntime(), regs.beg[i]);
        }
    }

    /** match_end
     *
     */
    @JRubyMethod(name = "end", required = 1)
    public IRubyObject end(IRubyObject index) {
        int i = backrefNumber(index);

        if (regs == null) {
            if (i != 0) throw getRuntime().newIndexError("index " + i + " out of matches");
            if (end < 0) return getRuntime().getNil();
            return RubyFixnum.newFixnum(getRuntime(), end);
        } else {
            if (i < 0 || regs.numRegs <= i) throw getRuntime().newIndexError("index " + i + " out of matches");
            if (regs.end[i] < 0) return getRuntime().getNil();
            return RubyFixnum.newFixnum(getRuntime(), regs.end[i]);
        }
    }

    /** match_offset
     *
     */
    @JRubyMethod(name = "offset", required = 1)
    public IRubyObject offset(IRubyObject index) {
        int i = backrefNumber(index);
        Ruby runtime = getRuntime();

        if (regs == null) {
            if (i != 0) throw getRuntime().newIndexError("index " + i + " out of matches");
            if (begin < 0) return runtime.newArray(runtime.getNil(), runtime.getNil());
            return runtime.newArray(RubyFixnum.newFixnum(runtime, begin),RubyFixnum.newFixnum(runtime, end));            
        } else {
            if (i < 0 || regs.numRegs <= i) throw runtime.newIndexError("index " + i + " out of matches");
            if (regs.beg[i] < 0) return runtime.newArray(runtime.getNil(), runtime.getNil());
            return runtime.newArray(RubyFixnum.newFixnum(runtime, regs.beg[i]),RubyFixnum.newFixnum(runtime, regs.end[i]));
        }
    }

    /** match_pre_match
     *
     */
    @JRubyMethod(name = "pre_match")
    public IRubyObject pre_match(ThreadContext context) {
        RubyString ss;
        
        if (regs == null) {
            if(begin == -1) return context.getRuntime().getNil();
            ss = str.makeShared(context.getRuntime(), 0, begin);
        } else {
            if(regs.beg[0] == -1) return context.getRuntime().getNil();
            ss = str.makeShared(context.getRuntime(), 0, regs.beg[0]);
        }
        
        if (isTaint()) ss.setTaint(true);
        return ss;
    }

    /** match_post_match
     *
     */
    @JRubyMethod(name = "post_match")
    public IRubyObject post_match(ThreadContext context) {
        RubyString ss;
        
        if (regs == null) {
            if (begin == -1) return context.getRuntime().getNil();
            ss = str.makeShared(context.getRuntime(), end, str.getByteList().length() - end);
        } else {
            if (regs.beg[0] == -1) return context.getRuntime().getNil();
            ss = str.makeShared(context.getRuntime(), regs.end[0], str.getByteList().length() - regs.end[0]);
        }
        
        if(isTaint()) ss.setTaint(true);
        return ss;
    }

    /** match_to_s
     *
     */
    @JRubyMethod(name = "to_s")
    public IRubyObject to_s() {
        IRubyObject ss = RubyRegexp.last_match(this);
        if (ss.isNil()) ss = RubyString.newEmptyString(getRuntime());
        if (isTaint()) ss.setTaint(true);
        return ss;
    }

    /** match_string
     *
     */
    @JRubyMethod(name = "string")
    public IRubyObject string() {
        return str; //str is frozen
    }

    @JRubyMethod(name = "initialize_copy", required = 1)
    public IRubyObject initialize_copy(IRubyObject original) {
        if (this == original) return this;
        
        if (!(getMetaClass() == original.getMetaClass())){ // MRI also does a pointer comparison here
            throw getRuntime().newTypeError("wrong argument class");
        }

        RubyMatchData origMatchData = (RubyMatchData)original;
        str = origMatchData.str;
        regs = origMatchData.regs;

        return this;
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Chad Fowler <chadfowler@chadfowler.com>
 * Copyright (C) 2001-2002 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2002-2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;

@JRubyModule(name="Math")
public class RubyMath {
    /** Create the Math module and add it to the Ruby runtime.
     * 
     */
    public static RubyModule createMathModule(Ruby runtime) {
        RubyModule result = runtime.defineModule("Math");
        runtime.setMath(result);
        
        result.defineConstant("E", RubyFloat.newFloat(runtime, Math.E));
        result.defineConstant("PI", RubyFloat.newFloat(runtime, Math.PI));
        
        result.defineAnnotatedMethods(RubyMath.class);

        return result;
    }
    
    
    private static void domainCheck(IRubyObject recv, double value, String msg) {  
        if (Double.isNaN(value)) {
            throw recv.getRuntime().newErrnoEDOMError(msg);
        }
    }
    
    private static double chebylevSerie(double x, double coef[]) {
        double  b0, b1, b2, twox;
        int i;
        b1 = 0.0;
        b0 = 0.0;
        b2 = 0.0;
        twox = 2.0 * x;
        for (i = coef.length-1; i >= 0; i--) {
            b2 = b1;
            b1 = b0;
            b0 = twox * b1 - b2 + coef[i];
        }
        return 0.5*(b0 - b2);
    }
    
    private static double sign(double x, double y) {
        double abs = ((x < 0) ? -x : x);
        return (y < 0.0) ? -abs : abs;
    }
    
    @JRubyMethod(name = "atan2", required = 2, module = true, visibility = Visibility.PRIVATE)
    public static RubyFloat atan2(IRubyObject recv, IRubyObject x, IRubyObject y) {
        double valuea = ((RubyFloat)RubyKernel.new_float(recv,x)).getDoubleValue();
        double valueb = ((RubyFloat)RubyKernel.new_float(recv,y)).getDoubleValue();
        return RubyFloat.newFloat(recv.getRuntime(), Math.atan2(valuea, valueb));
    }

    @JRubyMethod(name = "cos", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static RubyFloat cos(IRubyObject recv, IRubyObject x) {
        double value = ((RubyFloat)RubyKernel.new_float(recv,x)).getDoubleValue();
        return RubyFloat.newFloat(recv.getRuntime(),Math.cos(value));
    }

    @JRubyMethod(name = "sin", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static RubyFloat sin(IRubyObject recv, IRubyObject x) {
        double value = ((RubyFloat)RubyKernel.new_float(recv,x)).getDoubleValue();
        return RubyFloat.newFloat(recv.getRuntime(),Math.sin(value));
    }

    @JRubyMethod(name = "tan", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static RubyFloat tan(IRubyObject recv,  IRubyObject x) {
        double value = ((RubyFloat)RubyKernel.new_float(recv,x)).getDoubleValue();
        return RubyFloat.newFloat(recv.getRuntime(),Math.tan(value));
    }
    
    @JRubyMethod(name = "asin", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static RubyFloat asin(IRubyObject recv, IRubyObject x) {
        double value = ((RubyFloat)RubyKernel.new_float(recv,x)).getDoubleValue();
        double result = Math.asin(value);
        domainCheck(recv, result, "asin");        
        return RubyFloat.newFloat(recv.getRuntime(),result);
    }

    @JRubyMethod(name = "acos", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static RubyFloat acos(IRubyObject recv, IRubyObject x) {
        double value = ((RubyFloat)RubyKernel.new_float(recv,x)).getDoubleValue();
        double result = Math.acos(value);  
        domainCheck(recv, result, "acos");
        return RubyFloat.newFloat(recv.getRuntime(), result);
    }
    
    @JRubyMethod(name = "atan", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static RubyFloat atan(IRubyObject recv, IRubyObject x) {
        double value = ((RubyFloat)RubyKernel.new_float(recv,x)).getDoubleValue();
        return RubyFloat.newFloat(recv.getRuntime(),Math.atan(value));
    }

    @JRubyMethod(name = "cosh", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static RubyFloat cosh(IRubyObject recv, IRubyObject x) {
        double value = ((RubyFloat)RubyKernel.new_float(recv,x)).getDoubleValue();
        return RubyFloat.newFloat(recv.getRuntime(),(Math.exp(value) + Math.exp(-value)) / 2.0);
    }    

    @JRubyMethod(name = "sinh", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static RubyFloat sinh(IRubyObject recv, IRubyObject x) {
        double value = ((RubyFloat)RubyKernel.new_float(recv,x)).getDoubleValue();
        return RubyFloat.newFloat(recv.getRuntime(),(Math.exp(value) - Math.exp(-value)) / 2.0);
    }
    
    @JRubyMethod(name = "tanh", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static RubyFloat tanh(IRubyObject recv, IRubyObject x) {
        double value = ((RubyFloat)RubyKernel.new_float(recv,x)).getDoubleValue();
        return RubyFloat.newFloat(recv.getRuntime(), Math.tanh(value));
    }          
    
    @JRubyMethod(name = "acosh", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static RubyFloat acosh(IRubyObject recv, IRubyObject x) {
        double value = ((RubyFloat)RubyKernel.new_float(recv,x)).getDoubleValue();
        double result;
        if (Double.isNaN(value) || value < 1) {
            result = Double.NaN;
        } else if (value < 94906265.62) {
            result = Math.log(value + Math.sqrt(value * value - 1.0));
        } else{
            result = 0.69314718055994530941723212145818 + Math.log(value);
        }
        
        domainCheck(recv, result, "acosh");
        
        return RubyFloat.newFloat(recv.getRuntime(),result);
    }
    
    private static final double ASINH_COEF[] = {
        -.12820039911738186343372127359268e+0,
        -.58811761189951767565211757138362e-1,
        .47274654322124815640725249756029e-2,
        -.49383631626536172101360174790273e-3,
        .58506207058557412287494835259321e-4,
        -.74669983289313681354755069217188e-5,
        .10011693583558199265966192015812e-5,
        -.13903543858708333608616472258886e-6,
        .19823169483172793547317360237148e-7,
        -.28847468417848843612747272800317e-8,
        .42672965467159937953457514995907e-9,
        -.63976084654366357868752632309681e-10,
        .96991686089064704147878293131179e-11,
        -.14844276972043770830246658365696e-11,
        .22903737939027447988040184378983e-12,
        -.35588395132732645159978942651310e-13,
        .55639694080056789953374539088554e-14,
        -.87462509599624678045666593520162e-15,
        .13815248844526692155868802298129e-15,
        -.21916688282900363984955142264149e-16,
        .34904658524827565638313923706880e-17
    };      
    
    @JRubyMethod(name = "asinh", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static RubyFloat asinh(IRubyObject recv, IRubyObject x) {
        double value = ((RubyFloat)RubyKernel.new_float(recv,x)).getDoubleValue();
        double  y = Math.abs(value);
        double result;
        
        if (Double.isNaN(value)) {
            result = Double.NaN;
        } else if (y <= 1.05367e-08) {
            result = value;
        } else if (y <= 1.0) {          
            result = value * (1.0 + chebylevSerie(2.0 * value * value - 1.0, ASINH_COEF));
        } else if (y < 94906265.62) {
            result = Math.log(value + Math.sqrt(value * value + 1.0));
        } else {    
            result = 0.69314718055994530941723212145818 + Math.log(y);
            if (value < 0) result *= -1;
        }

        return RubyFloat.newFloat(recv.getRuntime(),result);        
    }
    
    private static final double ATANH_COEF[] = {
        .9439510239319549230842892218633e-1,
        .4919843705578615947200034576668e-1,
        .2102593522455432763479327331752e-2,
        .1073554449776116584640731045276e-3,
        .5978267249293031478642787517872e-5,
        .3505062030889134845966834886200e-6,
        .2126374343765340350896219314431e-7,
        .1321694535715527192129801723055e-8,
        .8365875501178070364623604052959e-10,
        .5370503749311002163881434587772e-11,
        .3486659470157107922971245784290e-12,
        .2284549509603433015524024119722e-13,
        .1508407105944793044874229067558e-14,
        .1002418816804109126136995722837e-15,
        .6698674738165069539715526882986e-17,
        .4497954546494931083083327624533e-18
    };    
    
    @JRubyMethod(name = "atanh", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static RubyFloat atanh(IRubyObject recv, IRubyObject x) {
        double value = ((RubyFloat)RubyKernel.new_float(recv,x)).getDoubleValue();
        double  y = Math.abs(value);
        double  result;

        if (Double.isNaN(value)) {
            result = Double.NaN;
        } else if (y < 1.82501e-08) {
            result = value;
        } else if (y <= 0.5) {
            result = value * (1.0 + chebylevSerie(8.0 * value * value - 1.0, ATANH_COEF));
        } else if (y < 1.0) {
            result = 0.5 * Math.log((1.0 + value) / (1.0 - value));
        } else if (y == 1.0) {
            result = value * Double.POSITIVE_INFINITY;
        } else {
            result = Double.NaN;
        }

        domainCheck(recv, result, "atanh");
        return RubyFloat.newFloat(recv.getRuntime(),result);        
    }
    
    @JRubyMethod(name = "exp", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static RubyFloat exp(IRubyObject recv, IRubyObject exponent) {
        double value = ((RubyFloat)RubyKernel.new_float(recv,exponent)).getDoubleValue();
        return RubyFloat.newFloat(recv.getRuntime(),Math.exp(value));
    }

    /** Returns the natural logarithm of x.
     * 
     */
    @JRubyMethod(name = "log", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static RubyFloat log(IRubyObject recv, IRubyObject x) {
        double value = ((RubyFloat)RubyKernel.new_float(recv,x)).getDoubleValue();
        double result = Math.log(value);
        domainCheck(recv, result, "log");
        return RubyFloat.newFloat(recv.getRuntime(),result);
    }

    /** Returns the base 10 logarithm of x.
     * 
     */
    @JRubyMethod(name = "log10", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static RubyFloat log10(IRubyObject recv, IRubyObject x) {
        double value = ((RubyFloat)RubyKernel.new_float(recv,x)).getDoubleValue();
        double result =  Math.log(value) / Math.log(10);
        domainCheck(recv, result, "log10");
        return RubyFloat.newFloat(recv.getRuntime(),result);
    }

    @JRubyMethod(name = "sqrt", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static RubyFloat sqrt(IRubyObject recv, IRubyObject x) {
        double value = ((RubyFloat)RubyKernel.new_float(recv,x)).getDoubleValue();
        double result;

        if (value < 0) {
            result = Double.NaN;
        } else{
            result = Math.sqrt(value);
        }
        
        domainCheck(recv, result, "sqrt");
        return RubyFloat.newFloat(recv.getRuntime(), result);
    }
    
    @JRubyMethod(name = "hypot", required = 2, module = true, visibility = Visibility.PRIVATE)
    public static RubyFloat hypot(IRubyObject recv, IRubyObject x, IRubyObject y) {
        double valuea = ((RubyFloat)RubyKernel.new_float(recv,x)).getDoubleValue(); 
        double valueb = ((RubyFloat)RubyKernel.new_float(recv,y)).getDoubleValue();
        double result;
        
        if (Math.abs(valuea) > Math.abs(valueb)) {
            result = valueb / valuea;
            result = Math.abs(valuea) * Math.sqrt(1 + result * result);
        } else if (valueb != 0) {
            result = valuea / valueb;
            result = Math.abs(valueb) * Math.sqrt(1 + result * result);
        } else {
            result = 0;
        }
        return RubyFloat.newFloat(recv.getRuntime(),result);
    }    
    
    
    /*
     * x = mantissa * 2 ** exponent
     *
     * Where mantissa is in the range of [.5, 1)
     *
     */
    @JRubyMethod(name = "frexp", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static RubyArray frexp(IRubyObject recv, IRubyObject other) {
        double mantissa = ((RubyFloat)RubyKernel.new_float(recv,other)).getDoubleValue();
        short sign = 1;
        long exponent = 0;

        if (mantissa != 0.0) {
            // Make mantissa same sign so we only have one code path.
            if (mantissa < 0) {
                mantissa = -mantissa;
                sign = -1;
            }

            // Increase value to hit lower range.
            for (; mantissa < 0.5; mantissa *= 2.0, exponent -=1) { }

            // Decrease value to hit upper range.  
            for (; mantissa >= 1.0; mantissa *= 0.5, exponent +=1) { }
        }
	 
        return RubyArray.newArray(recv.getRuntime(), 
                                 RubyFloat.newFloat(recv.getRuntime(), sign * mantissa),
                                 RubyNumeric.int2fix(recv.getRuntime(), exponent));
    }

    /*
     * r = x * 2 ** y
     */
    @JRubyMethod(name = "ldexp", required = 2, module = true, visibility = Visibility.PRIVATE)
    public static RubyFloat ldexp(IRubyObject recv, IRubyObject mantissa, IRubyObject exponent) {
        double mantissaValue = ((RubyFloat)RubyKernel.new_float(recv, mantissa)).getDoubleValue();
        return RubyFloat.newFloat(recv.getRuntime(),mantissaValue * Math.pow(2.0, RubyNumeric.num2int(exponent)));
    }

    private static final double ERFC_COEF[] = {
         -.490461212346918080399845440334e-1,
         -.142261205103713642378247418996e0,
         .100355821875997955757546767129e-1,
         -.576876469976748476508270255092e-3,
         .274199312521960610344221607915e-4,
         -.110431755073445076041353812959e-5,
         .384887554203450369499613114982e-7,
         -.118085825338754669696317518016e-8,
         .323342158260509096464029309534e-10,
         -.799101594700454875816073747086e-12,
         .179907251139614556119672454866e-13,
         -.371863548781869263823168282095e-15,
         .710359900371425297116899083947e-17,
         -.126124551191552258324954248533e-18
    };
    
    @JRubyMethod(name = "erf", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static RubyFloat erf(IRubyObject recv, IRubyObject x) {
        double value = ((RubyFloat)RubyKernel.new_float(recv,x)).getDoubleValue();

        double  result;
        double  y = Math.abs(value);

        if (y <= 1.49012e-08) {
            result = 2 * value / 1.77245385090551602729816748334;
        } else if (y <= 1) {
            result = value * (1 + chebylevSerie(2 * value * value - 1, ERFC_COEF));
        } else if (y < 6.013687357) {
            result = sign(1 - erfc(recv, RubyFloat.newFloat(recv.getRuntime(),y)).getDoubleValue(), value);
        } else {
            result = sign(1, value);
        }
        return RubyFloat.newFloat(recv.getRuntime(),result);
    }

    private static final double ERFC2_COEF[] = {
         -.69601346602309501127391508262e-1,
         -.411013393626208934898221208467e-1,
         .391449586668962688156114370524e-2,
         -.490639565054897916128093545077e-3,
         .715747900137703638076089414183e-4,
         -.115307163413123283380823284791e-4,
         .199467059020199763505231486771e-5,
         -.364266647159922287393611843071e-6,
         .694437261000501258993127721463e-7,
         -.137122090210436601953460514121e-7,
         .278838966100713713196386034809e-8,
         -.581416472433116155186479105032e-9,
         .123892049175275318118016881795e-9,
         -.269063914530674343239042493789e-10,
         .594261435084791098244470968384e-11,
         -.133238673575811957928775442057e-11,
         .30280468061771320171736972433e-12,
         -.696664881494103258879586758895e-13,
         .162085454105392296981289322763e-13,
         -.380993446525049199987691305773e-14,
         .904048781597883114936897101298e-15,
         -.2164006195089607347809812047e-15,
         .522210223399585498460798024417e-16,
         -.126972960236455533637241552778e-16,
         .310914550427619758383622741295e-17,
         -.766376292032038552400956671481e-18,
         .190081925136274520253692973329e-18
    };

    private static final double ERFCC_COEF[] = {
         .715179310202924774503697709496e-1,
         -.265324343376067157558893386681e-1,
         .171115397792085588332699194606e-2,
         -.163751663458517884163746404749e-3,
         .198712935005520364995974806758e-4,
         -.284371241276655508750175183152e-5,
         .460616130896313036969379968464e-6,
         -.822775302587920842057766536366e-7,
         .159214187277090112989358340826e-7,
         -.329507136225284321486631665072e-8,
         .72234397604005554658126115389e-9,
         -.166485581339872959344695966886e-9,
         .401039258823766482077671768814e-10,
         -.100481621442573113272170176283e-10,
         .260827591330033380859341009439e-11,
         -.699111056040402486557697812476e-12,
         .192949233326170708624205749803e-12,
         -.547013118875433106490125085271e-13,
         .158966330976269744839084032762e-13,
         -.47268939801975548392036958429e-14,
         .14358733767849847867287399784e-14,
         -.444951056181735839417250062829e-15,
         .140481088476823343737305537466e-15,
         -.451381838776421089625963281623e-16,
         .147452154104513307787018713262e-16,
         -.489262140694577615436841552532e-17,
         .164761214141064673895301522827e-17,
         -.562681717632940809299928521323e-18,
         .194744338223207851429197867821e-18
    };
        
    @JRubyMethod(name = "erfc", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static RubyFloat erfc(IRubyObject recv, IRubyObject x) {
        double value = ((RubyFloat)RubyKernel.new_float(recv,x)).getDoubleValue();
        double  result;
        double  y = Math.abs(value);

        if (value <= -6.013687357) {
            result = 2;
        } else if (y < 1.49012e-08) {
            result = 1 - 2 * value / 1.77245385090551602729816748334;
        } else {
            double ysq = y*y;
            if (y < 1) {
                result = 1 - value * (1 + chebylevSerie(2 * ysq - 1, ERFC_COEF));
            } else if (y <= 4.0) {
                result = Math.exp(-ysq)/y*(0.5+chebylevSerie((8.0 / ysq - 5.0) / 3.0, ERFC2_COEF));
                if (value < 0) result = 2.0 - result;
                if (value < 0) result = 2.0 - result;
                if (value < 0) result = 2.0 - result;
            } else {
                result = Math.exp(-ysq) / y * (0.5 + chebylevSerie(8.0 / ysq - 1, ERFCC_COEF));
                if (value < 0) result = 2.0 - result;
            }
        }
        return RubyFloat.newFloat(recv.getRuntime(),result);        
    }

}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2002 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.exceptions.JumpException;
import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.runtime.Block;
import org.jruby.runtime.CallbackFactory;
import org.jruby.runtime.MethodBlock;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;

/** 
 * The RubyMethod class represents a RubyMethod object.
 * 
 * You can get such a method by calling the "method" method of an object.
 * 
 * Note: This was renamed from Method.java
 * 
 * @author  jpetersen
 * @since 0.2.3
 */
@JRubyClass(name="Method")
public class RubyMethod extends RubyObject {
    protected RubyModule implementationModule;
    protected String methodName;
    protected RubyModule originModule;
    protected String originName;
    protected DynamicMethod method;
    protected IRubyObject receiver;

    protected RubyMethod(Ruby runtime, RubyClass rubyClass) {
        super(runtime, rubyClass);
    }

    /** Create the RubyMethod class and add it to the Ruby runtime.
     * 
     */
    public static RubyClass createMethodClass(Ruby runtime) {
        // TODO: NOT_ALLOCATABLE_ALLOCATOR is probably ok here. Confirm. JRUBY-415
        RubyClass methodClass = runtime.defineClass("Method", runtime.getObject(), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
        runtime.setMethod(methodClass);
        
        methodClass.defineAnnotatedMethods(RubyMethod.class);
        
        return methodClass;
    }

    public static RubyMethod newMethod(
        RubyModule implementationModule,
        String methodName,
        RubyModule originModule,
        String originName,
        DynamicMethod method,
        IRubyObject receiver) {
        Ruby runtime = implementationModule.getRuntime();
        RubyMethod newMethod = new RubyMethod(runtime, runtime.getMethod());

        newMethod.implementationModule = implementationModule;
        newMethod.methodName = methodName;
        newMethod.originModule = originModule;
        newMethod.originName = originName;
        newMethod.method = method.getRealMethod();
        newMethod.receiver = receiver;

        return newMethod;
    }

    /** Call the method.
     * 
     */
    @JRubyMethod(name = {"call", "[]"})
    public IRubyObject call(ThreadContext context, Block block) {
        return method.call(context, receiver, implementationModule, methodName, block);
    }
    @JRubyMethod(name = {"call", "[]"})
    public IRubyObject call(ThreadContext context, IRubyObject arg, Block block) {
        return method.call(context, receiver, implementationModule, methodName, arg, block);
    }
    @JRubyMethod(name = {"call", "[]"})
    public IRubyObject call(ThreadContext context, IRubyObject arg0, IRubyObject arg1, Block block) {
        return method.call(context, receiver, implementationModule, methodName, arg0, arg1, block);
    }
    @JRubyMethod(name = {"call", "[]"})
    public IRubyObject call(ThreadContext context, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Block block) {
        return method.call(context, receiver, implementationModule, methodName, arg0, arg1, arg2, block);
    }
    @JRubyMethod(name = {"call", "[]"}, rest = true)
    public IRubyObject call(ThreadContext context, IRubyObject[] args, Block block) {
        return method.call(context, receiver, implementationModule, methodName, args, block);
    }

    /** Returns the number of arguments a method accepted.
     * 
     * @return the number of arguments of a method.
     */
    @JRubyMethod(name = "arity")
    public RubyFixnum arity() {
        return getRuntime().newFixnum(method.getArity().getValue());
    }

    @JRubyMethod(name = "==", required = 1)
    @Override
    public RubyBoolean op_equal(ThreadContext context, IRubyObject other) {
        if (!(other instanceof RubyMethod)) return context.getRuntime().getFalse();
        RubyMethod otherMethod = (RubyMethod)other;
        return context.getRuntime().newBoolean(implementationModule == otherMethod.implementationModule &&
                                       originModule == otherMethod.originModule &&
                                       receiver == otherMethod.receiver &&
                                       method.getRealMethod() == otherMethod.method.getRealMethod());
    }

    @JRubyMethod(name = "clone")
    @Override
    public RubyMethod rbClone() {
        return newMethod(implementationModule, methodName, originModule, originName, method, receiver);
    }

    /** Create a Proc object.
     * 
     */
    @JRubyMethod(name = "to_proc", frame = true)
    public IRubyObject to_proc(ThreadContext context, Block unusedBlock) {
        Ruby runtime = context.getRuntime();
        CallbackFactory f = runtime.callbackFactory(RubyMethod.class);
        Block block = MethodBlock.createMethodBlock(context, context.getCurrentScope(), 
                f.getBlockMethod("bmcall"), this, runtime.getTopSelf());
        
        while (true) {
            try {
                // FIXME: We should not be regenerating this over and over
                return mproc(context, block);
            } catch (JumpException.BreakJump bj) {
                return (IRubyObject) bj.getValue();
            } catch (JumpException.ReturnJump rj) {
                return (IRubyObject) rj.getValue();
            } catch (JumpException.RetryJump rj) {
                // Execute iterateMethod again.
            }
        }
    }

    /** Create a Proc object which is called like a ruby method.
     *
     * Used by the RubyMethod#to_proc method.
     *
     */
    private IRubyObject mproc(ThreadContext context, Block block) {
        try {
            context.preMproc();
            
            return RubyKernel.proc(context, context.getRuntime().getNil(), block);
        } finally {
            context.postMproc();
        }
    }

    /** Delegate a block call to a bound method call.
     *
     * Used by the RubyMethod#to_proc method.
     *
     */
    public static IRubyObject bmcall(IRubyObject blockArg, IRubyObject arg1,
            IRubyObject self, Block unusedBlock) {
        ThreadContext context = blockArg.getRuntime().getCurrentContext();
        
        if (blockArg instanceof RubyArray) {
            // ENEBO: Very wrong
            return ((RubyMethod) arg1).call(context, ((RubyArray) blockArg).toJavaArray(), Block.NULL_BLOCK);
        }
        // ENEBO: Very wrong
        return ((RubyMethod) arg1).call(context, new IRubyObject[] { blockArg }, Block.NULL_BLOCK);
    }

    @JRubyMethod(name = "unbind", frame = true)
    public RubyUnboundMethod unbind(Block unusedBlock) {
        RubyUnboundMethod unboundMethod =
        	RubyUnboundMethod.newUnboundMethod(implementationModule, methodName, originModule, originName, method);
        unboundMethod.infectBy(this);
        
        return unboundMethod;
    }
    
    @JRubyMethod(name = {"inspect", "to_s"})
    @Override
    public IRubyObject inspect() {
        StringBuilder buf = new StringBuilder("#<");
        char delimeter = '#';
        
        buf.append(getMetaClass().getRealClass().getName()).append(": ");
        
        if (implementationModule.isSingleton()) {
            IRubyObject attached = ((MetaClass) implementationModule).getAttached();
            if (receiver == null) {
                buf.append(implementationModule.inspect().toString());
            } else if (receiver == attached) {
                buf.append(attached.inspect().toString());
                delimeter = '.';
            } else {
                buf.append(receiver.inspect().toString());
                buf.append('(').append(attached.inspect().toString()).append(')');
                delimeter = '.';
            }
        } else {
            buf.append(originModule.getName());
            
            if (implementationModule != originModule) {
                buf.append('(').append(implementationModule.getName()).append(')');
            }
        }
        
        buf.append(delimeter).append(methodName).append('>');
        
        RubyString str = getRuntime().newString(buf.toString());
        str.setTaint(isTaint());
        return str;
    }
}

/*
 **** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Chad Fowler <chadfowler@chadfowler.com>
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004-2005 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2006-2007 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * Copyright (C) 2007 William N Dortch <bill.dortch@gmail.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyConstant;
import org.jruby.anno.JavaMethodDescriptor;
import org.jruby.anno.TypePopulator;
import org.jruby.common.IRubyWarnings.ID;
import org.jruby.compiler.ASTInspector;
import org.jruby.internal.runtime.methods.AliasMethod;
import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.internal.runtime.methods.FullFunctionCallbackMethod;
import org.jruby.internal.runtime.methods.SimpleCallbackMethod;
import org.jruby.internal.runtime.methods.MethodMethod;
import org.jruby.internal.runtime.methods.ProcMethod;
import org.jruby.internal.runtime.methods.UndefinedMethod;
import org.jruby.internal.runtime.methods.WrapperMethod;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.CacheMap;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import static org.jruby.runtime.Visibility.*;
import static org.jruby.anno.FrameField.*;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.builtin.Variable;
import org.jruby.runtime.callback.Callback;
import org.jruby.runtime.component.VariableEntry;
import org.jruby.runtime.marshal.MarshalStream;
import org.jruby.runtime.marshal.UnmarshalStream;
import org.jruby.util.ClassProvider;
import org.jruby.util.IdUtil;
import org.jruby.exceptions.RaiseException;
import org.jruby.internal.runtime.methods.JavaMethod;
import org.jruby.javasupport.util.RuntimeHelpers;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.MethodFactory;
import org.jruby.runtime.MethodIndex;

/**
 *
 * @author  jpetersen
 */
@JRubyClass(name="Module")
public class RubyModule extends RubyObject {
    private static final boolean DEBUG = false;
    
    public static RubyClass createModuleClass(Ruby runtime, RubyClass moduleClass) {
        moduleClass.index = ClassIndex.MODULE;
        moduleClass.kindOf = new RubyModule.KindOf() {
            @Override
            public boolean isKindOf(IRubyObject obj, RubyModule type) {
                return obj instanceof RubyModule;
            }
        };
        
        moduleClass.defineAnnotatedMethods(RubyModule.class);
        moduleClass.defineAnnotatedMethods(ModuleKernelMethods.class);

        return moduleClass;
    }
    
    public static class ModuleKernelMethods {
        @JRubyMethod
        public static IRubyObject autoload(IRubyObject recv, IRubyObject arg0, IRubyObject arg1) {
            return RubyKernel.autoload(recv, arg0, arg1);
        }
        
        @JRubyMethod(name = "autoload?")
        public static IRubyObject autoload_p(ThreadContext context, IRubyObject recv, IRubyObject arg0) {
            return RubyKernel.autoload_p(context, recv, arg0);
        }
    }
    
    static ObjectAllocator MODULE_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return new RubyModule(runtime, klass);
        }
    };
    
    @Override
    public int getNativeTypeIndex() {
        return ClassIndex.MODULE;
    }

    @Override
    public boolean isModule() {
        return true;
    }

    @Override
    public boolean isClass() {
        return false;
    }

    public boolean isSingleton() {
        return false;
    }    
    
    // superClass may be null.
    protected RubyClass superClass;

    public int index;

    public static class KindOf {
        public static final KindOf DEFAULT_KIND_OF = new KindOf();
        public boolean isKindOf(IRubyObject obj, RubyModule type) {
            return obj.getMetaClass().hasModuleInHierarchy(type);
        }
    }
    
    public boolean isInstance(IRubyObject object) {
        return kindOf.isKindOf(object, this);
    }

    public KindOf kindOf = KindOf.DEFAULT_KIND_OF;

    public final int id;

    // Containing class...The parent of Object is null. Object should always be last in chain.
    public RubyModule parent;

    // ClassId is the name of the class/module sans where it is located.
    // If it is null, then it an anonymous class.
    protected String classId;


    // CONSTANT TABLE
    
    // Lock used for variableTable/constantTable writes. The RubyObject variableTable
    // write methods are overridden here to use this lock rather than Java
    // synchronization for faster concurrent writes for modules/classes.
    protected final ReentrantLock variableWriteLock = new ReentrantLock();
    
    protected transient volatile ConstantTableEntry[] constantTable =
        new ConstantTableEntry[CONSTANT_TABLE_DEFAULT_CAPACITY];

    protected transient int constantTableSize;

    protected transient int constantTableThreshold = 
        (int)(CONSTANT_TABLE_DEFAULT_CAPACITY * CONSTANT_TABLE_LOAD_FACTOR);

    private final Map<String, DynamicMethod> methods = new ConcurrentHashMap<String, DynamicMethod>(12, 0.75f, 1);
    
    // ClassProviders return Java class/module (in #defineOrGetClassUnder and
    // #defineOrGetModuleUnder) when class/module is opened using colon syntax. 
    private transient List<ClassProvider> classProviders;

    /** separate path for MetaClass construction
     * 
     */
    protected RubyModule(Ruby runtime, RubyClass metaClass, boolean objectSpace) {
        super(runtime, metaClass, objectSpace);
        id = runtime.allocModuleId();
        // if (parent == null) parent = runtime.getObject();
        setFlag(USER7_F, !isClass());
    }
    
    /** used by MODULE_ALLOCATOR and RubyClass constructors
     * 
     */
    protected RubyModule(Ruby runtime, RubyClass metaClass) {
        this(runtime, metaClass, runtime.isObjectSpaceEnabled());
    }
    
    /** standard path for Module construction
     * 
     */
    protected RubyModule(Ruby runtime) {
        this(runtime, runtime.getModule());
    }

    public boolean needsImplementer() {
        return getFlag(USER7_F);
    }
    
    /** rb_module_new
     * 
     */
    public static RubyModule newModule(Ruby runtime) {
        return new RubyModule(runtime);
    }
    
    /** rb_module_new/rb_define_module_id/rb_name_class/rb_set_class_path
     * 
     */
    public static RubyModule newModule(Ruby runtime, String name, RubyModule parent, boolean setParent) {
        RubyModule module = newModule(runtime);
        module.setBaseName(name);
        if (setParent) module.setParent(parent);
        parent.setConstant(name, module);
        return module;
    }
    
    // synchronized method per JRUBY-1173 (unsafe Double-Checked Locking)
    // FIXME: synchronization is still wrong in CP code
    public synchronized void addClassProvider(ClassProvider provider) {
        if (classProviders == null) {
            List<ClassProvider> cp = Collections.synchronizedList(new ArrayList<ClassProvider>());
            cp.add(provider);
            classProviders = cp;
        } else {
            synchronized(classProviders) {
                if (!classProviders.contains(provider)) {
                    classProviders.add(provider);
                }
            }
        }
    }

    public void removeClassProvider(ClassProvider provider) {
        if (classProviders != null) {
            classProviders.remove(provider);
        }
    }

    private RubyClass searchProvidersForClass(String name, RubyClass superClazz) {
        if (classProviders != null) {
            synchronized(classProviders) {
                RubyClass clazz;
                for (ClassProvider classProvider: classProviders) {
                    if ((clazz = classProvider.defineClassUnder(this, name, superClazz)) != null) {
                        return clazz;
                    }
                }
            }
        }
        return null;
    }

    private RubyModule searchProvidersForModule(String name) {
        if (classProviders != null) {
            synchronized(classProviders) {
                RubyModule module;
                for (ClassProvider classProvider: classProviders) {
                    if ((module = classProvider.defineModuleUnder(this, name)) != null) {
                        return module;
                    }
                }
            }
        }
        return null;
    }

    /** Getter for property superClass.
     * @return Value of property superClass.
     */
    public RubyClass getSuperClass() {
        return superClass;
    }

    protected void setSuperClass(RubyClass superClass) {
        this.superClass = superClass;
    }

    public RubyModule getParent() {
        return parent;
    }

    public void setParent(RubyModule parent) {
        this.parent = parent;
    }

    public Map<String, DynamicMethod> getMethods() {
        return methods;
    }
    

    // note that addMethod now does its own put, so any change made to
    // functionality here should be made there as well 
    private void putMethod(String name, DynamicMethod method) {
        getMethods().put(name, method);
    }

    /**
     * Is this module one that in an included one (e.g. an IncludedModuleWrapper). 
     */
    public boolean isIncluded() {
        return false;
    }

    public RubyModule getNonIncludedClass() {
        return this;
    }

    public String getBaseName() {
        return classId;
    }

    public void setBaseName(String name) {
        classId = name;
    }
    
    private volatile String bareName;
    private volatile String fullName;

    /**
     * Generate a fully-qualified class name or a #-style name for anonymous and singleton classes.
     * 
     * Ruby C equivalent = "classname"
     * 
     * @return The generated class name
     */
    public String getName() {
        if (fullName == null) {
            fullName = calculateFullName();
        }
        return fullName;
    }

    private String calculateFullName() {
        if (getBaseName() == null) {
            if (bareName == null) {
                if (isClass()) {
                    bareName = "#<" + "Class" + ":01x" + Integer.toHexString(System.identityHashCode(this)) + ">";
                } else {
                    bareName = "#<" + "Module" + ":01x" + Integer.toHexString(System.identityHashCode(this)) + ">";
                }
            }

            return bareName;
        }

        String result = getBaseName();
        RubyClass objectClass = getRuntime().getObject();

        for (RubyModule p = this.getParent(); p != null && p != objectClass; p = p.getParent()) {
            String pName = p.getBaseName();
            // This is needed when the enclosing class or module is a singleton.
            // In that case, we generated a name such as null::Foo, which broke 
            // Marshalling, among others. The correct thing to do in this situation 
            // is to insert the generate the name of form #<Class:01xasdfasd> if 
            // it's a singleton module/class, which this code accomplishes.
            if(pName == null) {
                pName = p.getName();
            }
            result = pName + "::" + result;
        }

        return result;
    }

    /**
     * Create a wrapper to use for including the specified module into this one.
     * 
     * Ruby C equivalent = "include_class_new"
     * 
     * @return The module wrapper
     */
    public IncludedModuleWrapper newIncludeClass(RubyClass superClazz) {
        IncludedModuleWrapper includedModule = new IncludedModuleWrapper(getRuntime(), superClazz, this);

        // include its parent (and in turn that module's parents)
        if (getSuperClass() != null) {
            includedModule.includeModule(getSuperClass());
        }

        return includedModule;
    }
    /**
     * Finds a class that is within the current module (or class).
     * 
     * @param name to be found in this module (or class)
     * @return the class or null if no such class
     */
    public RubyClass getClass(String name) {
        IRubyObject module;
        if ((module = getConstantAt(name)) instanceof RubyClass) {
            return (RubyClass)module;
        }
        return null;
    }

    public RubyClass fastGetClass(String internedName) {
        IRubyObject module;
        if ((module = fastGetConstantAt(internedName)) instanceof RubyClass) {
            return (RubyClass)module;
        }
        return null;
    }

    /**
     * Include a new module in this module or class.
     * 
     * @param arg The module to include
     */
    public synchronized void includeModule(IRubyObject arg) {
        assert arg != null;

        testFrozen("module");
        if (!isTaint()) {
            getRuntime().secure(4);
        }

        if (!(arg instanceof RubyModule)) {
            throw getRuntime().newTypeError("Wrong argument type " + arg.getMetaClass().getName() +
                    " (expected Module).");
        }

        RubyModule module = (RubyModule) arg;

        // Make sure the module we include does not already exist
        if (isSame(module)) {
            return;
        }

        infectBy(module);

        doIncludeModule(module);
    }

    public void defineMethod(String name, Callback method) {
        Visibility visibility = name.equals("initialize") ?
                PRIVATE : PUBLIC;
        addMethod(name, new FullFunctionCallbackMethod(this, method, visibility));
    }
    
    public void defineAnnotatedMethod(Class clazz, String name) {
        // FIXME: This is probably not very efficient, since it loads all methods for each call
        boolean foundMethod = false;
        for (Method method : clazz.getDeclaredMethods()) {
            if (method.getName().equals(name) && defineAnnotatedMethod(method, MethodFactory.createFactory(getRuntime().getJRubyClassLoader()))) {
                foundMethod = true;
            }
        }

        if (!foundMethod) {
            throw new RuntimeException("No JRubyMethod present for method " + name + "on class " + clazz.getName());
        }
    }
    
    public void defineAnnotatedConstants(Class clazz) {
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field field : declaredFields) {
            if(Modifier.isStatic(field.getModifiers())) {
                defineAnnotatedConstant(field);
            }
        }
    }

    public boolean defineAnnotatedConstant(Field field) {
        JRubyConstant jrubyConstant = field.getAnnotation(JRubyConstant.class);

        if (jrubyConstant == null) return false;

        String[] names = jrubyConstant.value();
        if(names.length == 0) {
            names = new String[]{field.getName()};
        }

        Class tp = field.getType();
        IRubyObject realVal;

        try {
            if(tp == Integer.class || tp == Integer.TYPE || tp == Short.class || tp == Short.TYPE || tp == Byte.class || tp == Byte.TYPE) {
                realVal = RubyNumeric.int2fix(getRuntime(), field.getInt(null));
            } else if(tp == Boolean.class || tp == Boolean.TYPE) {
                realVal = field.getBoolean(null) ? getRuntime().getTrue() : getRuntime().getFalse();
            } else {
                realVal = getRuntime().getNil();
            }
        } catch(Exception e) {
            realVal = getRuntime().getNil();
        }

        
        for(String name : names) {
            this.fastSetConstant(name, realVal);
        }

        return true;
    }

    public void defineAnnotatedMethods(Class clazz) {
        defineAnnotatedMethodsIndividually(clazz);
    }
    
    public static class MethodClumper {
        Map<String, List<JavaMethodDescriptor>> annotatedMethods = new HashMap<String, List<JavaMethodDescriptor>>();
        Map<String, List<JavaMethodDescriptor>> staticAnnotatedMethods = new HashMap<String, List<JavaMethodDescriptor>>();
        Map<String, List<JavaMethodDescriptor>> annotatedMethods1_8 = new HashMap<String, List<JavaMethodDescriptor>>();
        Map<String, List<JavaMethodDescriptor>> staticAnnotatedMethods1_8 = new HashMap<String, List<JavaMethodDescriptor>>();
        Map<String, List<JavaMethodDescriptor>> annotatedMethods1_9 = new HashMap<String, List<JavaMethodDescriptor>>();
        Map<String, List<JavaMethodDescriptor>> staticAnnotatedMethods1_9 = new HashMap<String, List<JavaMethodDescriptor>>();
        
        public void clump(Class cls) {
            Method[] declaredMethods = cls.getDeclaredMethods();
            for (Method method: declaredMethods) {
                JRubyMethod anno = method.getAnnotation(JRubyMethod.class);
                if (anno == null) continue;
                
                JavaMethodDescriptor desc = new JavaMethodDescriptor(method);
                
                String name = anno.name().length == 0 ? method.getName() : anno.name()[0];
                
                List<JavaMethodDescriptor> methodDescs;
                Map<String, List<JavaMethodDescriptor>> methodsHash = null;
                if (desc.isStatic) {
                    if (anno.compat() == CompatVersion.RUBY1_8) {
                        methodsHash = staticAnnotatedMethods1_8;
                    } else if (anno.compat() == CompatVersion.RUBY1_9) {
                        methodsHash = staticAnnotatedMethods1_9;
                    } else {
                        methodsHash = staticAnnotatedMethods;
                    }
                } else {
                    if (anno.compat() == CompatVersion.RUBY1_8) {
                        methodsHash = annotatedMethods1_8;
                    } else if (anno.compat() == CompatVersion.RUBY1_9) {
                        methodsHash = annotatedMethods1_9;
                    } else {
                        methodsHash = annotatedMethods;
                    }
                }
                
                methodDescs = methodsHash.get(name);
                if (methodDescs == null) {
                    methodDescs = new ArrayList<JavaMethodDescriptor>();
                    methodsHash.put(name, methodDescs);
                }
                
                methodDescs.add(desc);
            }
        }

        public Map<String, List<JavaMethodDescriptor>> getAnnotatedMethods() {
            return annotatedMethods;
        }

        public Map<String, List<JavaMethodDescriptor>> getAnnotatedMethods1_8() {
            return annotatedMethods1_8;
        }

        public Map<String, List<JavaMethodDescriptor>> getAnnotatedMethods1_9() {
            return annotatedMethods1_9;
        }

        public Map<String, List<JavaMethodDescriptor>> getStaticAnnotatedMethods() {
            return staticAnnotatedMethods;
        }

        public Map<String, List<JavaMethodDescriptor>> getStaticAnnotatedMethods1_8() {
            return staticAnnotatedMethods1_8;
        }

        public Map<String, List<JavaMethodDescriptor>> getStaticAnnotatedMethods1_9() {
            return staticAnnotatedMethods1_9;
        }
    }
    
    public void defineAnnotatedMethodsIndividually(Class clazz) {
        String x = clazz.getSimpleName();
        TypePopulator populator = null;
        
        if (RubyInstanceConfig.FULL_TRACE_ENABLED) {
            // we need full traces, use default (slow) populator
            if (DEBUG) System.out.println("trace mode, using default populator");
            populator = TypePopulator.DEFAULT;
        } else {
            try {
                String qualifiedName = "org.jruby.gen." + clazz.getCanonicalName().replace('.', '$');

                if (DEBUG) System.out.println("looking for " + qualifiedName + "$Populator");

                Class populatorClass = Class.forName(qualifiedName + "$Populator");
                populator = (TypePopulator)populatorClass.newInstance();
            } catch (Throwable t) {
                if (DEBUG) System.out.println("Could not find it, using default populator");
                populator = TypePopulator.DEFAULT;
            }
        }
        
        populator.populate(this, clazz);
    }

    @Deprecated
    private void defineAnnotatedMethodsIndexed(Class clazz) {
        MethodFactory methodFactory = MethodFactory.createFactory(getRuntime().getJRubyClassLoader());
        methodFactory.defineIndexedAnnotatedMethods(this, clazz, methodDefiningCallback);
    }
    
    private static MethodFactory.MethodDefiningCallback methodDefiningCallback = new MethodFactory.MethodDefiningCallback() {
        public void define(RubyModule module, JavaMethodDescriptor desc, DynamicMethod dynamicMethod) {
            JRubyMethod jrubyMethod = desc.anno;
            if (jrubyMethod.frame()) {
                for (String name : jrubyMethod.name()) {
                    ASTInspector.FRAME_AWARE_METHODS.add(name);
                }
            }
            if(jrubyMethod.compat() == CompatVersion.BOTH ||
                    module.getRuntime().getInstanceConfig().getCompatVersion() == jrubyMethod.compat()) {
                RubyModule singletonClass;

                if (jrubyMethod.meta()) {
                    singletonClass = module.getSingletonClass();
                    dynamicMethod.setImplementationClass(singletonClass);

                    String baseName;
                    if (jrubyMethod.name().length == 0) {
                        baseName = desc.name;
                        singletonClass.addMethod(baseName, dynamicMethod);
                    } else {
                        baseName = jrubyMethod.name()[0];
                        for (String name : jrubyMethod.name()) {
                            singletonClass.addMethod(name, dynamicMethod);
                        }
                    }

                    if (jrubyMethod.alias().length > 0) {
                        for (String alias : jrubyMethod.alias()) {
                            singletonClass.defineAlias(alias, baseName);
                        }
                    }
                } else {
                    String baseName;
                    if (jrubyMethod.name().length == 0) {
                        baseName = desc.name;
                        module.addMethod(baseName, dynamicMethod);
                    } else {
                        baseName = jrubyMethod.name()[0];
                        for (String name : jrubyMethod.name()) {
                            module.addMethod(name, dynamicMethod);
                        }
                    }

                    if (jrubyMethod.alias().length > 0) {
                        for (String alias : jrubyMethod.alias()) {
                            module.defineAlias(alias, baseName);
                        }
                    }

                    if (jrubyMethod.module()) {
                        singletonClass = module.getSingletonClass();
                        // module/singleton methods are all defined public
                        DynamicMethod moduleMethod = dynamicMethod.dup();
                        moduleMethod.setVisibility(PUBLIC);

                        if (jrubyMethod.name().length == 0) {
                            baseName = desc.name;
                            singletonClass.addMethod(desc.name, moduleMethod);
                        } else {
                            baseName = jrubyMethod.name()[0];
                            for (String name : jrubyMethod.name()) {
                                singletonClass.addMethod(name, moduleMethod);
                            }
                        }

                        if (jrubyMethod.alias().length > 0) {
                            for (String alias : jrubyMethod.alias()) {
                                singletonClass.defineAlias(alias, baseName);
                            }
                        }
                    }
                }
            }
        }
    };
    
    public boolean defineAnnotatedMethod(String name, List<JavaMethodDescriptor> methods, MethodFactory methodFactory) {
        JavaMethodDescriptor desc = methods.get(0);
        if (methods.size() == 1) {
            return defineAnnotatedMethod(desc, methodFactory);
        } else {
            DynamicMethod dynamicMethod = methodFactory.getAnnotatedMethod(this, methods);
            methodDefiningCallback.define(this, desc, dynamicMethod);
            
            return true;
        }
    }
    
    public boolean defineAnnotatedMethod(Method method, MethodFactory methodFactory) { 
        JRubyMethod jrubyMethod = method.getAnnotation(JRubyMethod.class);

        if (jrubyMethod == null) return false;

            if(jrubyMethod.compat() == CompatVersion.BOTH ||
                    getRuntime().getInstanceConfig().getCompatVersion() == jrubyMethod.compat()) {
            JavaMethodDescriptor desc = new JavaMethodDescriptor(method);
            DynamicMethod dynamicMethod = methodFactory.getAnnotatedMethod(this, desc);
            methodDefiningCallback.define(this, desc, dynamicMethod);

            return true;
        }
        return false;
    }
    
    public boolean defineAnnotatedMethod(JavaMethodDescriptor desc, MethodFactory methodFactory) { 
        JRubyMethod jrubyMethod = desc.anno;

        if (jrubyMethod == null) return false;

            if(jrubyMethod.compat() == CompatVersion.BOTH ||
                    getRuntime().getInstanceConfig().getCompatVersion() == jrubyMethod.compat()) {
            DynamicMethod dynamicMethod = methodFactory.getAnnotatedMethod(this, desc);
            methodDefiningCallback.define(this, desc, dynamicMethod);

            return true;
        }
        return false;
    }

    public void defineFastMethod(String name, Callback method) {
        Visibility visibility = name.equals("initialize") ?
                PRIVATE : PUBLIC;
        addMethod(name, new SimpleCallbackMethod(this, method, visibility));
    }

    public void defineFastMethod(String name, Callback method, Visibility visibility) {
        addMethod(name, new SimpleCallbackMethod(this, method, visibility));
    }

    public void definePrivateMethod(String name, Callback method) {
        addMethod(name, new FullFunctionCallbackMethod(this, method, PRIVATE));
    }

    public void defineFastPrivateMethod(String name, Callback method) {
        addMethod(name, new SimpleCallbackMethod(this, method, PRIVATE));
    }

    public void defineFastProtectedMethod(String name, Callback method) {
        addMethod(name, new SimpleCallbackMethod(this, method, PROTECTED));
    }

    public void undefineMethod(String name) {
        addMethod(name, UndefinedMethod.getInstance());
    }

    /** rb_undef
     *
     */
    public void undef(ThreadContext context, String name) {
        Ruby runtime = context.getRuntime();
        
        if (this == runtime.getObject()) runtime.secure(4);

        if (runtime.getSafeLevel() >= 4 && !isTaint()) {
            throw new SecurityException("Insecure: can't undef");
        }
        testFrozen("module");
        if (name.equals("__id__") || name.equals("__send__")) {
            runtime.getWarnings().warn(ID.UNDEFINING_BAD, "undefining `"+ name +"' may cause serious problem");
        }
        DynamicMethod method = searchMethod(name);
        if (method.isUndefined()) {
            String s0 = " class";
            RubyModule c = this;

            if (c.isSingleton()) {
                IRubyObject obj = ((MetaClass)c).getAttached();

                if (obj != null && obj instanceof RubyModule) {
                    c = (RubyModule) obj;
                    s0 = "";
                }
            } else if (c.isModule()) {
                s0 = " module";
            }

            throw runtime.newNameError("Undefined method " + name + " for" + s0 + " '" + c.getName() + "'", name);
        }
        addMethod(name, UndefinedMethod.getInstance());
        
        if (isSingleton()) {
            IRubyObject singleton = ((MetaClass)this).getAttached(); 
            singleton.callMethod(context, "singleton_method_undefined", runtime.newSymbol(name));
        } else {
            callMethod(context, "method_undefined", runtime.newSymbol(name));
        }
    }
    
    @JRubyMethod(name = "include?", required = 1)
    public IRubyObject include_p(ThreadContext context, IRubyObject arg) {
        if (!arg.isModule()) {
            throw context.getRuntime().newTypeError(arg, context.getRuntime().getModule());
        }
        
        for (RubyModule p = this; p != null; p = p.getSuperClass()) {
            if ((p instanceof IncludedModuleWrapper) && ((IncludedModuleWrapper) p).getNonIncludedClass() == arg) {
                return context.getRuntime().getTrue();
            }
        }
        
        return context.getRuntime().getFalse();
    }

    // TODO: Consider a better way of synchronizing 
    public void addMethod(String name, DynamicMethod method) {
        Ruby runtime = getRuntime();
        
        if (this == runtime.getObject()) runtime.secure(4);

        if (runtime.getSafeLevel() >= 4 && !isTaint()) {
            throw runtime.newSecurityError("Insecure: can't define method");
        }
        testFrozen("class/module");

        // We can safely reference methods here instead of doing getMethods() since if we
        // are adding we are not using a IncludedModuleWrapper.
        synchronized(getMethods()) {
            // If we add a method which already is cached in this class, then we should update the 
            // cachemap so it stays up to date.
            DynamicMethod existingMethod = getMethods().put(name, method);
            if (existingMethod != null) {
                runtime.getCacheMap().remove(existingMethod);
            }
        }
    }

    public void removeMethod(ThreadContext context, String name) {
        Ruby runtime = context.getRuntime();
        
        if (this == runtime.getObject()) runtime.secure(4);

        if (runtime.getSafeLevel() >= 4 && !isTaint()) {
            throw runtime.newSecurityError("Insecure: can't remove method");
        }
        testFrozen("class/module");

        // We can safely reference methods here instead of doing getMethods() since if we
        // are adding we are not using a IncludedModuleWrapper.
        synchronized(getMethods()) {
            DynamicMethod method = (DynamicMethod) getMethods().remove(name);
            if (method == null) {
                throw runtime.newNameError("method '" + name + "' not defined in " + getName(), name);
            }
            
            runtime.getCacheMap().remove(method);
        }
        
        if (isSingleton()) {
            IRubyObject singleton = ((MetaClass)this).getAttached(); 
            singleton.callMethod(context, "singleton_method_removed", runtime.newSymbol(name));
        } else {
            callMethod(context, "method_removed", runtime.newSymbol(name));
        }
    }

    /**
     * Search through this module and supermodules for method definitions. Cache superclass definitions in this class.
     * 
     * @param name The name of the method to search for
     * @return The method, or UndefinedMethod if not found
     */    
    public DynamicMethod searchMethod(String name) {
        DynamicMethod method = getMethods().get(name);

        if (method != null) return method;

        return superClass == null ? UndefinedMethod.getInstance() : superClass.searchMethod(name);
    }

    /**
     * Search through this module and supermodules for method definitions. Cache superclass definitions in this class.
     * 
     * @param name The name of the method to search for
     * @return The method, or UndefinedMethod if not found
     */
    public DynamicMethod retrieveMethod(String name) {
        return getMethods().get(name);
    }

    /**
     * Search through this module and supermodules for method definitions. Cache superclass definitions in this class.
     * 
     * @param name The name of the method to search for
     * @return The method, or UndefinedMethod if not found
     */
    public RubyModule findImplementer(RubyModule clazz) {
        for (RubyModule searchModule = this; searchModule != null; searchModule = searchModule.getSuperClass()) {
            if (searchModule.isSame(clazz)) {
                return searchModule;
            }
        }

        return null;
    }

    public void addModuleFunction(String name, DynamicMethod method) {
        addMethod(name, method);
        getSingletonClass().addMethod(name, method);
    }

    /** rb_define_module_function
     *
     */
    public void defineModuleFunction(String name, Callback method) {
        definePrivateMethod(name, method);
        getSingletonClass().defineMethod(name, method);
    }

    /** rb_define_module_function
     *
     */
    public void definePublicModuleFunction(String name, Callback method) {
        defineMethod(name, method);
        getSingletonClass().defineMethod(name, method);
    }

    /** rb_define_module_function
     *
     */
    public void defineFastModuleFunction(String name, Callback method) {
        defineFastPrivateMethod(name, method);
        getSingletonClass().defineFastMethod(name, method);
    }

    /** rb_define_module_function
     *
     */
    public void defineFastPublicModuleFunction(String name, Callback method) {
        defineFastMethod(name, method);
        getSingletonClass().defineFastMethod(name, method);
    }

    /** rb_alias
     *
     */
    public synchronized void defineAlias(String name, String oldName) {
        testFrozen("module");
        if (oldName.equals(name)) {
            return;
        }
        Ruby runtime = getRuntime();
        if (this == runtime.getObject()) {
            runtime.secure(4);
        }
        DynamicMethod method = searchMethod(oldName);
        DynamicMethod oldMethod = searchMethod(name);
        if (method.isUndefined()) {
            if (isModule()) {
                method = runtime.getObject().searchMethod(oldName);
            }

            if (method.isUndefined()) {
                throw runtime.newNameError("undefined method `" + oldName + "' for " +
                        (isModule() ? "module" : "class") + " `" + getName() + "'", oldName);
            }
        }
        CacheMap cacheMap = runtime.getCacheMap();
        cacheMap.remove(method);
        cacheMap.remove(oldMethod);
        if (oldMethod != oldMethod.getRealMethod()) {
            cacheMap.remove(oldMethod.getRealMethod());
        }
        putMethod(name, new AliasMethod(this, method, oldName));
    }

    public synchronized void defineAliases(List<String> aliases, String oldName) {
        testFrozen("module");
        Ruby runtime = getRuntime();
        if (this == runtime.getObject()) {
            runtime.secure(4);
        }
        DynamicMethod method = searchMethod(oldName);
        if (method.isUndefined()) {
            if (isModule()) {
                method = runtime.getObject().searchMethod(oldName);
            }

            if (method.isUndefined()) {
                throw runtime.newNameError("undefined method `" + oldName + "' for " +
                        (isModule() ? "module" : "class") + " `" + getName() + "'", oldName);
            }
        }
        CacheMap cacheMap = runtime.getCacheMap();
        cacheMap.remove(method);
        for (String name: aliases) {
            if (oldName.equals(name)) continue;
            DynamicMethod oldMethod = searchMethod(name);
            cacheMap.remove(oldMethod);
            if (oldMethod != oldMethod.getRealMethod()) {
                cacheMap.remove(oldMethod.getRealMethod());
            }
            putMethod(name, new AliasMethod(this, method, oldName));
        }
    }

    /** this method should be used only by interpreter or compiler 
     * 
     */
    public RubyClass defineOrGetClassUnder(String name, RubyClass superClazz) {
        // This method is intended only for defining new classes in Ruby code,
        // so it uses the allocator of the specified superclass or default to
        // the Object allocator. It should NOT be used to define classes that require a native allocator.

        Ruby runtime = getRuntime();
        IRubyObject classObj = getConstantAt(name);
        RubyClass clazz;

        if (classObj != null) {
            if (!(classObj instanceof RubyClass)) throw runtime.newTypeError(name + " is not a class");
            clazz = (RubyClass)classObj;

            if (superClazz != null) {
                RubyClass tmp = clazz.getSuperClass();
                while (tmp != null && tmp.isIncluded()) tmp = tmp.getSuperClass(); // need to skip IncludedModuleWrappers
                if (tmp != null) tmp = tmp.getRealClass();
                if (tmp != superClazz) throw runtime.newTypeError("superclass mismatch for class " + name);
                // superClazz = null;
            }

            if (runtime.getSafeLevel() >= 4) throw runtime.newTypeError("extending class prohibited");
        } else if (classProviders != null && (clazz = searchProvidersForClass(name, superClazz)) != null) {
            // reopen a java class
        } else {
            if (superClazz == null) superClazz = runtime.getObject();
            clazz = RubyClass.newClass(runtime, superClazz, name, superClazz.getAllocator(), this, true);
        }

        return clazz;
    }

    /** this method should be used only by interpreter or compiler 
     * 
     */
    public RubyModule defineOrGetModuleUnder(String name) {
        // This method is intended only for defining new modules in Ruby code
        Ruby runtime = getRuntime();
        IRubyObject moduleObj = getConstantAt(name);
        RubyModule module;
        if (moduleObj != null) {
            if (!moduleObj.isModule()) throw runtime.newTypeError(name + " is not a module");
            if (runtime.getSafeLevel() >= 4) throw runtime.newSecurityError("extending module prohibited");
            module = (RubyModule)moduleObj;
        } else if (classProviders != null && (module = searchProvidersForModule(name)) != null) {
            // reopen a java module
        } else {
            module = RubyModule.newModule(runtime, name, this, true); 
        }
        return module;
    }

    /** rb_define_class_under
     *  this method should be used only as an API to define/open nested classes 
     */
    public RubyClass defineClassUnder(String name, RubyClass superClass, ObjectAllocator allocator) {
        return getRuntime().defineClassUnder(name, superClass, allocator, this);
    }

    /** rb_define_module_under
     *  this method should be used only as an API to define/open nested module
     */
    public RubyModule defineModuleUnder(String name) {
        return getRuntime().defineModuleUnder(name, this);
    }

    // FIXME: create AttrReaderMethod, AttrWriterMethod, for faster attr access
    private void addAccessor(ThreadContext context, String internedName, boolean readable, boolean writeable) {
        assert internedName == internedName.intern() : internedName + " is not interned";

        final Ruby runtime = context.getRuntime();

        // Check the visibility of the previous frame, which will be the frame in which the class is being eval'ed
        Visibility attributeScope = context.getCurrentVisibility();
        if (attributeScope == PRIVATE) {
            //FIXME warning
        } else if (attributeScope == MODULE_FUNCTION) {
            attributeScope = PRIVATE;
            // FIXME warning
        }
        final String variableName = ("@" + internedName).intern();
        if (readable) {
            // FIXME: should visibility be set to current visibility?
            addMethod(internedName, new JavaMethod(this, PUBLIC) {
                public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args, Block block) {
                    if (args.length != 0) Arity.raiseArgumentError(runtime, args.length, 0, 0);

                    IRubyObject variable = self.getInstanceVariables().fastGetInstanceVariable(variableName);

                    return variable == null ? runtime.getNil() : variable;
                }

                @Override
                public Arity getArity() {
                    return Arity.noArguments();
                }
            });
            callMethod(context, "method_added", runtime.fastNewSymbol(internedName));
        }
        if (writeable) {
            internedName = (internedName + "=").intern();
            // FIXME: should visibility be set to current visibility?
            addMethod(internedName, new JavaMethod(this, PUBLIC) {
                public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args, Block block) {
                    // ENEBO: Can anyone get args to be anything but length 1?
                    if (args.length != 1) Arity.raiseArgumentError(runtime, args.length, 1, 1);

                    return self.getInstanceVariables().fastSetInstanceVariable(variableName, args[0]);
                }

                @Override
                public Arity getArity() {
                    return Arity.singleArgument();
                }
            });
            callMethod(context, "method_added", runtime.fastNewSymbol(internedName));
        }
    }

    /** set_method_visibility
     *
     */
    public void setMethodVisibility(IRubyObject[] methods, Visibility visibility) {
        if (getRuntime().getSafeLevel() >= 4 && !isTaint()) {
            throw getRuntime().newSecurityError("Insecure: can't change method visibility");
        }

        for (int i = 0; i < methods.length; i++) {
            exportMethod(methods[i].asJavaString(), visibility);
        }
    }

    /** rb_export_method
     *
     */
    public void exportMethod(String name, Visibility visibility) {
        if (this == getRuntime().getObject()) {
            getRuntime().secure(4);
        }

        DynamicMethod method = searchMethod(name);

        if (method.isUndefined()) {
            throw getRuntime().newNameError("undefined method '" + name + "' for " +
                                (isModule() ? "module" : "class") + " '" + getName() + "'", name);
        }

        if (method.getVisibility() != visibility) {
            if (this == method.getImplementationClass()) {
                method.setVisibility(visibility);
            } else {
                // FIXME: Why was this using a FullFunctionCallbackMethod before that did callSuper?
                addMethod(name, new WrapperMethod(this, method, visibility));
            }
        }
    }

    /**
     * MRI: rb_method_boundp
     *
     */
    public boolean isMethodBound(String name, boolean checkVisibility) {
        DynamicMethod method = searchMethod(name);
        if (!method.isUndefined()) {
            return !(checkVisibility && method.getVisibility() == PRIVATE);
        }
        return false;
    }

    public IRubyObject newMethod(IRubyObject receiver, String name, boolean bound) {
        DynamicMethod method = searchMethod(name);
        if (method.isUndefined()) {
            throw getRuntime().newNameError("undefined method `" + name +
                "' for class `" + this.getName() + "'", name);
        }

        RubyModule implementationModule = method.getImplementationClass();
        RubyModule originModule = this;
        while (originModule != implementationModule && originModule.isSingleton()) {
            originModule = ((MetaClass)originModule).getRealClass();
        }

        RubyMethod newMethod = null;
        if (bound) {
            newMethod = RubyMethod.newMethod(implementationModule, name, originModule, name, method, receiver);
        } else {
            newMethod = RubyUnboundMethod.newUnboundMethod(implementationModule, name, originModule, name, method);
        }
        newMethod.infectBy(this);

        return newMethod;
    }

    @JRubyMethod(name = "define_method", frame = true, visibility = PRIVATE, reads = VISIBILITY)
    public IRubyObject define_method(ThreadContext context, IRubyObject arg0, Block block) {
        Ruby runtime = context.getRuntime();
        String name = arg0.asJavaString().intern();
        DynamicMethod newMethod = null;
        Visibility visibility = context.getCurrentVisibility();

        if (visibility == MODULE_FUNCTION) visibility = PRIVATE;
        RubyProc proc = runtime.newProc(Block.Type.LAMBDA, block);

        // a normal block passed to define_method changes to do arity checking; make it a lambda
        proc.getBlock().type = Block.Type.LAMBDA;
        
        newMethod = createProcMethod(name, visibility, proc);
        
        RuntimeHelpers.addInstanceMethod(this, name, newMethod, context.getPreviousVisibility(), context, runtime);

        return proc;
    }
    
    @JRubyMethod(name = "define_method", frame = true, visibility = PRIVATE, reads = VISIBILITY)
    public IRubyObject define_method(ThreadContext context, IRubyObject arg0, IRubyObject arg1, Block block) {
        Ruby runtime = context.getRuntime();
        IRubyObject body;
        String name = arg0.asJavaString().intern();
        DynamicMethod newMethod = null;
        Visibility visibility = context.getCurrentVisibility();

        if (visibility == MODULE_FUNCTION) visibility = PRIVATE;
        if (runtime.getProc().isInstance(arg1)) {
            // double-testing args.length here, but it avoids duplicating the proc-setup code in two places
            RubyProc proc = (RubyProc)arg1;
            body = proc;

            newMethod = createProcMethod(name, visibility, proc);
        } else if (runtime.getMethod().isInstance(arg1)) {
            RubyMethod method = (RubyMethod)arg1;
            body = method;

            newMethod = new MethodMethod(this, method.unbind(null), visibility);
        } else {
            throw runtime.newTypeError("wrong argument type " + arg1.getType().getName() + " (expected Proc/Method)");
        }
        
        RuntimeHelpers.addInstanceMethod(this, name, newMethod, context.getPreviousVisibility(), context, runtime);

        return body;
    }
    @Deprecated
    public IRubyObject define_method(ThreadContext context, IRubyObject[] args, Block block) {
        switch (args.length) {
        case 1:
            return define_method(context, args[0], block);
        case 2:
            return define_method(context, args[0], args[1], block);
        default:
            throw context.getRuntime().newArgumentError("wrong # of arguments(" + args.length + " for 2)");
        }
    }
    
    private DynamicMethod createProcMethod(String name, Visibility visibility, RubyProc proc) {
        Block block = proc.getBlock();
        block.getBinding().getFrame().setKlazz(this);
        block.getBinding().getFrame().setName(name);
        
        StaticScope scope = block.getBody().getStaticScope();

        // for zsupers in define_method (blech!) we tell the proc scope to act as the "argument" scope
        scope.setArgumentScope(true);

        Arity arity = block.arity();
        // just using required is broken...but no more broken than before zsuper refactoring
        scope.setRequiredArgs(arity.required());

        if(!arity.isFixed()) {
            scope.setRestArg(arity.required());
        }

        return new ProcMethod(this, proc, visibility);
    }

    @Deprecated
    public IRubyObject executeUnder(ThreadContext context, Callback method, IRubyObject[] args, Block block) {
        context.preExecuteUnder(this, block);
        try {
            return method.execute(this, args, block);
        } finally {
            context.postExecuteUnder();
        }
    }

    @JRubyMethod(name = "name")
    public RubyString name() {
        return getRuntime().newString(getBaseName() == null ? "" : getName());
    }

    protected IRubyObject cloneMethods(RubyModule clone) {
        RubyModule realType = this.getNonIncludedClass();
        for (Map.Entry<String, DynamicMethod> entry : getMethods().entrySet()) {
            DynamicMethod method = entry.getValue();
            // Do not clone cached methods
            // FIXME: MRI copies all methods here
            if (method.getImplementationClass() == realType || method instanceof UndefinedMethod) {
                
                // A cloned method now belongs to a new class.  Set it.
                // TODO: Make DynamicMethod immutable
                DynamicMethod clonedMethod = method.dup();
                clonedMethod.setImplementationClass(clone);
                clone.putMethod(entry.getKey(), clonedMethod);
            }
        }

        return clone;
    }

    /** rb_mod_init_copy
     * 
     */
    @JRubyMethod(name = "initialize_copy", required = 1)
    @Override
    public IRubyObject initialize_copy(IRubyObject original) {
        super.initialize_copy(original);

        RubyModule originalModule = (RubyModule)original;

        if (!getMetaClass().isSingleton()) setMetaClass(originalModule.getSingletonClassClone());
        setSuperClass(originalModule.getSuperClass());

        if (originalModule.hasVariables()){
            syncVariables(originalModule.getVariableList());
        }

        originalModule.cloneMethods(this);

        return this;
    }

    /** rb_mod_included_modules
     *
     */
    @JRubyMethod(name = "included_modules")
    public RubyArray included_modules(ThreadContext context) {
        RubyArray ary = context.getRuntime().newArray();

        for (RubyModule p = getSuperClass(); p != null; p = p.getSuperClass()) {
            if (p.isIncluded()) {
                ary.append(p.getNonIncludedClass());
            }
        }

        return ary;
    }

    /** rb_mod_ancestors
     *
     */
    @JRubyMethod(name = "ancestors")
    public RubyArray ancestors(ThreadContext context) {
        return context.getRuntime().newArray(getAncestorList());
    }
    
    @Deprecated
    public RubyArray ancestors() {
        return getRuntime().newArray(getAncestorList());
    }

    public List<IRubyObject> getAncestorList() {
        ArrayList<IRubyObject> list = new ArrayList<IRubyObject>();

        for (RubyModule p = this; p != null; p = p.getSuperClass()) {
            if(!p.isSingleton()) {
                list.add(p.getNonIncludedClass());
            }
        }

        return list;
    }

    public boolean hasModuleInHierarchy(RubyModule type) {
        // XXX: This check previously used callMethod("==") to check for equality between classes
        // when scanning the hierarchy. However the == check may be safe; we should only ever have
        // one instance bound to a given type/constant. If it's found to be unsafe, examine ways
        // to avoid the == call.
        for (RubyModule p = this; p != null; p = p.getSuperClass()) {
            if (p.getNonIncludedClass() == type) return true;
        }

        return false;
    }

    @Override
    public int hashCode() {
        return id;
    }

    @JRubyMethod(name = "hash")
    @Override
    public RubyFixnum hash() {
        return getRuntime().newFixnum(id);
    }

    /** rb_mod_to_s
     *
     */
    @JRubyMethod(name = "to_s")
    @Override
    public IRubyObject to_s() {
        if(isSingleton()){            
            IRubyObject attached = ((MetaClass)this).getAttached();
            StringBuilder buffer = new StringBuilder("#<Class:");
            if (attached != null) { // FIXME: figure out why we get null sometimes
                if(attached instanceof RubyClass || attached instanceof RubyModule){
                    buffer.append(attached.inspect());
                }else{
                    buffer.append(attached.anyToString());
                }
            }
            buffer.append(">");
            return getRuntime().newString(buffer.toString());
        }
        return getRuntime().newString(getName());
    }

    /** rb_mod_eqq
     *
     */
    @JRubyMethod(name = "===", required = 1)
    @Override
    public RubyBoolean op_eqq(ThreadContext context, IRubyObject obj) {
        return context.getRuntime().newBoolean(isInstance(obj));
    }

    @JRubyMethod(name = "==", required = 1)
    @Override
    public IRubyObject op_equal(ThreadContext context, IRubyObject other) {
        return super.op_equal(context, other);
    }

    /** rb_mod_freeze
     *
     */
    @JRubyMethod(name = "freeze")
    @Override
    public IRubyObject freeze(ThreadContext context) {
        to_s();
        return super.freeze(context);
    }

    /** rb_mod_le
    *
    */
    @JRubyMethod(name = "<=", required = 1)
   public IRubyObject op_le(IRubyObject obj) {
        if (!(obj instanceof RubyModule)) {
            throw getRuntime().newTypeError("compared with non class/module");
        }

        if (isKindOfModule((RubyModule) obj)) {
            return getRuntime().getTrue();
        } else if (((RubyModule) obj).isKindOfModule(this)) {
            return getRuntime().getFalse();
        }

        return getRuntime().getNil();
    }

    /** rb_mod_lt
    *
    */
    @JRubyMethod(name = "<", required = 1)
   public IRubyObject op_lt(IRubyObject obj) {
        return obj == this ? getRuntime().getFalse() : op_le(obj);
    }

    /** rb_mod_ge
    *
    */
    @JRubyMethod(name = ">=", required = 1)
   public IRubyObject op_ge(IRubyObject obj) {
        if (!(obj instanceof RubyModule)) {
            throw getRuntime().newTypeError("compared with non class/module");
        }

        return ((RubyModule) obj).op_le(this);
    }

    /** rb_mod_gt
    *
    */
    @JRubyMethod(name = ">", required = 1)
   public IRubyObject op_gt(IRubyObject obj) {
        return this == obj ? getRuntime().getFalse() : op_ge(obj);
    }

    /** rb_mod_cmp
    *
    */
    @JRubyMethod(name = "<=>", required = 1)
   public IRubyObject op_cmp(IRubyObject obj) {
        if (this == obj) return getRuntime().newFixnum(0);
        if (!(obj instanceof RubyModule)) return getRuntime().getNil();

        RubyModule module = (RubyModule) obj;

        if (module.isKindOfModule(this)) {
            return getRuntime().newFixnum(1);
        } else if (this.isKindOfModule(module)) {
            return getRuntime().newFixnum(-1);
        }

        return getRuntime().getNil();
    }

    public boolean isKindOfModule(RubyModule type) {
        for (RubyModule p = this; p != null; p = p.getSuperClass()) {
            if (p.isSame(type)) {
                return true;
            }
        }

        return false;
    }

    protected boolean isSame(RubyModule module) {
        return this == module;
    }

    /** rb_mod_initialize
     *
     */
    @JRubyMethod(name = "initialize", frame = true, visibility = PRIVATE)
    public IRubyObject initialize(Block block) {
        if (block.isGiven()) {
            // class and module bodies default to public, so make the block's visibility public. JRUBY-1185.
            block.getBinding().setVisibility(PUBLIC);
            block.yield(getRuntime().getCurrentContext(), this, this, this, false);
        }

        return getRuntime().getNil();
    }
    
    public void addReadWriteAttribute(ThreadContext context, String name) {
        addAccessor(context, name.intern(), true, true);
    }
    
    public void addReadAttribute(ThreadContext context, String name) {
        addAccessor(context, name.intern(), true, false);
    }
    
    public void addWriteAttribute(ThreadContext context, String name) {
        addAccessor(context, name.intern(), false, true);
    }

    /** rb_mod_attr
     *
     */
    @JRubyMethod(name = "attr", required = 1, optional = 1, visibility = PRIVATE, reads = VISIBILITY)
    public IRubyObject attr(ThreadContext context, IRubyObject[] args) {
        boolean writeable = args.length > 1 ? args[1].isTrue() : false;

        addAccessor(context, args[0].asJavaString().intern(), true, writeable);

        return getRuntime().getNil();
    }

    /**
     * @deprecated
     */
    public IRubyObject attr_reader(IRubyObject[] args) {
        return attr_reader(getRuntime().getCurrentContext(), args);
    }
    
    /** rb_mod_attr_reader
     *
     */
    @JRubyMethod(name = "attr_reader", rest = true, visibility = PRIVATE, reads = VISIBILITY)
    public IRubyObject attr_reader(ThreadContext context, IRubyObject[] args) {
        for (int i = 0; i < args.length; i++) {
            addAccessor(context, args[i].asJavaString().intern(), true, false);
        }

        return context.getRuntime().getNil();
    }

    /** rb_mod_attr_writer
     *
     */
    @JRubyMethod(name = "attr_writer", rest = true, visibility = PRIVATE, reads = VISIBILITY)
    public IRubyObject attr_writer(ThreadContext context, IRubyObject[] args) {
        for (int i = 0; i < args.length; i++) {
            addAccessor(context, args[i].asJavaString().intern(), false, true);
        }

        return context.getRuntime().getNil();
    }

    /**
     * @deprecated
     */
    public IRubyObject attr_accessor(IRubyObject[] args) {
        return attr_accessor(getRuntime().getCurrentContext(), args);
    }

    /** rb_mod_attr_accessor
     *
     */
    @JRubyMethod(name = "attr_accessor", rest = true, visibility = PRIVATE, reads = VISIBILITY)
    public IRubyObject attr_accessor(ThreadContext context, IRubyObject[] args) {
        for (int i = 0; i < args.length; i++) {
            // This is almost always already interned, since it will be called with a symbol in most cases
            // but when created from Java code, we might get an argument that needs to be interned.
            // addAccessor has as a precondition that the string MUST be interned
            addAccessor(context, args[i].asJavaString().intern(), true, true);
        }

        return context.getRuntime().getNil();
    }

    /**
     * Get a list of all instance methods names of the provided visibility unless not is true, then 
     * get all methods which are not the provided 
     * 
     * @param args passed into one of the Ruby instance_method methods
     * @param visibility to find matching instance methods against
     * @param not if true only find methods not matching supplied visibility
     * @return a RubyArray of instance method names
     */
    private RubyArray instance_methods(IRubyObject[] args, final Visibility visibility, boolean not) {
        boolean includeSuper = args.length > 0 ? args[0].isTrue() : true;
        RubyArray ary = getRuntime().newArray();
        Set<String> seen = new HashSet<String>();

        for (RubyModule type = this; type != null; type = type.getSuperClass()) {
            RubyModule realType = type.getNonIncludedClass();
            for (Iterator iter = type.getMethods().entrySet().iterator(); iter.hasNext();) {
                Map.Entry entry = (Map.Entry) iter.next();
                DynamicMethod method = (DynamicMethod) entry.getValue();
                String methodName = (String) entry.getKey();

                if (! seen.contains(methodName)) {
                    seen.add(methodName);
                    
                    if (method.getImplementationClass() == realType &&
                        (!not && method.getVisibility() == visibility || (not && method.getVisibility() != visibility)) &&
                        ! method.isUndefined()) {

                        ary.append(getRuntime().newString(methodName));
                    }
                }
            }

            if (!includeSuper) {
                break;
            }
        }

        return ary;
    }

    @JRubyMethod(name = "instance_methods", optional = 1)
    public RubyArray instance_methods(IRubyObject[] args) {
        return instance_methods(args, PRIVATE, true);
    }

    @JRubyMethod(name = "public_instance_methods", optional = 1)
    public RubyArray public_instance_methods(IRubyObject[] args) {
        return instance_methods(args, PUBLIC, false);
    }

    @JRubyMethod(name = "instance_method", required = 1)
    public IRubyObject instance_method(IRubyObject symbol) {
        return newMethod(null, symbol.asJavaString(), false);
    }

    /** rb_class_protected_instance_methods
     *
     */
    @JRubyMethod(name = "protected_instance_methods", optional = 1)
    public RubyArray protected_instance_methods(IRubyObject[] args) {
        return instance_methods(args, PROTECTED, false);
    }

    /** rb_class_private_instance_methods
     *
     */
    @JRubyMethod(name = "private_instance_methods", optional = 1)
    public RubyArray private_instance_methods(IRubyObject[] args) {
        return instance_methods(args, PRIVATE, false);
    }

    /** rb_mod_append_features
     *
     */
    @JRubyMethod(name = "append_features", required = 1, visibility = PRIVATE)
    public RubyModule append_features(IRubyObject module) {
        if (!(module instanceof RubyModule)) {
            // MRI error message says Class, even though Module is ok 
            throw getRuntime().newTypeError(module,getRuntime().getClassClass());
        }
        ((RubyModule) module).includeModule(this);
        return this;
    }

    /** rb_mod_extend_object
     *
     */
    @JRubyMethod(name = "extend_object", required = 1, visibility = PRIVATE)
    public IRubyObject extend_object(IRubyObject obj) {
        obj.getSingletonClass().includeModule(this);
        return obj;
    }

    /** rb_mod_include
     *
     */
    @JRubyMethod(name = "include", required = 1, rest = true, visibility = PRIVATE)
    public RubyModule include(IRubyObject[] modules) {
        ThreadContext context = getRuntime().getCurrentContext();
        // MRI checks all types first:
        for (int i = modules.length; --i >= 0; ) {
            IRubyObject obj = modules[i];
            if (!obj.isModule()) throw context.getRuntime().newTypeError(obj, context.getRuntime().getModule());
        }
        for (int i = modules.length - 1; i >= 0; i--) {
            modules[i].callMethod(context, "append_features", this);
            modules[i].callMethod(context, "included", this);
        }

        return this;
    }

    @JRubyMethod(name = "included", required = 1)
    public IRubyObject included(ThreadContext context, IRubyObject other) {
        return context.getRuntime().getNil();
    }

    @JRubyMethod(name = "extended", required = 1, frame = true)
    public IRubyObject extended(ThreadContext context, IRubyObject other, Block block) {
        return context.getRuntime().getNil();
    }

    private void setVisibility(ThreadContext context, IRubyObject[] args, Visibility visibility) {
        if (context.getRuntime().getSafeLevel() >= 4 && !isTaint()) {
            throw context.getRuntime().newSecurityError("Insecure: can't change method visibility");
        }

        if (args.length == 0) {
            // Note: we change current frames visibility here because the methods which call
            // this method are all "fast" (e.g. they do not created their own frame).
            context.setCurrentVisibility(visibility);
        } else {
            setMethodVisibility(args, visibility);
        }
    }

    /** rb_mod_public
     *
     */
    @JRubyMethod(name = "public", rest = true, visibility = PRIVATE, writes = VISIBILITY)
    public RubyModule rbPublic(ThreadContext context, IRubyObject[] args) {
        setVisibility(context, args, PUBLIC);
        return this;
    }

    /** rb_mod_protected
     *
     */
    @JRubyMethod(name = "protected", rest = true, visibility = PRIVATE, writes = VISIBILITY)
    public RubyModule rbProtected(ThreadContext context, IRubyObject[] args) {
        setVisibility(context, args, PROTECTED);
        return this;
    }

    /** rb_mod_private
     *
     */
    @JRubyMethod(name = "private", rest = true, visibility = PRIVATE, writes = VISIBILITY)
    public RubyModule rbPrivate(ThreadContext context, IRubyObject[] args) {
        setVisibility(context, args, PRIVATE);
        return this;
    }

    /** rb_mod_modfunc
     *
     */
    @JRubyMethod(name = "module_function", rest = true, visibility = PRIVATE, writes = VISIBILITY)
    public RubyModule module_function(ThreadContext context, IRubyObject[] args) {
        if (context.getRuntime().getSafeLevel() >= 4 && !isTaint()) {
            throw context.getRuntime().newSecurityError("Insecure: can't change method visibility");
        }

        if (args.length == 0) {
            context.setCurrentVisibility(MODULE_FUNCTION);
        } else {
            setMethodVisibility(args, PRIVATE);

            for (int i = 0; i < args.length; i++) {
                String name = args[i].asJavaString().intern();
                DynamicMethod method = searchMethod(name);
                assert !method.isUndefined() : "undefined method '" + name + "'";
                getSingletonClass().addMethod(name, new WrapperMethod(getSingletonClass(), method, PUBLIC));
                callMethod(context, "singleton_method_added", context.getRuntime().fastNewSymbol(name));
            }
        }
        return this;
    }

    @JRubyMethod(name = "method_added", required = 1, visibility = PRIVATE)
    public IRubyObject method_added(ThreadContext context, IRubyObject nothing) {
        return context.getRuntime().getNil();
    }

    @JRubyMethod(name = "method_removed", required = 1, visibility = PRIVATE)
    public IRubyObject method_removed(ThreadContext context, IRubyObject nothing) {
        return context.getRuntime().getNil();
    }

    @JRubyMethod(name = "method_undefined", required = 1, visibility = PRIVATE)
    public IRubyObject method_undefined(ThreadContext context, IRubyObject nothing) {
        return context.getRuntime().getNil();
    }
    
    @JRubyMethod(name = "method_defined?", required = 1)
    public RubyBoolean method_defined_p(ThreadContext context, IRubyObject symbol) {
        return isMethodBound(symbol.asJavaString(), true) ? context.getRuntime().getTrue() : context.getRuntime().getFalse();
    }

    @JRubyMethod(name = "public_method_defined?", required = 1)
    public IRubyObject public_method_defined(ThreadContext context, IRubyObject symbol) {
        DynamicMethod method = searchMethod(symbol.asJavaString());
        
        return context.getRuntime().newBoolean(!method.isUndefined() && method.getVisibility() == PUBLIC);
    }

    @JRubyMethod(name = "protected_method_defined?", required = 1)
    public IRubyObject protected_method_defined(ThreadContext context, IRubyObject symbol) {
        DynamicMethod method = searchMethod(symbol.asJavaString());
	    
        return context.getRuntime().newBoolean(!method.isUndefined() && method.getVisibility() == PROTECTED);
    }
	
    @JRubyMethod(name = "private_method_defined?", required = 1)
    public IRubyObject private_method_defined(ThreadContext context, IRubyObject symbol) {
        DynamicMethod method = searchMethod(symbol.asJavaString());
	    
        return context.getRuntime().newBoolean(!method.isUndefined() && method.getVisibility() == PRIVATE);
    }

    @JRubyMethod(name = "public_class_method", rest = true)
    public RubyModule public_class_method(IRubyObject[] args) {
        getMetaClass().setMethodVisibility(args, PUBLIC);
        return this;
    }

    @JRubyMethod(name = "private_class_method", rest = true)
    public RubyModule private_class_method(IRubyObject[] args) {
        getMetaClass().setMethodVisibility(args, PRIVATE);
        return this;
    }

    @JRubyMethod(name = "alias_method", required = 2, visibility = PRIVATE)
    public RubyModule alias_method(ThreadContext context, IRubyObject newId, IRubyObject oldId) {
        String newName = newId.asJavaString();
        defineAlias(newName, oldId.asJavaString());
        RubySymbol newSym = newId instanceof RubySymbol ? (RubySymbol)newId :
            context.getRuntime().newSymbol(newName);
        if (isSingleton()) {
            ((MetaClass)this).getAttached().callMethod(context, "singleton_method_added", newSym);
        } else {
            callMethod(context, "method_added", newSym);
        }
        return this;
    }

    @JRubyMethod(name = "undef_method", required = 1, rest = true, visibility = PRIVATE)
    public RubyModule undef_method(ThreadContext context, IRubyObject[] args) {
        for (int i=0; i<args.length; i++) {
            undef(context, args[i].asJavaString());
        }
        return this;
    }

    @JRubyMethod(name = {"module_eval", "class_eval"}, frame = true)
    public IRubyObject module_eval(ThreadContext context, Block block) {
        return specificEval(context, this, block);
    }
    @JRubyMethod(name = {"module_eval", "class_eval"}, frame = true)
    public IRubyObject module_eval(ThreadContext context, IRubyObject arg0, Block block) {
        return specificEval(context, this, arg0, block);
    }
    @JRubyMethod(name = {"module_eval", "class_eval"}, frame = true)
    public IRubyObject module_eval(ThreadContext context, IRubyObject arg0, IRubyObject arg1, Block block) {
        return specificEval(context, this, arg0, arg1, block);
    }
    @JRubyMethod(name = {"module_eval", "class_eval"}, frame = true)
    public IRubyObject module_eval(ThreadContext context, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Block block) {
        return specificEval(context, this, arg0, arg1, arg2, block);
    }
    @Deprecated
    public IRubyObject module_eval(ThreadContext context, IRubyObject[] args, Block block) {
        return specificEval(context, this, args, block);
    }

    @JRubyMethod(name = "remove_method", required = 1, rest = true, visibility = PRIVATE)
    public RubyModule remove_method(ThreadContext context, IRubyObject[] args) {
        for(int i=0;i<args.length;i++) {
            removeMethod(context, args[i].asJavaString());
        }
        return this;
    }

    public static void marshalTo(RubyModule module, MarshalStream output) throws java.io.IOException {
        output.registerLinkTarget(module);
        output.writeString(MarshalStream.getPathFromClass(module));
    }

    public static RubyModule unmarshalFrom(UnmarshalStream input) throws java.io.IOException {
        String name = RubyString.byteListToString(input.unmarshalString());
        RubyModule result = UnmarshalStream.getModuleFromPath(input.getRuntime(), name);
        input.registerLinkTarget(result);
        return result;
    }

    /* Module class methods */
    
    /** 
     * Return an array of nested modules or classes.
     */
    @JRubyMethod(name = "nesting", frame = true, meta = true)
    public static RubyArray nesting(ThreadContext context, IRubyObject recv, Block block) {
        Ruby runtime = context.getRuntime();
        RubyModule object = runtime.getObject();
        StaticScope scope = context.getCurrentScope().getStaticScope();
        RubyArray result = runtime.newArray();
        
        for (StaticScope current = scope; current.getModule() != object; current = current.getPreviousCRefScope()) {
            result.append(current.getModule());
        }
        
        return result;
    }

    private void doIncludeModule(RubyModule includedModule) {
        boolean skip = false;

        RubyModule currentModule = this;
        while (includedModule != null) {

            if (getNonIncludedClass() == includedModule.getNonIncludedClass()) {
                throw getRuntime().newArgumentError("cyclic include detected");
            }

            boolean superclassSeen = false;

            // scan class hierarchy for module
            for (RubyModule superClass = this.getSuperClass(); superClass != null; superClass = superClass.getSuperClass()) {
                if (superClass instanceof IncludedModuleWrapper) {
                    if (superClass.getNonIncludedClass() == includedModule.getNonIncludedClass()) {
                        if (!superclassSeen) {
                            currentModule = superClass;
                        }
                        skip = true;
                        break;
                    }
                } else {
                    superclassSeen = true;
                }
            }

            if (!skip) {

                // blow away caches for any methods that are redefined by module
                getRuntime().getCacheMap().moduleIncluded(currentModule, includedModule);
                
                // In the current logic, if we get here we know that module is not an
                // IncludedModuleWrapper, so there's no need to fish out the delegate. But just
                // in case the logic should change later, let's do it anyway:
                currentModule.setSuperClass(new IncludedModuleWrapper(getRuntime(), currentModule.getSuperClass(),
                        includedModule.getNonIncludedClass()));
                currentModule = currentModule.getSuperClass();
            }

            includedModule = includedModule.getSuperClass();
            skip = false;
        }
    }


    //
    ////////////////// CLASS VARIABLE RUBY METHODS ////////////////
    //

    @JRubyMethod(name = "class_variable_defined?", required = 1)
    public IRubyObject class_variable_defined_p(ThreadContext context, IRubyObject var) {
        String internedName = validateClassVariable(var.asJavaString().intern());
        RubyModule module = this;
        do {
            if (module.fastHasClassVariable(internedName)) {
                return context.getRuntime().getTrue();
            }
        } while ((module = module.getSuperClass()) != null);

        return context.getRuntime().getFalse();
    }

    /** rb_mod_cvar_get
     *
     */
    @JRubyMethod(name = "class_variable_get", required = 1, visibility = PRIVATE)
    public IRubyObject class_variable_get(IRubyObject var) {
        return fastGetClassVar(validateClassVariable(var.asJavaString()).intern());
    }

    /** rb_mod_cvar_set
     *
     */
    @JRubyMethod(name = "class_variable_set", required = 2, visibility = PRIVATE)
    public IRubyObject class_variable_set(IRubyObject var, IRubyObject value) {
        return fastSetClassVar(validateClassVariable(var.asJavaString()).intern(), value);
    }

    /** rb_mod_remove_cvar
     *
     */
    @JRubyMethod(name = "remove_class_variable", required = 1, visibility = PRIVATE)
    public IRubyObject remove_class_variable(ThreadContext context, IRubyObject name) {
        String javaName = validateClassVariable(name.asJavaString());
        IRubyObject value;

        if ((value = deleteClassVariable(javaName)) != null) {
            return value;
        }

        if (fastIsClassVarDefined(javaName)) {
            throw cannotRemoveError(javaName);
        }

        throw context.getRuntime().newNameError("class variable " + javaName + " not defined for " + getName(), javaName);
    }

    /** rb_mod_class_variables
     *
     */
    @JRubyMethod(name = "class_variables")
    public RubyArray class_variables(ThreadContext context) {
        Set<String> names = new HashSet<String>();

        for (RubyModule p = this; p != null; p = p.getSuperClass()) {
            for (String name : p.getClassVariableNameList()) {
                names.add(name);
            }
        }

        Ruby runtime = context.getRuntime();
        RubyArray ary = runtime.newArray();

        for (String name : names) {
            ary.append(runtime.newString(name));
        }

        return ary;
    }


    //
    ////////////////// CONSTANT RUBY METHODS ////////////////
    //

    /** rb_mod_const_defined
     *
     */
    @JRubyMethod(name = "const_defined?", required = 1)
    public RubyBoolean const_defined_p(ThreadContext context, IRubyObject symbol) {
        // Note: includes part of fix for JRUBY-1339
        return context.getRuntime().newBoolean(fastIsConstantDefined(validateConstant(symbol.asJavaString()).intern()));
    }

    /** rb_mod_const_get
     *
     */
    @JRubyMethod(name = "const_get", required = 1)
    public IRubyObject const_get(IRubyObject symbol) {
        return fastGetConstant(validateConstant(symbol.asJavaString()).intern());
    }

    /** rb_mod_const_set
     *
     */
    @JRubyMethod(name = "const_set", required = 2)
    public IRubyObject const_set(IRubyObject symbol, IRubyObject value) {
        return fastSetConstant(validateConstant(symbol.asJavaString()).intern(), value);
    }

    @JRubyMethod(name = "remove_const", required = 1, visibility = PRIVATE)
    public IRubyObject remove_const(ThreadContext context, IRubyObject name) {
        String id = validateConstant(name.asJavaString());
        IRubyObject value;
        if ((value = deleteConstant(id)) != null) {
            if (value != UNDEF) {
                return value;
            }
            context.getRuntime().getLoadService().removeAutoLoadFor(getName() + "::" + id);
            // FIXME: I'm not sure this is right, but the old code returned
            // the undef, which definitely isn't right...
            return context.getRuntime().getNil();
        }

        if (hasConstantInHierarchy(id)) {
            throw cannotRemoveError(id);
        }

        throw context.getRuntime().newNameError("constant " + id + " not defined for " + getName(), id);
    }

    private boolean hasConstantInHierarchy(final String name) {
        for (RubyModule p = this; p != null; p = p.getSuperClass()) {
            if (p.hasConstant(name)) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Base implementation of Module#const_missing, throws NameError for specific missing constant.
     * 
     * @param name The constant name which was found to be missing
     * @return Nothing! Absolutely nothing! (though subclasses might choose to return something)
     */
    @JRubyMethod(name = "const_missing", required = 1, frame = true)
    public IRubyObject const_missing(ThreadContext context, IRubyObject name, Block block) {
        /* Uninitialized constant */
        if (this != context.getRuntime().getObject()) {
            throw context.getRuntime().newNameError("uninitialized constant " + getName() + "::" + name.asJavaString(), "" + getName() + "::" + name.asJavaString());
        }

        throw context.getRuntime().newNameError("uninitialized constant " + name.asJavaString(), name.asJavaString());
    }

    /** rb_mod_constants
     *
     */
    @JRubyMethod(name = "constants")
    public RubyArray constants(ThreadContext context) {
        Ruby runtime = context.getRuntime();
        RubyArray array = runtime.newArray();
        RubyModule objectClass = runtime.getObject();

        if (getRuntime().getModule() == this) {

            for (String name : objectClass.getStoredConstantNameList()) {
                array.append(runtime.newString(name));
            }

        } else if (objectClass == this) {

            for (String name : getStoredConstantNameList()) {
                array.append(runtime.newString(name));
            }

        } else {
            Set<String> names = new HashSet<String>();
            for (RubyModule p = this; p != null; p = p.getSuperClass()) {
                if (objectClass != p) {
                    for (String name : p.getStoredConstantNameList()) {
                        names.add(name);
                    }
                }
            }
            for (String name : names) {
                array.append(runtime.newString(name));
            }
        }

        return array;
    }


    //
    ////////////////// CLASS VARIABLE API METHODS ////////////////
    //

    /**
     * Set the named class variable to the given value, provided taint and freeze allow setting it.
     * 
     * Ruby C equivalent = "rb_cvar_set"
     * 
     * @param name The variable name to set
     * @param value The value to set it to
     */
    public IRubyObject setClassVar(String name, IRubyObject value) {
        RubyModule module = this;
        do {
            if (module.hasClassVariable(name)) {
                return module.storeClassVariable(name, value);
            }
        } while ((module = module.getSuperClass()) != null);
        
        return storeClassVariable(name, value);
    }

    public IRubyObject fastSetClassVar(final String internedName, final IRubyObject value) {
        assert internedName == internedName.intern() : internedName + " is not interned";
        RubyModule module = this;
        do {
            if (module.fastHasClassVariable(internedName)) {
                return module.fastStoreClassVariable(internedName, value);
            }
        } while ((module = module.getSuperClass()) != null);
        
        return fastStoreClassVariable(internedName, value);
    }

    /**
     * Retrieve the specified class variable, searching through this module, included modules, and supermodules.
     * 
     * Ruby C equivalent = "rb_cvar_get"
     * 
     * @param name The name of the variable to retrieve
     * @return The variable's value, or throws NameError if not found
     */
    public IRubyObject getClassVar(String name) {
        assert IdUtil.isClassVariable(name);
        IRubyObject value;
        RubyModule module = this;
        
        do {
            if ((value = module.variableTableFetch(name)) != null) return value;
        } while ((module = module.getSuperClass()) != null);

        throw getRuntime().newNameError("uninitialized class variable " + name + " in " + getName(), name);
    }

    public IRubyObject fastGetClassVar(String internedName) {
        assert internedName == internedName.intern() : internedName + " is not interned";
        assert IdUtil.isClassVariable(internedName);
        IRubyObject value;
        RubyModule module = this;
        
        do {
            if ((value = module.variableTableFastFetch(internedName)) != null) return value; 
        } while ((module = module.getSuperClass()) != null);

        throw getRuntime().newNameError("uninitialized class variable " + internedName + " in " + getName(), internedName);
    }

    /**
     * Is class var defined?
     * 
     * Ruby C equivalent = "rb_cvar_defined"
     * 
     * @param name The class var to determine "is defined?"
     * @return true if true, false if false
     */
    public boolean isClassVarDefined(String name) {
        RubyModule module = this;
        do {
            if (module.hasClassVariable(name)) return true;
        } while ((module = module.getSuperClass()) != null);

        return false;
    }

    public boolean fastIsClassVarDefined(String internedName) {
        assert internedName == internedName.intern() : internedName + " is not interned";
        RubyModule module = this;
        do {
            if (module.fastHasClassVariable(internedName)) return true;
        } while ((module = module.getSuperClass()) != null);

        return false;
    }

    
    /** rb_mod_remove_cvar
     *
     * FIXME: any good reason to have two identical methods? (same as remove_class_variable)
     */
    public IRubyObject removeCvar(IRubyObject name) { // Wrong Parameter ?
        String internedName = validateClassVariable(name.asJavaString());
        IRubyObject value;

        if ((value = deleteClassVariable(internedName)) != null) {
            return value;
        }

        if (fastIsClassVarDefined(internedName)) {
            throw cannotRemoveError(internedName);
        }

        throw getRuntime().newNameError("class variable " + internedName + " not defined for " + getName(), internedName);
    }


    //
    ////////////////// CONSTANT API METHODS ////////////////
    //

    public IRubyObject getConstantAt(String name) {
        IRubyObject value;
        if ((value = fetchConstant(name)) != UNDEF) {
            return value;
        }
        deleteConstant(name);
        return getRuntime().getLoadService().autoload(getName() + "::" + name);
    }

    public IRubyObject fastGetConstantAt(String internedName) {
        assert internedName == internedName.intern() : internedName + " is not interned";
        IRubyObject value;
        if ((value = fastFetchConstant(internedName)) != UNDEF) {
            return value;
        }
        deleteConstant(internedName);
        return getRuntime().getLoadService().autoload(getName() + "::" + internedName);
    }

    /**
     * Retrieve the named constant, invoking 'const_missing' should that be appropriate.
     * 
     * @param name The constant to retrieve
     * @return The value for the constant, or null if not found
     */
    public IRubyObject getConstant(String name) {
        assert IdUtil.isConstant(name);
        boolean retryForModule = false;
        IRubyObject value;
        RubyModule p = this;

        retry: while (true) {
            while (p != null) {
                if ((value = p.constantTableFetch(name)) != null) {
                    if (value != UNDEF) {
                        return value;
                    }
                    p.deleteConstant(name);
                    if (getRuntime().getLoadService().autoload(
                            p.getName() + "::" + name) == null) {
                        break;
                    }
                    continue;
                }
                p = p.getSuperClass();
            }

            if (!retryForModule && !isClass()) {
                retryForModule = true;
                p = getRuntime().getObject();
                continue retry;
            }

            break;
        }

        return callMethod(getRuntime().getCurrentContext(),
                "const_missing", getRuntime().newSymbol(name));
    }
    
    public IRubyObject fastGetConstant(String internedName) {
        assert internedName == internedName.intern() : internedName + " is not interned";
        assert IdUtil.isConstant(internedName);
        boolean retryForModule = false;
        IRubyObject value;
        RubyModule p = this;

        retry: while (true) {
            while (p != null) {
                if ((value = p.constantTableFastFetch(internedName)) != null) {
                    if (value != UNDEF) {
                        return value;
                    }
                    p.deleteConstant(internedName);
                    if (getRuntime().getLoadService().autoload(
                            p.getName() + "::" + internedName) == null) {
                        break;
                    }
                    continue;
                }
                p = p.getSuperClass();
            }

            if (!retryForModule && !isClass()) {
                retryForModule = true;
                p = getRuntime().getObject();
                continue retry;
            }

            break;
        }

        return callMethod(getRuntime().getCurrentContext(),
                "const_missing", getRuntime().fastNewSymbol(internedName));
    }

    // not actually called anywhere (all known uses call the fast version)
    public IRubyObject getConstantFrom(String name) {
        return fastGetConstantFrom(name.intern());
    }
    
    public IRubyObject fastGetConstantFrom(String internedName) {
        assert internedName == internedName.intern() : internedName + " is not interned";
        assert IdUtil.isConstant(internedName);
        RubyClass objectClass = getRuntime().getObject();
        IRubyObject value;

        RubyModule p = this;
        
        while (p != null) {
            if ((value = p.constantTableFastFetch(internedName)) != null) {
                if (value != UNDEF) {
                    if (p == objectClass && this != objectClass) {
                        String badCName = getName() + "::" + internedName;
                        getRuntime().getWarnings().warn(ID.CONSTANT_BAD_REFERENCE, "toplevel constant " + 
                                internedName + " referenced by " + badCName, badCName);
                    }
                    return value;
                }
                p.deleteConstant(internedName);
                if (getRuntime().getLoadService().autoload(
                        p.getName() + "::" + internedName) == null) {
                    break;
                }
                continue;
            }
            p = p.getSuperClass();
        }

        return callMethod(getRuntime().getCurrentContext(),
                "const_missing", getRuntime().fastNewSymbol(internedName));
    }
    /**
     * Set the named constant on this module. Also, if the value provided is another Module and
     * that module has not yet been named, assign it the specified name.
     * 
     * @param name The name to assign
     * @param value The value to assign to it; if an unnamed Module, also set its basename to name
     * @return The result of setting the variable.
     */
    public IRubyObject setConstant(String name, IRubyObject value) {
        IRubyObject oldValue;
        if ((oldValue = fetchConstant(name)) != null) {
            if (oldValue == UNDEF) {
                getRuntime().getLoadService().removeAutoLoadFor(getName() + "::" + name);
            } else {
                getRuntime().getWarnings().warn(ID.CONSTANT_ALREADY_INITIALIZED, "already initialized constant " + name, name);
            }
        }

        storeConstant(name, value);

        // if adding a module under a constant name, set that module's basename to the constant name
        if (value instanceof RubyModule) {
            RubyModule module = (RubyModule)value;
            if (module.getBaseName() == null) {
                module.setBaseName(name);
                module.setParent(this);
            }
            /*
            module.setParent(this);
            */
        }
        return value;
    }

    public IRubyObject fastSetConstant(String internedName, IRubyObject value) {
        assert internedName == internedName.intern() : internedName + " is not interned";
        IRubyObject oldValue;
        if ((oldValue = fastFetchConstant(internedName)) != null) {
            if (oldValue == UNDEF) {
                getRuntime().getLoadService().removeAutoLoadFor(getName() + "::" + internedName);
            } else {
                getRuntime().getWarnings().warn(ID.CONSTANT_ALREADY_INITIALIZED, "already initialized constant " + internedName, internedName);
            }
        }

        fastStoreConstant(internedName, value);

        // if adding a module under a constant name, set that module's basename to the constant name
        if (value instanceof RubyModule) {
            RubyModule module = (RubyModule)value;
            if (module.getBaseName() == null) {
                module.setBaseName(internedName);
                module.setParent(this);
            }
            /*
            module.setParent(this);
            */
        }
        return value;
    }
    
    /** rb_define_const
     *
     */
    public void defineConstant(String name, IRubyObject value) {
        assert value != null;

        if (this == getRuntime().getClassClass()) {
            getRuntime().secure(4);
        }

        if (!IdUtil.isValidConstantName(name)) {
            throw getRuntime().newNameError("bad constant name " + name, name);
        }

        setConstant(name, value);
    }

    // Fix for JRUBY-1339 - search hierarchy for constant
    /** rb_const_defined_at
     * 
     */
    public boolean isConstantDefined(String name) {
        assert IdUtil.isConstant(name);
        boolean isObject = this == getRuntime().getObject();

        RubyModule module = this;

        do {
            Object value;
            if ((value = module.constantTableFetch(name)) != null) {
                if (value != UNDEF) return true;
                return getRuntime().getLoadService().autoloadFor(
                        module.getName() + "::" + name) != null;
            }

        } while (isObject && (module = module.getSuperClass()) != null );

        return false;
    }

    public boolean fastIsConstantDefined(String internedName) {
        assert internedName == internedName.intern() : internedName + " is not interned";
        assert IdUtil.isConstant(internedName);
        boolean isObject = this == getRuntime().getObject();

        RubyModule module = this;

        do {
            Object value;
            if ((value = module.constantTableFastFetch(internedName)) != null) {
                if (value != UNDEF) return true;
                return getRuntime().getLoadService().autoloadFor(
                        module.getName() + "::" + internedName) != null;
            }

        } while (isObject && (module = module.getSuperClass()) != null );

        return false;
    }

    //
    ////////////////// COMMON CONSTANT / CVAR METHODS ////////////////
    //

    private RaiseException cannotRemoveError(String id) {
        return getRuntime().newNameError("cannot remove " + id + " for " + getName(), id);
    }


    //
    ////////////////// INTERNAL MODULE VARIABLE API METHODS ////////////////
    //
    
    /**
     * Behaves similarly to {@link #getClassVar(String)}. Searches this
     * class/module <em>and its ancestors</em> for the specified internal
     * variable.
     * 
     * @param name the internal variable name
     * @return the value of the specified internal variable if found, else null
     * @see #setInternalModuleVariable(String, IRubyObject)
     */
    public boolean hasInternalModuleVariable(final String name) {
        RubyModule module = this;
        do {
            if (module.hasInternalVariable(name)) {
                return true;
            }
        } while ((module = module.getSuperClass()) != null);

        return false;
    }
    /**
     * Behaves similarly to {@link #getClassVar(String)}. Searches this
     * class/module <em>and its ancestors</em> for the specified internal
     * variable.
     * 
     * @param name the internal variable name
     * @return the value of the specified internal variable if found, else null
     * @see #setInternalModuleVariable(String, IRubyObject)
     */
    public IRubyObject searchInternalModuleVariable(final String name) {
        RubyModule module = this;
        IRubyObject value;
        do {
            if ((value = module.getInternalVariable(name)) != null) {
                return value;
            }
        } while ((module = module.getSuperClass()) != null);

        return null;
    }

    /**
     * Behaves similarly to {@link #setClassVar(String, IRubyObject)}. If the
     * specified internal variable is found in this class/module <em>or an ancestor</em>,
     * it is set where found.  Otherwise it is set in this module. 
     * 
     * @param name the internal variable name
     * @param value the internal variable value
     * @see #searchInternalModuleVariable(String)
     */
    public void setInternalModuleVariable(final String name, final IRubyObject value) {
        RubyModule module = this;
        do {
            if (module.hasInternalVariable(name)) {
                module.setInternalVariable(name, value);
                return;
            }
        } while ((module = module.getSuperClass()) != null);

        setInternalVariable(name, value);
    }

    //
    ////////////////// LOW-LEVEL CLASS VARIABLE INTERFACE ////////////////
    //
    // fetch/store/list class variables for this module
    //
    
    public boolean hasClassVariable(String name) {
        assert IdUtil.isClassVariable(name);
        return variableTableContains(name);
    }

    public boolean fastHasClassVariable(String internedName) {
        assert IdUtil.isClassVariable(internedName);
        return variableTableFastContains(internedName);
    }

    public IRubyObject fetchClassVariable(String name) {
        assert IdUtil.isClassVariable(name);
        return variableTableFetch(name);
    }

    public IRubyObject fastFetchClassVariable(String internedName) {
        assert IdUtil.isClassVariable(internedName);
        return variableTableFastFetch(internedName);
    }

    public IRubyObject storeClassVariable(String name, IRubyObject value) {
        assert IdUtil.isClassVariable(name) && value != null;
        ensureClassVariablesSettable();
        return variableTableStore(name, value);
    }

    public IRubyObject fastStoreClassVariable(String internedName, IRubyObject value) {
        assert IdUtil.isClassVariable(internedName) && value != null;
        ensureClassVariablesSettable();
        return variableTableFastStore(internedName, value);
    }

    public IRubyObject deleteClassVariable(String name) {
        assert IdUtil.isClassVariable(name);
        ensureClassVariablesSettable();
        return variableTableRemove(name);
    }

    public List<Variable<IRubyObject>> getClassVariableList() {
        ArrayList<Variable<IRubyObject>> list = new ArrayList<Variable<IRubyObject>>();
        VariableTableEntry[] table = variableTableGetTable();
        IRubyObject readValue;
        for (int i = table.length; --i >= 0; ) {
            for (VariableTableEntry e = table[i]; e != null; e = e.next) {
                if (IdUtil.isClassVariable(e.name)) {
                    if ((readValue = e.value) == null) readValue = variableTableReadLocked(e);
                    list.add(new VariableEntry<IRubyObject>(e.name, readValue));
                }
            }
        }
        return list;
    }

    public List<String> getClassVariableNameList() {
        ArrayList<String> list = new ArrayList<String>();
        VariableTableEntry[] table = variableTableGetTable();
        for (int i = table.length; --i >= 0; ) {
            for (VariableTableEntry e = table[i]; e != null; e = e.next) {
                if (IdUtil.isClassVariable(e.name)) {
                    list.add(e.name);
                }
            }
        }
        return list;
    }

    protected static final String ERR_INSECURE_SET_CLASS_VAR = "Insecure: can't modify class variable";
    protected static final String ERR_FROZEN_CVAR_TYPE = "class/module ";
   
    protected final String validateClassVariable(String name) {
        if (IdUtil.isValidClassVariableName(name)) {
            return name;
        }
        throw getRuntime().newNameError("`" + name + "' is not allowed as a class variable name", name);
    }

    protected final void ensureClassVariablesSettable() {
        Ruby runtime = getRuntime();
        
        if (!isFrozen() && (runtime.getSafeLevel() < 4 || isTaint())) {
            return;
        }
        
        if (runtime.getSafeLevel() >= 4 && !isTaint()) {
            throw runtime.newSecurityError(ERR_INSECURE_SET_CONSTANT);
        }
        if (isFrozen()) {
            if (this instanceof RubyModule) {
                throw runtime.newFrozenError(ERR_FROZEN_CONST_TYPE);
            } else {
                throw runtime.newFrozenError("");
            }
        }
    }

    //
    ////////////////// LOW-LEVEL CONSTANT INTERFACE ////////////////
    //
    // fetch/store/list constants for this module
    //

    public boolean hasConstant(String name) {
        assert IdUtil.isConstant(name);
        return constantTableContains(name);
    }

    public boolean fastHasConstant(String internedName) {
        assert IdUtil.isConstant(internedName);
        return constantTableFastContains(internedName);
    }

    // returns the stored value without processing undefs (autoloads)
    public IRubyObject fetchConstant(String name) {
        assert IdUtil.isConstant(name);
        return constantTableFetch(name);
    }

    // returns the stored value without processing undefs (autoloads)
    public IRubyObject fastFetchConstant(String internedName) {
        assert IdUtil.isConstant(internedName);
        return constantTableFastFetch(internedName);
    }

    public IRubyObject storeConstant(String name, IRubyObject value) {
        assert IdUtil.isConstant(name) && value != null;
        ensureConstantsSettable();
        return constantTableStore(name, value);
    }

    public IRubyObject fastStoreConstant(String internedName, IRubyObject value) {
        assert IdUtil.isConstant(internedName) && value != null;
        ensureConstantsSettable();
        return constantTableFastStore(internedName, value);
    }

    // removes and returns the stored value without processing undefs (autoloads)
    public IRubyObject deleteConstant(String name) {
        assert IdUtil.isConstant(name);
        ensureConstantsSettable();
        return constantTableRemove(name);
    }

    public List<Variable<IRubyObject>> getStoredConstantList() {
        ArrayList<Variable<IRubyObject>> list = new ArrayList<Variable<IRubyObject>>();
        ConstantTableEntry[] table = constantTableGetTable();
        for (int i = table.length; --i >= 0; ) {
            for (ConstantTableEntry e = table[i]; e != null; e = e.next) {
                list.add(e);
            }
        }
        return list;
    }

    public List<String> getStoredConstantNameList() {
        ArrayList<String> list = new ArrayList<String>();
        ConstantTableEntry[] table = constantTableGetTable();
        for (int i = table.length; --i >= 0; ) {
            for (ConstantTableEntry e = table[i]; e != null; e = e.next) {
                list.add(e.name);
            }
        }
        return list;
    }

    protected static final String ERR_INSECURE_SET_CONSTANT  = "Insecure: can't modify constant";
    protected static final String ERR_FROZEN_CONST_TYPE = "class/module ";
   
    protected final String validateConstant(String name) {
        if (IdUtil.isValidConstantName(name)) {
            return name;
        }
        throw getRuntime().newNameError("wrong constant name " + name, name);
    }

    protected final void ensureConstantsSettable() {
        Ruby runtime = getRuntime();
        
        if (!isFrozen() && (runtime.getSafeLevel() < 4 || isTaint())) {
            return;
        }
        
        if (runtime.getSafeLevel() >= 4 && !isTaint()) {
            throw runtime.newSecurityError(ERR_INSECURE_SET_CONSTANT);
        }
        if (isFrozen()) {
            if (this instanceof RubyModule) {
                throw runtime.newFrozenError(ERR_FROZEN_CONST_TYPE);
            } else {
                throw runtime.newFrozenError("");
            }
        }
    }

     
    //
    ////////////////// VARIABLE TABLE METHODS ////////////////
    //
    // Overridden to use variableWriteLock in place of synchronization  
    //

    @Override
    protected IRubyObject variableTableStore(String name, IRubyObject value) {
        int hash = name.hashCode();
        ReentrantLock lock;
        (lock = variableWriteLock).lock();
        try {
            VariableTableEntry[] table;
            VariableTableEntry e;
            if ((table = variableTable) == null) {
                table =  new VariableTableEntry[VARIABLE_TABLE_DEFAULT_CAPACITY];
                e = new VariableTableEntry(hash, name.intern(), value, null);
                table[hash & (VARIABLE_TABLE_DEFAULT_CAPACITY - 1)] = e;
                variableTableThreshold = (int)(VARIABLE_TABLE_DEFAULT_CAPACITY * VARIABLE_TABLE_LOAD_FACTOR);
                variableTableSize = 1;
                variableTable = table;
                return value;
            }
            int potentialNewSize;
            if ((potentialNewSize = variableTableSize + 1) > variableTableThreshold) {
                table = variableTableRehash();
            }
            int index;
            for (e = table[index = hash & (table.length - 1)]; e != null; e = e.next) {
                if (hash == e.hash && name.equals(e.name)) {
                    e.value = value;
                    return value;
                }
            }
            // external volatile value initialization intended to obviate the need for
            // readValueUnderLock technique used in ConcurrentHashMap. may be a little
            // slower, but better to pay a price on first write rather than all reads.
            e = new VariableTableEntry(hash, name.intern(), value, table[index]);
            table[index] = e;
            variableTableSize = potentialNewSize;
            variableTable = table; // write-volatile
        } finally {
            lock.unlock();
        }
        return value;
    }
    
    @Override
    protected IRubyObject variableTableFastStore(String internedName, IRubyObject value) {
        assert internedName == internedName.intern() : internedName + " not interned";
        int hash = internedName.hashCode();
        ReentrantLock lock;
        (lock = variableWriteLock).lock();
        try {
            VariableTableEntry[] table;
            VariableTableEntry e;
            if ((table = variableTable) == null) {
                table =  new VariableTableEntry[VARIABLE_TABLE_DEFAULT_CAPACITY];
                e = new VariableTableEntry(hash, internedName, value, null);
                table[hash & (VARIABLE_TABLE_DEFAULT_CAPACITY - 1)] = e;
                variableTableThreshold = (int)(VARIABLE_TABLE_DEFAULT_CAPACITY * VARIABLE_TABLE_LOAD_FACTOR);
                variableTableSize = 1;
                variableTable = table;
                return value;
            }
            int potentialNewSize;
            if ((potentialNewSize = variableTableSize + 1) > variableTableThreshold) {
                table = variableTableRehash();
            }
            int index;
            for (e = table[index = hash & (table.length - 1)]; e != null; e = e.next) {
                if (internedName == e.name) {
                    e.value = value;
                    return value;
                }
            }
            // external volatile value initialization intended to obviate the need for
            // readValueUnderLock technique used in ConcurrentHashMap. may be a little
            // slower, but better to pay a price on first write rather than all reads.
            e = new VariableTableEntry(hash, internedName, value, table[index]);
            table[index] = e;
            variableTableSize = potentialNewSize;
            variableTable = table; // write-volatile
        } finally {
            lock.unlock();
        }
        return value;
    }

    @Override   
    protected IRubyObject variableTableRemove(String name) {
        ReentrantLock lock;
        (lock = variableWriteLock).lock();
        try {
            VariableTableEntry[] table;
            if ((table = variableTable) != null) {
                int hash = name.hashCode();
                int index = hash & (table.length - 1);
                VariableTableEntry first = table[index];
                VariableTableEntry e;
                for (e = first; e != null; e = e.next) {
                    if (hash == e.hash && name.equals(e.name)) {
                        IRubyObject oldValue = e.value;
                        // All entries following removed node can stay
                        // in list, but all preceding ones need to be
                        // cloned.
                        VariableTableEntry newFirst = e.next;
                        for (VariableTableEntry p = first; p != e; p = p.next) {
                            newFirst = new VariableTableEntry(p.hash, p.name, p.value, newFirst);
                        }
                        table[index] = newFirst;
                        variableTableSize--;
                        variableTable = table; // write-volatile 
                        return oldValue;
                    }
                }
            }
        } finally {
            lock.unlock();
        }
        return null;
    }
    
    @Override
    protected IRubyObject variableTableReadLocked(VariableTableEntry entry) {
        ReentrantLock lock;
        (lock = variableWriteLock).lock();
        try {
            return entry.value;
        } finally {
            lock.unlock();
        }
    }
    
    @Override
    protected void variableTableSync(List<Variable<IRubyObject>> vars) {
        ReentrantLock lock;
        (lock = variableWriteLock).lock();
        try {
            variableTableSize = 0;
            variableTableThreshold = (int)(VARIABLE_TABLE_DEFAULT_CAPACITY * VARIABLE_TABLE_LOAD_FACTOR);
            variableTable =  new VariableTableEntry[VARIABLE_TABLE_DEFAULT_CAPACITY];
            for (Variable<IRubyObject> var : vars) {
                assert !var.isConstant() && var.getValue() != null;
                variableTableStore(var.getName(), var.getValue());
            }
        } finally {
            lock.unlock();
        }
    }
    
    @Override
    public void syncVariables(List<Variable<IRubyObject>> variables) {
        ArrayList<Variable<IRubyObject>> constants = new ArrayList<Variable<IRubyObject>>(variables.size());
        Variable<IRubyObject> var;
        for (Iterator<Variable<IRubyObject>> iter = variables.iterator(); iter.hasNext(); ) {
            if ((var = iter.next()).isConstant()) {
                constants.add(var);
                iter.remove();
            }
        }
        ReentrantLock lock;
        (lock = variableWriteLock).lock();
        try {
            variableTableSync(variables);
            constantTableSync(constants);
        } finally {
            lock.unlock();
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    @Deprecated // born deprecated
    public Map getVariableMap() {
        Map map = variableTableGetMap();
        constantTableGetMap(map);
        return map;
    }

    @Override
    public boolean hasVariables() {
        return variableTableGetSize() > 0 || constantTableGetSize() > 0;
    }

    @Override
    public int getVariableCount() {
        return variableTableGetSize() + constantTableGetSize();
    }
    
    @Override
    public List<Variable<IRubyObject>> getVariableList() {
        VariableTableEntry[] vtable = variableTableGetTable();
        ConstantTableEntry[] ctable = constantTableGetTable();
        ArrayList<Variable<IRubyObject>> list = new ArrayList<Variable<IRubyObject>>();
        IRubyObject readValue;
        for (int i = vtable.length; --i >= 0; ) {
            for (VariableTableEntry e = vtable[i]; e != null; e = e.next) {
                if ((readValue = e.value) == null) readValue = variableTableReadLocked(e);
                list.add(new VariableEntry<IRubyObject>(e.name, readValue));
            }
        }
        for (int i = ctable.length; --i >= 0; ) {
            for (ConstantTableEntry e = ctable[i]; e != null; e = e.next) {
                list.add(e);
            }
        }
        return list;
    }

    @Override
    public List<String> getVariableNameList() {
        VariableTableEntry[] vtable = variableTableGetTable();
        ConstantTableEntry[] ctable = constantTableGetTable();
        ArrayList<String> list = new ArrayList<String>();
        for (int i = vtable.length; --i >= 0; ) {
            for (VariableTableEntry e = vtable[i]; e != null; e = e.next) {
                list.add(e.name);
            }
        }
        for (int i = ctable.length; --i >= 0; ) {
            for (ConstantTableEntry e = ctable[i]; e != null; e = e.next) {
                list.add(e.name);
            }
        }
        return list;
    }
    

    //
    ////////////////// CONSTANT TABLE METHODS, ETC. ////////////////
    //
    
    protected static final int CONSTANT_TABLE_DEFAULT_CAPACITY = 8; // MUST be power of 2!
    protected static final int CONSTANT_TABLE_MAXIMUM_CAPACITY = 1 << 30;
    protected static final float CONSTANT_TABLE_LOAD_FACTOR = 0.75f;

    protected static final class ConstantTableEntry implements Variable<IRubyObject> {
        final int hash;
        final String name;
        final IRubyObject value;
        final ConstantTableEntry next;

        // constant table entry values are final; if a constant is redefined, the
        // entry will be removed and replaced with a new entry.
        ConstantTableEntry(
                int hash,
                String name,
                IRubyObject value,
                ConstantTableEntry next) {
            this.hash = hash;
            this.name = name;
            this.value = value;
            this.next = next;
        }
        
        public String getName() {
            return name;
        }
        
        public IRubyObject getValue() {
            return value;
        }
        public final boolean isClassVariable() {
            return false;
        }
        
        public final boolean isConstant() {
            return true;
        }
        
        public final boolean isInstanceVariable() {
            return false;
        }

        public final boolean isRubyVariable() {
            return true;
        }
    }

    protected boolean constantTableContains(String name) {
        int hash = name.hashCode();
        ConstantTableEntry[] table;
        for (ConstantTableEntry e = (table = constantTable)[hash & (table.length - 1)]; e != null; e = e.next) {
            if (hash == e.hash && name.equals(e.name)) {
                return true;
            }
        }
        return false;
    }
    
    protected boolean constantTableFastContains(String internedName) {
        ConstantTableEntry[] table;
        for (ConstantTableEntry e = (table = constantTable)[internedName.hashCode() & (table.length - 1)]; e != null; e = e.next) {
            if (internedName == e.name) {
                return true;
            }
        }
        return false;
    }
    
    protected IRubyObject constantTableFetch(String name) {
        int hash = name.hashCode();
        ConstantTableEntry[] table;
        for (ConstantTableEntry e = (table = constantTable)[hash & (table.length - 1)]; e != null; e = e.next) {
            if (hash == e.hash && name.equals(e.name)) {
                return e.value;
            }
        }
        return null;
    }
    
    protected IRubyObject constantTableFastFetch(String internedName) {
        ConstantTableEntry[] table;
        for (ConstantTableEntry e = (table = constantTable)[internedName.hashCode() & (table.length - 1)]; e != null; e = e.next) {
            if (internedName == e.name) {
                return e.value;
            }
        }
        return null;
    }
    
    protected IRubyObject constantTableStore(String name, IRubyObject value) {
        int hash = name.hashCode();
        ReentrantLock lock;
        (lock = variableWriteLock).lock();
        try {
            ConstantTableEntry[] table;
            ConstantTableEntry e;
            ConstantTableEntry first;
            int potentialNewSize;
            if ((potentialNewSize = constantTableSize + 1) > constantTableThreshold) {
                table = constantTableRehash();
            } else {
                table = constantTable;
            }
            int index;
            for (e = first = table[index = hash & (table.length - 1)]; e != null; e = e.next) {
                if (hash == e.hash && name.equals(e.name)) {
                    // if value is unchanged, do nothing
                    if (value == e.value) {
                        return value;
                    }
                    // create new entry, prepend to any trailing entries
                    ConstantTableEntry newFirst = new ConstantTableEntry(e.hash, e.name, value, e.next);
                    // all entries before this one must be cloned
                    for (ConstantTableEntry n = first; n != e; n = n.next) {
                        newFirst = new ConstantTableEntry(n.hash, n.name, n.value, newFirst);
                    }
                    table[index] = newFirst;
                    constantTable = table; // write-volatile
                    return value;
                }
            }
            table[index] = new ConstantTableEntry(hash, name.intern(), value, table[index]);
            constantTableSize = potentialNewSize;
            constantTable = table; // write-volatile
        } finally {
            lock.unlock();
        }
        return value;
    }
    
    protected IRubyObject constantTableFastStore(String internedName, IRubyObject value) {
        assert internedName == internedName.intern() : internedName + " not interned";
        int hash = internedName.hashCode();
        ReentrantLock lock;
        (lock = variableWriteLock).lock();
        try {
            ConstantTableEntry[] table;
            ConstantTableEntry e;
            ConstantTableEntry first;
            int potentialNewSize;
            if ((potentialNewSize = constantTableSize + 1) > constantTableThreshold) {
                table = constantTableRehash();
            } else {
                table = constantTable;
            }
            int index;
            for (e = first = table[index = hash & (table.length - 1)]; e != null; e = e.next) {
                if (internedName == e.name) {
                    // if value is unchanged, do nothing
                    if (value == e.value) {
                        return value;
                    }
                    // create new entry, prepend to any trailing entries
                    ConstantTableEntry newFirst = new ConstantTableEntry(e.hash, e.name, value, e.next);
                    // all entries before this one must be cloned
                    for (ConstantTableEntry n = first; n != e; n = n.next) {
                        newFirst = new ConstantTableEntry(n.hash, n.name, n.value, newFirst);
                    }
                    table[index] = newFirst;
                    constantTable = table; // write-volatile
                    return value;
                }
            }
            table[index] = new ConstantTableEntry(hash, internedName, value, table[index]);
            constantTableSize = potentialNewSize;
            constantTable = table; // write-volatile
        } finally {
            lock.unlock();
        }
        return value;
    }
        
    protected IRubyObject constantTableRemove(String name) {
        ReentrantLock lock;
        (lock = variableWriteLock).lock();
        try {
            ConstantTableEntry[] table;
            if ((table = constantTable) != null) {
                int hash = name.hashCode();
                int index = hash & (table.length - 1);
                ConstantTableEntry first = table[index];
                ConstantTableEntry e;
                for (e = first; e != null; e = e.next) {
                    if (hash == e.hash && name.equals(e.name)) {
                        IRubyObject oldValue = e.value;
                        // All entries following removed node can stay
                        // in list, but all preceding ones need to be
                        // cloned.
                        ConstantTableEntry newFirst = e.next;
                        for (ConstantTableEntry p = first; p != e; p = p.next) {
                            newFirst = new ConstantTableEntry(p.hash, p.name, p.value, newFirst);
                        }
                        table[index] = newFirst;
                        constantTableSize--;
                        constantTable = table; // write-volatile 
                        return oldValue;
                    }
                }
            }
        } finally {
            lock.unlock();
        }
        return null;
    }
    

    protected ConstantTableEntry[] constantTableGetTable() {
        return constantTable;
    }
    
    protected int constantTableGetSize() {
        if (constantTable != null) {
            return constantTableSize;
        }
        return 0;
    }
    
    protected void constantTableSync(List<Variable<IRubyObject>> vars) {
        ReentrantLock lock;
        (lock = variableWriteLock).lock();
        try {
            constantTableSize = 0;
            constantTableThreshold = (int)(CONSTANT_TABLE_DEFAULT_CAPACITY * CONSTANT_TABLE_LOAD_FACTOR);
            constantTable =  new ConstantTableEntry[CONSTANT_TABLE_DEFAULT_CAPACITY];
            for (Variable<IRubyObject> var : vars) {
                assert var.isConstant() && var.getValue() != null;
                constantTableStore(var.getName(), var.getValue());
            }
        } finally {
            lock.unlock();
        }
    }
    
    // MUST be called from synchronized/locked block!
    // should only be called by constantTableStore/constantTableFastStore
    private final ConstantTableEntry[] constantTableRehash() {
        ConstantTableEntry[] oldTable = constantTable;
        int oldCapacity;
        if ((oldCapacity = oldTable.length) >= CONSTANT_TABLE_MAXIMUM_CAPACITY) {
            return oldTable;
        }
        
        int newCapacity = oldCapacity << 1;
        ConstantTableEntry[] newTable = new ConstantTableEntry[newCapacity];
        constantTableThreshold = (int)(newCapacity * CONSTANT_TABLE_LOAD_FACTOR);
        int sizeMask = newCapacity - 1;
        ConstantTableEntry e;
        for (int i = oldCapacity; --i >= 0; ) {
            // We need to guarantee that any existing reads of old Map can
            //  proceed. So we cannot yet null out each bin.
            e = oldTable[i];

            if (e != null) {
                ConstantTableEntry next = e.next;
                int idx = e.hash & sizeMask;

                //  Single node on list
                if (next == null)
                    newTable[idx] = e;

                else {
                    // Reuse trailing consecutive sequence at same slot
                    ConstantTableEntry lastRun = e;
                    int lastIdx = idx;
                    for (ConstantTableEntry last = next;
                         last != null;
                         last = last.next) {
                        int k = last.hash & sizeMask;
                        if (k != lastIdx) {
                            lastIdx = k;
                            lastRun = last;
                        }
                    }
                    newTable[lastIdx] = lastRun;

                    // Clone all remaining nodes
                    for (ConstantTableEntry p = e; p != lastRun; p = p.next) {
                        int k = p.hash & sizeMask;
                        ConstantTableEntry m = new ConstantTableEntry(p.hash, p.name, p.value, newTable[k]);
                        newTable[k] = m;
                    }
                }
            }
        }
        constantTable = newTable;
        return newTable;
    }
    

    /**
     * Method to help ease transition to new variables implementation.
     * Will likely be deprecated in the near future.
     */
    @SuppressWarnings("unchecked")
    protected Map constantTableGetMap() {
        HashMap map = new HashMap();
        ConstantTableEntry[] table;
        if ((table = constantTable) != null) {
            for (int i = table.length; --i >= 0; ) {
                for (ConstantTableEntry e = table[i]; e != null; e = e.next) {
                    map.put(e.name, e.value);
                }
            }
        }
        return map;
    }
    
    /**
     * Method to help ease transition to new variables implementation.
     * Will likely be deprecated in the near future.
     */
    @SuppressWarnings("unchecked")
    protected Map constantTableGetMap(Map map) {
        ConstantTableEntry[] table;
        if ((table = constantTable) != null) {
            for (int i = table.length; --i >= 0; ) {
                for (ConstantTableEntry e = table[i]; e != null; e = e.next) {
                    map.put(e.name, e.value);
                }
            }
        }
        return map;
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2006 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/

package org.jruby;

import static org.jruby.runtime.Visibility.PRIVATE;
import static org.jruby.runtime.Visibility.PROTECTED;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.exceptions.JumpException;
import org.jruby.runtime.Block;
import org.jruby.runtime.CallType;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.Sprintf;

/**
 * @author Anders Bengtsson
 */
@JRubyClass(name="NameError", parent="StandardError")
public class RubyNameError extends RubyException {
    private IRubyObject name;

    /** 
     * Nested class whose instances act as thunks reacting to to_str method
     * called from (Exception#to_str, Exception#message)
     * MRI equivalent: rb_cNameErrorMesg, class name: "message", construction method: "!",
     * to_str implementation: "name_err_mesg_to_str"
     *
     * TODO: this class should not be lookupable
     */
    @JRubyClass(name = "NameError::Message", parent = "Object")
    public static final class RubyNameErrorMessage extends RubyObject {

        static ObjectAllocator NAMEERRORMESSAGE_ALLOCATOR = new ObjectAllocator() {
            public IRubyObject allocate(Ruby runtime, RubyClass klass) {
                IRubyObject dummy = new RubyObject(runtime, runtime.getObject());
                return new RubyNameErrorMessage(runtime, dummy, dummy, Visibility.PRIVATE, CallType.VARIABLE);
            }
        };        

        private final IRubyObject object;
        private final IRubyObject method;
        private final Visibility visibility;
        private final CallType callType;

        RubyNameErrorMessage(Ruby runtime, IRubyObject object, IRubyObject method, Visibility visibility, CallType callType) {
            super(runtime, runtime.getNameErrorMessage(), false);
            this.object = object;
            this.method = method;
            this.visibility = visibility;
            this.callType = callType;
        }

        @JRubyMethod(name = "_load", meta = true)
        public static IRubyObject load(IRubyObject recv, IRubyObject arg) {
            return arg;
        }

        @JRubyMethod(name = "_dump")
        public IRubyObject dump(ThreadContext context, IRubyObject arg) {
            return to_str(context);
        }

        @JRubyMethod(name = "to_str")
        public IRubyObject to_str(ThreadContext context) {
            String format = null;

            if (visibility == PRIVATE) {
                format = "private method `%s' called for %s";
            } else if (visibility == PROTECTED) {
                format = "protected method `%s' called for %s";
            } else if (callType == CallType.VARIABLE) {
                format = "undefined local variable or method `%s' for %s";
            } else if (callType == CallType.SUPER) {
                format = "super: no superclass method `%s'";
            }

            if (format == null) format = "undefined method `%s' for %s";

            String description = null;

            if (object.isNil()) {
                description = "nil";
            } else if (object instanceof RubyBoolean && object.isTrue()) {
                description = "true";
            } else if (object instanceof RubyBoolean && !object.isTrue()) {
                description = "false";
            } else {
                try {
                    description = RubyObject.inspect(context, object).toString();
                } catch(JumpException e) {}

                if (description == null || description.length() > 65) description = object.anyToString().toString();
            }

            if (description.length() == 0 || (description.length() > 0 && description.charAt(0) != '#')) {
                description = description + ":" + object.getMetaClass().getRealClass().getName();            
            }

            Ruby runtime = getRuntime();
            RubyArray arr = runtime.newArray(method, runtime.newString(description));
            RubyString msg = runtime.newString(Sprintf.sprintf(runtime.newString(format), arr).toString());
            if (object.isTaint()) msg.setTaint(true);
            return msg;
        }
    }

    private static ObjectAllocator NAMEERROR_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return new RubyNameError(runtime, klass);
        }
    };

    public static RubyClass createNameErrorClass(Ruby runtime, RubyClass standardErrorClass) {
        RubyClass nameErrorClass = runtime.defineClass("NameError", standardErrorClass, NAMEERROR_ALLOCATOR);
        nameErrorClass.defineAnnotatedMethods(RubyNameError.class);
        return nameErrorClass;
    }

    public static RubyClass createNameErrorMessageClass(Ruby runtime, RubyClass nameErrorClass) {
        RubyClass messageClass = nameErrorClass.defineClassUnder("Message", runtime.getObject(), RubyNameErrorMessage.NAMEERRORMESSAGE_ALLOCATOR);
        messageClass.defineAnnotatedMethods(RubyNameErrorMessage.class);
        return messageClass;
    }

    protected RubyNameError(Ruby runtime, RubyClass exceptionClass) {
        this(runtime, exceptionClass, exceptionClass.getName());
    }

    public RubyNameError(Ruby runtime, RubyClass exceptionClass, String message) {
        this(runtime, exceptionClass, message, null);
    }

    public RubyNameError(Ruby runtime, RubyClass exceptionClass, String message, String name) {
        super(runtime, exceptionClass, message);
        this.name = name == null ? runtime.getNil() : runtime.newString(name);
    }

    @JRubyMethod(name = "exception", rest = true, meta = true)
    public static RubyException newRubyNameError(IRubyObject recv, IRubyObject[] args) {
        RubyClass klass = (RubyClass)recv;
        
        RubyException newError = (RubyException) klass.allocate();
        
        newError.callInit(args, Block.NULL_BLOCK);
        
        return newError;
    }

    @JRubyMethod(name = "initialize", optional = 2, frame = true)
    @Override
    public IRubyObject initialize(IRubyObject[] args, Block block) {
        if (args.length > 1) {
            name = args[args.length - 1];
            int newLength = args.length > 2 ? args.length - 2 : args.length - 1;

            IRubyObject []tmpArgs = new IRubyObject[newLength];
            System.arraycopy(args, 0, tmpArgs, 0, newLength);
            args = tmpArgs;
        } else {
            name = getRuntime().getNil();
        }

        super.initialize(args, block);
        return this;
    }

    @JRubyMethod(name = "to_s")
    @Override
    public IRubyObject to_s() {
        if (message.isNil()) return getRuntime().newString(message.getMetaClass().getName());
        RubyString str = message.convertToString();
        if (str != message) message = str;
        if (isTaint()) message.setTaint(true);
        return message;
    }

    @JRubyMethod(name = "name")
    public IRubyObject name() {
        return name;
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;

/**
 *
 * @author  jpetersen
 */
@JRubyClass(name="NilClass")
public class RubyNil extends RubyObject {
    public RubyNil(Ruby runtime) {
        super(runtime, runtime.getNilClass(), false);
        flags |= NIL_F | FALSE_F;
    }
    
    public static final ObjectAllocator NIL_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return runtime.getNil();
        }
    };
    
    public static RubyClass createNilClass(Ruby runtime) {
        RubyClass nilClass = runtime.defineClass("NilClass", runtime.getObject(), NIL_ALLOCATOR);
        runtime.setNilClass(nilClass);
        nilClass.index = ClassIndex.NIL;
        
        nilClass.defineAnnotatedMethods(RubyNil.class);
        
        nilClass.getMetaClass().undefineMethod("new");
        
        // FIXME: This is causing a verification error for some reason
        //nilClass.dispatcher = callbackFactory.createDispatcher(nilClass);
        
        return nilClass;
    }
    
    @Override
    public int getNativeTypeIndex() {
        return ClassIndex.NIL;
    }

    @Override
    public boolean isImmediate() {
        return true;
    }

    @Override
    public RubyClass getSingletonClass() {
        return metaClass;
    }
    
    @Override
    public Class<?> getJavaClass() {
        return void.class;
    }
    
    // Methods of the Nil Class (nil_*):
    
    /** nil_to_i
     *
     */
    @JRubyMethod(name = "to_i")
    public static RubyFixnum to_i(IRubyObject recv) {
        return RubyFixnum.zero(recv.getRuntime());
    }
    
    /**
     * nil_to_f
     *
     */
    @JRubyMethod(name = "to_f")
    public static RubyFloat to_f(IRubyObject recv) {
        return RubyFloat.newFloat(recv.getRuntime(), 0.0D);
    }
    
    /** nil_to_s
     *
     */
    @JRubyMethod(name = "to_s")
    public static RubyString to_s(IRubyObject recv) {
        return RubyString.newEmptyString(recv.getRuntime());
    }
    
    /** nil_to_a
     *
     */
    @JRubyMethod(name = "to_a")
    public static RubyArray to_a(IRubyObject recv) {
        return recv.getRuntime().newEmptyArray();
    }
    
    /** nil_inspect
     *
     */
    @JRubyMethod(name = "inspect")
    public static RubyString inspect(IRubyObject recv) {
        return recv.getRuntime().newString("nil");
    }
    
    /** nil_type
     *
     */
    @JRubyMethod(name = "type")
    public static RubyClass type(IRubyObject recv) {
        return recv.getRuntime().getNilClass();
    }
    
    /** nil_and
     *
     */
    @JRubyMethod(name = "&", required = 1)
    public static RubyBoolean op_and(IRubyObject recv, IRubyObject obj) {
        return recv.getRuntime().getFalse();
    }
    
    /** nil_or
     *
     */
    @JRubyMethod(name = "|", required = 1)
    public static RubyBoolean op_or(IRubyObject recv, IRubyObject obj) {
        return recv.getRuntime().newBoolean(obj.isTrue());
    }
    
    /** nil_xor
     *
     */
    @JRubyMethod(name = "^", required = 1)
    public static RubyBoolean op_xor(IRubyObject recv, IRubyObject obj) {
        return recv.getRuntime().newBoolean(obj.isTrue());
    }

    @JRubyMethod(name = "nil?")
    public IRubyObject nil_p() {
        return getRuntime().getTrue();
    }
    
    @Override
    public RubyFixnum id() {
        return getRuntime().newFixnum(4);
    }
    
    @Override
    public IRubyObject taint(ThreadContext context) {
        return this;
    }

    @Override
    public IRubyObject freeze(ThreadContext context) {
        return this;
    }

    /** nilclass_to_c
     * 
     */
    @JRubyMethod(name = "to_c", compat = CompatVersion.RUBY1_9)
    public static IRubyObject to_c(ThreadContext context, IRubyObject recv) {
        return RubyComplex.newComplexCanonicalize(context, RubyFixnum.zero(context.getRuntime()));
    }
    
    /** nilclass_to_r
     * 
     */
    @JRubyMethod(name = "to_r", compat = CompatVersion.RUBY1_9)
    public static IRubyObject to_r(ThreadContext context, IRubyObject recv) {
        return RubyRational.newRationalCanonicalize(context, RubyFixnum.zero(context.getRuntime()));
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2007 Koichiro Ohba <koichiro@meadowy.org>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.UnsupportedCharsetException;
import java.util.HashMap;
import java.util.Map;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;

import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;
import org.jruby.util.KCode;

@JRubyModule(name="NKF")
public class RubyNKF {
    public static final NKFCharset AUTO = new NKFCharset(0, "x-JISAutoDetect");
    public static final NKFCharset JIS = new NKFCharset(1, "iso-2022-jp");
    public static final NKFCharset EUC = new NKFCharset(2, "EUC-JP");
    public static final NKFCharset SJIS = new NKFCharset(3, "Windows-31J");
    public static final NKFCharset BINARY = new NKFCharset(4, null);
    public static final NKFCharset NOCONV = new NKFCharset(4, null);
    public static final NKFCharset UNKNOWN = new NKFCharset(0, null);
    public static final NKFCharset ASCII = new NKFCharset(5, "iso-8859-1");
    public static final NKFCharset UTF8 = new NKFCharset(6, "UTF-8");
    public static final NKFCharset UTF16 = new NKFCharset(8, "UTF-16");
    public static final NKFCharset UTF32 = new NKFCharset(12, "UTF-32");
    public static final NKFCharset OTHER = new NKFCharset(16, null);

    public static class NKFCharset {
        private final int value;
        private final String charset;

        public NKFCharset(int v, String c) {
            value = v;
            charset = c;
        }
        
        public int getValue() {
            return value;
        }
        
        public String getCharset() {
            return charset;
        }
    }

    public static void createNKF(Ruby runtime) {
        RubyModule nkfModule = runtime.defineModule("NKF");

        nkfModule.defineConstant("AUTO", RubyFixnum.newFixnum(runtime, AUTO.getValue()));
        nkfModule.defineConstant("JIS", RubyFixnum.newFixnum(runtime, JIS.getValue()));
        nkfModule.defineConstant("EUC", RubyFixnum.newFixnum(runtime, EUC.getValue()));
        nkfModule.defineConstant("SJIS", RubyFixnum.newFixnum(runtime, SJIS.getValue()));
        nkfModule.defineConstant("BINARY", RubyFixnum.newFixnum(runtime, BINARY.getValue()));
        nkfModule.defineConstant("NOCONV", RubyFixnum.newFixnum(runtime, NOCONV.getValue()));
        nkfModule.defineConstant("UNKNOWN", RubyFixnum.newFixnum(runtime, UNKNOWN.getValue()));
        nkfModule.defineConstant("ASCII", RubyFixnum.newFixnum(runtime, ASCII.getValue()));
        nkfModule.defineConstant("UTF8", RubyFixnum.newFixnum(runtime, UTF8.getValue()));
        nkfModule.defineConstant("UTF16", RubyFixnum.newFixnum(runtime, UTF16.getValue()));
        nkfModule.defineConstant("UTF32", RubyFixnum.newFixnum(runtime, UTF32.getValue()));
        nkfModule.defineConstant("OTHER", RubyFixnum.newFixnum(runtime, OTHER.getValue()));
        
        RubyString version = runtime.newString("2.0.7 (JRuby 2007-05-11)");
        RubyString nkfVersion = runtime.newString("2.0.7");
        RubyString nkfDate = runtime.newString("2007-05-11");

        ThreadContext context = runtime.getCurrentContext();
        
        version.freeze(context);
        nkfVersion.freeze(context);
        nkfDate.freeze(context);

        nkfModule.defineAnnotatedMethods(RubyNKF.class);
    }

    @JRubyMethod(name = "guess", required = 1, module = true)
    public static IRubyObject guess(ThreadContext context, IRubyObject recv, IRubyObject s) {
        Ruby runtime = context.getRuntime();
        if (!s.respondsTo("to_str")) {
            throw runtime.newTypeError("can't convert " + s.getMetaClass() + " into String");
        }
        ByteList bytes = s.convertToString().getByteList();
        ByteBuffer buf = ByteBuffer.wrap(bytes.unsafeBytes(), bytes.begin(), bytes.length());
        CharsetDecoder decoder = Charset.forName("x-JISAutoDetect").newDecoder();
        try {
            decoder.decode(buf);
        } catch (CharacterCodingException e) {
            return runtime.newFixnum(UNKNOWN.getValue());
        }
        if (!decoder.isCharsetDetected()) {
            return runtime.newFixnum(UNKNOWN.getValue());
        }
        Charset charset = decoder.detectedCharset();
        String name = charset.name();
//        System.out.println("detect: " + name + "\n");
        if ("Shift_JIS".equals(name))
            return runtime.newFixnum(SJIS.getValue());
        if ("windows-31j".equals(name))
            return runtime.newFixnum(SJIS.getValue());
        else if ("EUC-JP".equals(name))
            return runtime.newFixnum(EUC.getValue());
        else if ("ISO-2022-JP".equals(name))
            return runtime.newFixnum(JIS.getValue());
        else
            return runtime.newFixnum(UNKNOWN.getValue());
    }
    
    @JRubyMethod(name = "guess1", required = 1, module = true)
    public static IRubyObject guess1(ThreadContext context, IRubyObject recv, IRubyObject str) {
        return guess(context, recv, str);
    }
    
    @JRubyMethod(name = "guess2", required = 1, module = true)
    public static IRubyObject guess2(ThreadContext context, IRubyObject recv, IRubyObject str) {
        return guess(context, recv, str);
    }
    
    @JRubyMethod(name = "nkf", required = 2, module = true)
    public static IRubyObject nkf(ThreadContext context, IRubyObject recv, IRubyObject opt, IRubyObject str) {
        Ruby runtime = context.getRuntime();
        
        if (!opt.respondsTo("to_str")) {
            throw runtime.newTypeError("can't convert " + opt.getMetaClass() + " into String");
        }
        
        if (!str.respondsTo("to_str")) {
            throw runtime.newTypeError("can't convert " + str.getMetaClass() + " into String");
        }
        
        Map<String, NKFCharset> options = parseOpt(opt.convertToString().toString());
        
        NKFCharset nc = options.get("input");
        if (nc.getValue() == AUTO.getValue()) {
            KCode kcode = runtime.getKCode();
            if (kcode == KCode.SJIS) {
                nc = SJIS;
            } else if (kcode == KCode.EUC) {
                nc = EUC;
            } else if (kcode == KCode.UTF8) {
                nc = UTF8;
            }
        }
        String decodeCharset = nc.getCharset();
        String encodeCharset = options.get("output").getCharset();
        
        return convert(context, decodeCharset, encodeCharset, str);
    }
    
    private static IRubyObject convert(ThreadContext context, String decodeCharset, 
            String encodeCharset, IRubyObject str) {
        Ruby runtime = context.getRuntime();
        CharsetDecoder decoder;
        CharsetEncoder encoder;
        try {
            decoder = Charset.forName(decodeCharset).newDecoder();
            encoder = Charset.forName(encodeCharset).newEncoder();
        } catch (UnsupportedCharsetException e) {
            throw runtime.newArgumentError("invalid encoding");
        }
        
        ByteList bytes = str.convertToString().getByteList();
        ByteBuffer buf = ByteBuffer.wrap(bytes.unsafeBytes(), bytes.begin(), bytes.length());
        try {
            CharBuffer cbuf = decoder.decode(buf);
            buf = encoder.encode(cbuf);
        } catch (CharacterCodingException e) {
            throw runtime.newArgumentError("invalid encoding");
        }
        byte[] arr = buf.array();
        
        return runtime.newString(new ByteList(arr, 0, buf.limit()));
        
    }

    private static int optionUTF(String s, int i) {
        int n = 8;
        if (i+1 < s.length() && Character.isDigit(s.charAt(i+1))) {
            n = Character.digit(s.charAt(i+1), 10);
            if (i+2 < s.length() && Character.isDigit(s.charAt(i+2))) {
                n *= 10;
                n += Character.digit(s.charAt(i+2), 10);
            }
        }
        return n;
    }

    private static Map<String, NKFCharset> parseOpt(String s) {
        Map<String, NKFCharset> options = new HashMap<String, NKFCharset>();

        // default options
        options.put("input", AUTO);
        options.put("output", JIS);
        
        for (int i = 0; i < s.length(); i++) {
            switch (s.charAt(i)) {
            case 'b':
                break;
            case 'u':
                break;
            case 'j': // iso-2022-jp
                options.put("output", JIS);
                break;
            case 's': // Shift_JIS
                options.put("output", SJIS);
                break;
            case 'e': // EUC-JP
                options.put("output", EUC);
                break;
            case 'w': // UTF-8
            {
                int n = optionUTF(s, i);
                if (n == 32)
                    options.put("output", UTF32);
                else if (n == 16)
                    options.put("output", UTF16);
                else
                    options.put("output", UTF8);
            }
                break;
            case 'J': // iso-2022-jp
                options.put("input", JIS);
                break;
            case 'S': // Shift_JIS
                options.put("input", SJIS);
                break;
            case 'E': // EUC-JP
                options.put("input", EUC);
                break;
            case 'W': // UTF-8
            {
                int n = optionUTF(s, i);
                if (n == 32)
                    options.put("input", UTF32);
                else if (n == 16)
                    options.put("input", UTF16);
                else
                    options.put("input", UTF8);
            }
                break;
            case 't':
                break;
            case 'r':
                break;
            case 'h':
                break;
            case 'm':
                break;
            case 'M':
                break;
            case 'l':
                break;
            case 'f':
                break;
            case 'F':
                break;
            case 'Z':
                break;
            case 'X':
                break;
            case 'x':
                break;
            case 'B':
                break;
            case 'T':
                break;
            case 'd':
                break;
            case 'c':
                break;
            case 'I':
                break;
            case 'L':
                break;
            case '-':
                if (s.charAt(i+1) == '-') {
                    // long name option
                }
            default:
            }
        }
        return options;
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/

package org.jruby;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.builtin.IRubyObject;

@JRubyClass(name="NoMethodError", parent="NameError")
public class RubyNoMethodError extends RubyNameError {
    private IRubyObject args;

    private static final ObjectAllocator NOMETHODERROR_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return new RubyNoMethodError(runtime, klass);
        }
    };

    public static RubyClass createNoMethodErrorClass(Ruby runtime, RubyClass nameErrorClass) {
        RubyClass noMethodErrorClass = runtime.defineClass("NoMethodError", nameErrorClass, NOMETHODERROR_ALLOCATOR);
        
        noMethodErrorClass.defineAnnotatedMethods(RubyNoMethodError.class);

        return noMethodErrorClass;
    }

    protected RubyNoMethodError(Ruby runtime, RubyClass exceptionClass) {
        super(runtime, exceptionClass, exceptionClass.getName());
        this.args = runtime.getNil();
    }
    
    public RubyNoMethodError(Ruby runtime, RubyClass exceptionClass, String message, String name, IRubyObject args) {
        super(runtime, exceptionClass, message,  name);
        this.args = args;
    }    

    @JRubyMethod(name = "initialize", optional = 3, frame = true)
    public IRubyObject initialize(IRubyObject[] args, Block block) {
        if (args.length > 2) {
            this.args = args[args.length - 1];
            IRubyObject []tmpArgs = new IRubyObject[args.length - 1];
            System.arraycopy(args, 0, tmpArgs, 0, tmpArgs.length);
            args = tmpArgs;
        } else {
            this.args = getRuntime().getNil();
        }

        super.initialize(args, block);
        return this;
    }
    
    @JRubyMethod(name = "args")
    public IRubyObject args() {
        return args;
    }

}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2002-2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2006 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * Copyright (C) 2006 Antti Karanta <Antti.Karanta@napa.fi>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import static org.jruby.util.Numeric.f_abs;
import static org.jruby.util.Numeric.f_arg;
import static org.jruby.util.Numeric.f_negative_p;

import java.math.BigInteger;

import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
import org.jruby.javasupport.util.RuntimeHelpers;
import org.jruby.runtime.Block;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;
import org.jruby.util.Convert;

/**
 * Base class for all numerical types in ruby.
 */
// TODO: Numeric.new works in Ruby and it does here too.  However trying to use
//   that instance in a numeric operation should generate an ArgumentError. Doing
//   this seems so pathological I do not see the need to fix this now.
@JRubyClass(name="Numeric", include="Comparable")
public class RubyNumeric extends RubyObject {
    
    public static RubyClass createNumericClass(Ruby runtime) {
        RubyClass numeric = runtime.defineClass("Numeric", runtime.getObject(), NUMERIC_ALLOCATOR);
        runtime.setNumeric(numeric);

        numeric.kindOf = new RubyModule.KindOf() {
            @Override
            public boolean isKindOf(IRubyObject obj, RubyModule type) {
                return obj instanceof RubyNumeric;
            }
        };

        numeric.includeModule(runtime.getComparable());
        numeric.defineAnnotatedMethods(RubyNumeric.class);

        return numeric;
    }

    protected static final ObjectAllocator NUMERIC_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return new RubyNumeric(runtime, klass);
        }
    };

    public static double DBL_EPSILON=2.2204460492503131e-16;

    private static IRubyObject convertToNum(double val, Ruby runtime) {

        if (val >= (double) RubyFixnum.MAX || val < (double) RubyFixnum.MIN) {
            return RubyBignum.newBignum(runtime, val);
        }
        return RubyFixnum.newFixnum(runtime, (long) val);
    }
    
    public RubyNumeric(Ruby runtime, RubyClass metaClass) {
        super(runtime, metaClass);
    }

    public RubyNumeric(Ruby runtime, RubyClass metaClass, boolean useObjectSpace) {
        super(runtime, metaClass, useObjectSpace);
    }    

    // The implementations of these are all bonus (see TODO above)  I was going
    // to throw an error from these, but it appears to be the wrong place to
    // do it.
    public double getDoubleValue() {
        return 0;
    }

    public long getLongValue() {
        return 0;
    }
    
    public static RubyNumeric newNumeric(Ruby runtime) {
    	return new RubyNumeric(runtime, runtime.getNumeric());
    }

    /*  ================
     *  Utility Methods
     *  ================ 
     */

    /** rb_num2int, NUM2INT
     * 
     */
    public static int num2int(IRubyObject arg) {
        long num = num2long(arg);

        checkInt(arg, num);
        return (int)num;
    }
    
    /** check_int
     * 
     */
    public static void checkInt(IRubyObject arg, long num){
        String s;
        if (num < Integer.MIN_VALUE) {
            s = "small";
        } else if (num > Integer.MAX_VALUE) {
            s = "big";
        } else {
            return;
        }
        throw arg.getRuntime().newRangeError("integer " + num + " too " + s + " to convert to `int'");
    }

    /**
     * NUM2CHR
     */
    public static byte num2chr(IRubyObject arg) {
        if (arg instanceof RubyString) {
            String value = ((RubyString) arg).toString();

            if (value != null && value.length() > 0) return (byte) value.charAt(0);
        } 

        return (byte) num2int(arg);
    }

    /** rb_num2long and FIX2LONG (numeric.c)
     * 
     */
    public static long num2long(IRubyObject arg) {
        if (arg instanceof RubyFixnum) {
            return ((RubyFixnum) arg).getLongValue();
        }
        if (arg.isNil()) {
            throw arg.getRuntime().newTypeError("no implicit conversion from nil to integer");
        }

        if (arg instanceof RubyFloat) {
            double aFloat = ((RubyFloat) arg).getDoubleValue();
            if (aFloat <= (double) Long.MAX_VALUE && aFloat >= (double) Long.MIN_VALUE) {
                return (long) aFloat;
            } else {
                // TODO: number formatting here, MRI uses "%-.10g", 1.4 API is a must?
                throw arg.getRuntime().newRangeError("float " + aFloat + " out of range of integer");
            }
        } else if (arg instanceof RubyBignum) {
            return RubyBignum.big2long((RubyBignum) arg);
        }
        return arg.convertToInteger().getLongValue();
    }

    /** rb_dbl2big + LONG2FIX at once (numeric.c)
     * 
     */
    public static IRubyObject dbl2num(Ruby runtime, double val) {
        if (Double.isInfinite(val)) {
            throw runtime.newFloatDomainError(val < 0 ? "-Infinity" : "Infinity");
        }
        if (Double.isNaN(val)) {
            throw runtime.newFloatDomainError("NaN");
        }
        return convertToNum(val,runtime);
    }

    /** rb_num2dbl and NUM2DBL
     * 
     */
    public static double num2dbl(IRubyObject arg) {
        if (arg instanceof RubyFloat) {
            return ((RubyFloat) arg).getDoubleValue();
        } else if (arg instanceof RubyString) {
            throw arg.getRuntime().newTypeError("no implicit conversion to float from string");
        } else if (arg == arg.getRuntime().getNil()) {
            throw arg.getRuntime().newTypeError("no implicit conversion to float from nil");
        }
        return arg.convertToFloat().getDoubleValue();
    }

    /** rb_dbl_cmp (numeric.c)
     * 
     */
    public static IRubyObject dbl_cmp(Ruby runtime, double a, double b) {
        if (Double.isNaN(a) || Double.isNaN(b)) {
            return runtime.getNil();
        }
        if (a > b) {
            return RubyFixnum.one(runtime);
        }
        if (a < b) {
            return RubyFixnum.minus_one(runtime);
        }
        return RubyFixnum.zero(runtime);
    }

    public static long fix2long(IRubyObject arg) {
        return ((RubyFixnum) arg).getLongValue();
    }

    public static int fix2int(IRubyObject arg) {
        long num = arg instanceof RubyFixnum ? fix2long(arg) : num2long(arg);

        checkInt(arg, num);
        return (int) num;
        }

    public static RubyInteger str2inum(Ruby runtime, RubyString str, int base) {
        return str2inum(runtime,str,base,false);
    }

    public static RubyNumeric int2fix(Ruby runtime, long val) {
        return RubyFixnum.newFixnum(runtime,val);
    }

    /** rb_num2fix
     * 
     */
    public static IRubyObject num2fix(IRubyObject val) {
        if (val instanceof RubyFixnum) {
            return val;
        }
        if (val instanceof RubyBignum) {
            // any BigInteger is bigger than Fixnum and we don't have FIXABLE
            throw val.getRuntime().newRangeError("integer " + val + " out of range of fixnum");
        }
        return RubyFixnum.newFixnum(val.getRuntime(), num2long(val));
    }

    /**
     * Converts a string representation of an integer to the integer value. 
     * Parsing starts at the beginning of the string (after leading and 
     * trailing whitespace have been removed), and stops at the end or at the 
     * first character that can't be part of an integer.  Leading signs are
     * allowed. If <code>base</code> is zero, strings that begin with '0[xX]',
     * '0[bB]', or '0' (optionally preceded by a sign) will be treated as hex, 
     * binary, or octal numbers, respectively.  If a non-zero base is given, 
     * only the prefix (if any) that is appropriate to that base will be 
     * parsed correctly.  For example, if the base is zero or 16, the string
     * "0xff" will be converted to 256, but if the base is 10, it will come out 
     * as zero, since 'x' is not a valid decimal digit.  If the string fails 
     * to parse as a number, zero is returned.
     * 
     * @param runtime  the ruby runtime
     * @param str   the string to be converted
     * @param base  the expected base of the number (for example, 2, 8, 10, 16),
     *              or 0 if the method should determine the base automatically 
     *              (defaults to 10). Values 0 and 2-36 are permitted. Any other
     *              value will result in an ArgumentError.
     * @param strict if true, enforce the strict criteria for String encoding of
     *               numeric values, as required by Integer('n'), and raise an
     *               exception when those criteria are not met. Otherwise, allow
     *               lax expression of values, as permitted by String#to_i, and
     *               return a value in almost all cases (excepting illegal radix).
     *               TODO: describe the rules/criteria
     * @return  a RubyFixnum or (if necessary) a RubyBignum representing 
     *          the result of the conversion, which will be zero if the 
     *          conversion failed.
     */
    public static RubyInteger str2inum(Ruby runtime, RubyString str, int base, boolean strict) {
        if (base != 0 && (base < 2 || base > 36)) {
            throw runtime.newArgumentError("illegal radix " + base);
        }
        ByteList bytes = str.getByteList();
        try {
            return runtime.newFixnum(Convert.byteListToLong(bytes, base, strict));
        } catch (InvalidIntegerException e) {
            return str2inumIIE(strict, runtime, str);
        } catch (NumberTooLargeException e) {
            return str2inumNTLE(strict, runtime, str, bytes, base);
        }
    }

    private static RubyInteger str2inumIIE(boolean strict, Ruby runtime, RubyString str) throws RaiseException {
        if (strict) {
            throw runtime.newArgumentError("invalid value for Integer: " + str.callMethod(runtime.getCurrentContext(), "inspect").toString());
        }
        return RubyFixnum.zero(runtime);
    }
    
    private static RubyInteger str2inumNTLE(boolean strict, Ruby runtime, RubyString str, ByteList bytes, int base) {
        try {
            BigInteger bi = Convert.byteListToBigInteger(bytes, base, strict);
            return new RubyBignum(runtime, bi);
        } catch (InvalidIntegerException e2) {
            return str2inumIIE(strict, runtime, str);
        }
    }

    public static RubyFloat str2fnum(Ruby runtime, RubyString arg) {
        return str2fnum(runtime,arg,false);
    }

    /**
     * Converts a string representation of a floating-point number to the 
     * numeric value.  Parsing starts at the beginning of the string (after 
     * leading and trailing whitespace have been removed), and stops at the 
     * end or at the first character that can't be part of a number.  If 
     * the string fails to parse as a number, 0.0 is returned.
     * 
     * @param runtime  the ruby runtime
     * @param arg   the string to be converted
     * @param strict if true, enforce the strict criteria for String encoding of
     *               numeric values, as required by Float('n'), and raise an
     *               exception when those criteria are not met. Otherwise, allow
     *               lax expression of values, as permitted by String#to_f, and
     *               return a value in all cases.
     *               TODO: describe the rules/criteria
     * @return  a RubyFloat representing the result of the conversion, which
     *          will be 0.0 if the conversion failed.
     */
    public static RubyFloat str2fnum(Ruby runtime, RubyString arg, boolean strict) {
        final double ZERO = 0.0;
        
        try {
            return new RubyFloat(runtime,Convert.byteListToDouble(arg.getByteList(),strict));
        } catch (NumberFormatException e) {
            if (strict) {
                throw runtime.newArgumentError("invalid value for Float(): "
                        + arg.callMethod(runtime.getCurrentContext(), "inspect").toString());
            }
            return new RubyFloat(runtime,ZERO);
        }
    }
    
    /** Numeric methods. (num_*)
     *
     */
    
    protected IRubyObject[] getCoerced(ThreadContext context, IRubyObject other, boolean error) {
        IRubyObject result;
        
        try {
            result = other.callMethod(context, "coerce", this);
        } catch (RaiseException e) {
            if (error) {
                throw getRuntime().newTypeError(
                        other.getMetaClass().getName() + " can't be coerced into " + getMetaClass().getName());
            }
             
            return null;
        }
        
        if (!(result instanceof RubyArray) || ((RubyArray)result).getLength() != 2) {
            throw getRuntime().newTypeError("coerce must return [x, y]");
        }
        
        return ((RubyArray)result).toJavaArray();
    }

    protected IRubyObject callCoerced(ThreadContext context, String method, IRubyObject other, boolean err) {
        IRubyObject[] args = getCoerced(context, other, err);
        if(args == null) {
            return getRuntime().getNil();
        }
        return args[0].callMethod(context, method, args[1]);
    }

    protected IRubyObject callCoerced(ThreadContext context, String method, IRubyObject other) {
        IRubyObject[] args = getCoerced(context, other, false);
        if(args == null) {
            return getRuntime().getNil();
        }
        return args[0].callMethod(context, method, args[1]);
    }
    
    // beneath are rewritten coercions that reflect MRI logic, the aboves are used only by RubyBigDecimal

    /** coerce_body
     *
     */
    protected final IRubyObject coerceBody(ThreadContext context, IRubyObject other) {
        return other.callMethod(context, "coerce", this);
    }

    /** do_coerce
     * 
     */
    protected final RubyArray doCoerce(ThreadContext context, IRubyObject other, boolean err) {
        IRubyObject result;
        try {
            result = coerceBody(context, other);
        } catch (RaiseException e) {
            if (err) {
                throw getRuntime().newTypeError(
                        other.getMetaClass().getName() + " can't be coerced into " + getMetaClass().getName());
            }
            return null;
        }
    
        if (!(result instanceof RubyArray) || ((RubyArray) result).getLength() != 2) {
            throw getRuntime().newTypeError("coerce must return [x, y]");
        }
        return (RubyArray) result;
    }

    /** rb_num_coerce_bin
     *  coercion taking two arguments
     */
    protected final IRubyObject coerceBin(ThreadContext context, String method, IRubyObject other) {
        RubyArray ary = doCoerce(context, other, true);
        return (ary.eltInternal(0)).callMethod(context, method, ary.eltInternal(1));
    }
    
    /** rb_num_coerce_cmp
     *  coercion used for comparisons
     */
    protected final IRubyObject coerceCmp(ThreadContext context, String method, IRubyObject other) {
        RubyArray ary = doCoerce(context, other, false);
        if (ary == null) {
            return getRuntime().getNil(); // MRI does it!
        } 
        return (ary.eltInternal(0)).callMethod(context, method, ary.eltInternal(1));
    }
        
    /** rb_num_coerce_relop
     *  coercion used for relative operators
     */
    protected final IRubyObject coerceRelOp(ThreadContext context, String method, IRubyObject other) {
        RubyArray ary = doCoerce(context, other, false);
        if (ary == null) {
            return RubyComparable.cmperr(this, other);
        }

        return unwrapCoerced(context, method, other, ary);
    }
    
    private final IRubyObject unwrapCoerced(ThreadContext context, String method, IRubyObject other, RubyArray ary) {
        IRubyObject result = (ary.eltInternal(0)).callMethod(context, method, ary.eltInternal(1));
        if (result.isNil()) {
            return RubyComparable.cmperr(this, other);
        }
        return result;
    }
        
    public RubyNumeric asNumeric() {
        return this;
    }

    /*  ================
     *  Instance Methods
     *  ================ 
     */

    /** num_sadded
     *
     */
    @JRubyMethod(name = "singleton_method_added")
    public IRubyObject sadded(IRubyObject name) {
        throw getRuntime().newTypeError("can't define singleton method " + name + " for " + getType().getName());
    } 
        
    /** num_init_copy
     *
     */
    @Override
    @JRubyMethod(name = "initialize_copy", visibility = Visibility.PRIVATE)
    public IRubyObject initialize_copy(IRubyObject arg) {
        throw getRuntime().newTypeError("can't copy " + getType().getName());
    }
    
    /** num_coerce
     *
     */
    @JRubyMethod(name = "coerce")
    public IRubyObject coerce(IRubyObject other) {
        if (getClass() == other.getClass()) return getRuntime().newArray(other, this);

        IRubyObject cdr = RubyKernel.new_float(this, this);
        IRubyObject car = RubyKernel.new_float(this, other);

        return getRuntime().newArray(car, cdr);
    }

    /** num_uplus
     *
     */
    @JRubyMethod(name = "+@")
    public IRubyObject op_uplus() {
        return this;
    }

    /** num_uminus
     *
     */
    @JRubyMethod(name = "-@")
    public IRubyObject op_uminus(ThreadContext context) {
        RubyFixnum zero = RubyFixnum.zero(getRuntime());
        RubyArray ary = zero.doCoerce(context, this, true);
        return ary.eltInternal(0).callMethod(context, MethodIndex.OP_MINUS, "-", ary.eltInternal(1));
    }
    
    /** num_cmp
     *
     */
    @JRubyMethod(name = "<=>")
    public IRubyObject op_cmp(IRubyObject other) {
        if (this == other) { // won't hurt fixnums
            return RubyFixnum.zero(getRuntime());
        }
        return getRuntime().getNil();
    }

    /** num_eql
     *
     */
    @JRubyMethod(name = "eql?")
    public IRubyObject eql_p(ThreadContext context, IRubyObject other) {
        if (getClass() != other.getClass()) return getRuntime().getFalse();
        return equalInternal(context, this, other) ? getRuntime().getTrue() : getRuntime().getFalse();
    }

    /** num_quo
     *
     */
    @JRubyMethod(name = "quo")
    public IRubyObject quo(ThreadContext context, IRubyObject other) {
        return callMethod(context, "/", other);
    }
    
    /** num_quo
    *
    */
    @JRubyMethod(name = "quo", compat = CompatVersion.RUBY1_9)
    public IRubyObject quo_19(ThreadContext context, IRubyObject other) {
        return RubyRational.newRationalRaw(context.getRuntime(), this).callMethod(context, "/", other);
    }

    /** num_div
     * 
     */
    @JRubyMethod(name = "div")
    public IRubyObject div(ThreadContext context, IRubyObject other) {
        return callMethod(context, "/", other).convertToFloat().floor();
    }

    /** num_divmod
     * 
     */
    @JRubyMethod(name = "divmod")
    public IRubyObject divmod(ThreadContext context, IRubyObject other) {
        return RubyArray.newArray(getRuntime(), div(context, other), modulo(context, other));
    }
    
    /** num_fdiv (1.9) */
    @JRubyMethod(name = "fdiv", compat = CompatVersion.RUBY1_9)
    public IRubyObject fdiv(ThreadContext context, IRubyObject other) {
        return RuntimeHelpers.invoke(context, this.convertToFloat(), "/", other);
    }

    /** num_modulo
     *
     */
    @JRubyMethod(name = "modulo")
    public IRubyObject modulo(ThreadContext context, IRubyObject other) {
        return callMethod(context, "%", other);
    }

    /** num_remainder
     *
     */
    @JRubyMethod(name = "remainder")
    public IRubyObject remainder(ThreadContext context, IRubyObject dividend) {
        IRubyObject z = callMethod(context, "%", dividend);
        IRubyObject x = this;
        RubyFixnum zero = RubyFixnum.zero(getRuntime());

        if (!equalInternal(context, z, zero) &&
                ((x.callMethod(context, MethodIndex.OP_LT, "<", zero).isTrue() &&
                dividend.callMethod(context, MethodIndex.OP_GT, ">", zero).isTrue()) ||
                (x.callMethod(context, MethodIndex.OP_GT, ">", zero).isTrue() &&
                dividend.callMethod(context, MethodIndex.OP_LT, "<", zero).isTrue()))) {
            return z.callMethod(context, MethodIndex.OP_MINUS, "-", dividend);
        } else {
            return z;
        }
    }

    /** num_abs
     *
     */
    @JRubyMethod(name = "abs")
    public IRubyObject abs(ThreadContext context) {
        if (callMethod(context, MethodIndex.OP_LT, "<", RubyFixnum.zero(getRuntime())).isTrue()) {
            return callMethod(context, "-@");
        }
        return this;
    }
    
    /** num_to_int
     * 
     */
    @JRubyMethod(name = "to_int")
    public IRubyObject to_int(ThreadContext context) {
        return RuntimeHelpers.invoke(context, this, "to_i");
    }

    /** num_scalar_p
    *
    */
    @JRubyMethod(name = "scalar?", compat = CompatVersion.RUBY1_9)
    public IRubyObject scalar_p() {
        return getRuntime().getTrue();
    }

    /** num_int_p
     *
     */
    @JRubyMethod(name = "integer?")
    public IRubyObject integer_p() {
        return getRuntime().getFalse();
    }
    
    /** num_zero_p
     *
     */
    @JRubyMethod(name = "zero?")
    public IRubyObject zero_p(ThreadContext context) {
        return equalInternal(context, this, RubyFixnum.zero(getRuntime())) ? getRuntime().getTrue() : getRuntime().getFalse();
    }
    
    /** num_nonzero_p
     *
     */
    @JRubyMethod(name = "nonzero?")
    public IRubyObject nonzero_p(ThreadContext context) {
        if (callMethod(context, "zero?").isTrue()) {
            return getRuntime().getNil();
        }
        return this;
    }

    /** num_floor
     *
     */
    @JRubyMethod(name = "floor")
    public IRubyObject floor() {
        return convertToFloat().floor();
    }
        
    /** num_ceil
     *
     */
    @JRubyMethod(name = "ceil")
    public IRubyObject ceil() {
        return convertToFloat().ceil();
    }

    /** num_round
     *
     */
    @JRubyMethod(name = "round")
    public IRubyObject round() {
        return convertToFloat().round();
    }

    /** num_truncate
     *
     */
    @JRubyMethod(name = "truncate")
    public IRubyObject truncate() {
        return convertToFloat().truncate();
    }
    
    @JRubyMethod(name = "step", required = 1, optional = 1, frame = true)
    public IRubyObject step(ThreadContext context, IRubyObject[] args, Block block) {
        switch (args.length) {
        case 0: throw context.getRuntime().newArgumentError(0, 1);
        case 1: return step(context, args[0], block);
        case 2: return step(context, args[0], args[1], block);
        default: throw context.getRuntime().newArgumentError(args.length, 2);
        }
    }
    
    @JRubyMethod(name = "step", frame = true)
    public IRubyObject step(ThreadContext context, IRubyObject arg0, Block block) {
        return step(context, arg0, RubyFixnum.one(context.getRuntime()), block);
    }
    
    @JRubyMethod(name = "step", frame = true)
    public IRubyObject step(ThreadContext context, IRubyObject to, IRubyObject step, Block block) {
        if (this instanceof RubyFixnum && to instanceof RubyFixnum && step instanceof RubyFixnum) {
            long value = getLongValue();
            long end = ((RubyFixnum) to).getLongValue();
            long diff = ((RubyFixnum) step).getLongValue();

            if (diff == 0) {
                throw getRuntime().newArgumentError("step cannot be 0");
            }
            if (diff > 0) {
                for (long i = value; i <= end; i += diff) {
                    block.yield(context, RubyFixnum.newFixnum(getRuntime(), i));
                }
            } else {
                for (long i = value; i >= end; i += diff) {
                    block.yield(context, RubyFixnum.newFixnum(getRuntime(), i));
                }
            }
        } else if (this instanceof RubyFloat || to instanceof RubyFloat || step instanceof RubyFloat) {
            double beg = num2dbl(this);
            double end = num2dbl(to);
            double unit = num2dbl(step);

            if (unit == 0) {
                throw getRuntime().newArgumentError("step cannot be 0");
            }           
            
            double n = (end - beg)/unit;
            double err = (Math.abs(beg) + Math.abs(end) + Math.abs(end - beg)) / Math.abs(unit) * DBL_EPSILON;
            
            if (err>0.5) {
                err=0.5;            
            }
            n = Math.floor(n + err) + 1;
            
            for(double i = 0; i < n; i++){
                block.yield(context, RubyFloat.newFloat(getRuntime(), i * unit + beg));
            }

        } else {
            RubyNumeric i = this;
            
            int cmp;
            String cmpString;
            if (((RubyBoolean) step.callMethod(context, MethodIndex.OP_GT, ">", RubyFixnum.zero(getRuntime()))).isTrue()) {
                cmp = MethodIndex.OP_GT;
            } else {
                cmp = MethodIndex.OP_LT;
            }
            cmpString = MethodIndex.NAMES.get(cmp);

            while (true) {
                if (i.callMethod(context, cmp, cmpString, to).isTrue()) {
                    break;
                }
                block.yield(context, i);
                i = (RubyNumeric) i.callMethod(context, MethodIndex.OP_PLUS, "+", step);
            }
        }
        return this;
    }

    /** num_equal, doesn't override RubyObject.op_equal
     *
     */
    protected final IRubyObject op_num_equal(ThreadContext context, IRubyObject other) {
        // it won't hurt fixnums
        if (this == other)  return getRuntime().getTrue();

        return other.callMethod(context, MethodIndex.EQUALEQUAL, "==", this);
    }

    /** num_numerator
     * 
     */
    @JRubyMethod(name = "numerator", compat = CompatVersion.RUBY1_9)
    public IRubyObject numerator(ThreadContext context) {
        return RubyRational.newRationalConvert(context, this).callMethod(context, "numerator");
    }
    
    /** num_denominator
     * 
     */
    @JRubyMethod(name = "denominator", compat = CompatVersion.RUBY1_9)
    public IRubyObject denominator(ThreadContext context) {
        return RubyRational.newRationalConvert(context, this).callMethod(context, "denominator");
    }

    /** numeric_to_c
     * 
     */
    @JRubyMethod(name = "to_c", compat = CompatVersion.RUBY1_9)
    public IRubyObject to_c(ThreadContext context) {
        return RubyComplex.newComplexCanonicalize(context, this);
    }

    /** numeric_re
     * 
     */
    @JRubyMethod(name = "re", compat = CompatVersion.RUBY1_9)
    public IRubyObject re(ThreadContext context) {
        return RubyComplex.newComplexConvert(context, this);
    }

    /** numeric_im
     * 
     */
    @JRubyMethod(name = "im", compat = CompatVersion.RUBY1_9)
    public IRubyObject im(ThreadContext context) {
        return RubyComplex.newComplexConvert(context, RubyFixnum.zero(context.getRuntime()), this);
    }

    /** numeric_real
     * 
     */
    @JRubyMethod(name = "real", compat = CompatVersion.RUBY1_9)
    public IRubyObject real(ThreadContext context) {
        return this;
    }

    /** numeric_image
     * 
     */
    @JRubyMethod(name = {"image", "imag"}, compat = CompatVersion.RUBY1_9)
    public IRubyObject image(ThreadContext context) {
        return RubyFixnum.zero(context.getRuntime());
    }

    /** numeric_arg
     * 
     */
    @JRubyMethod(name = "arg", compat = CompatVersion.RUBY1_9)
    public IRubyObject arg(ThreadContext context) {
        if (!f_negative_p(context, this)) return RubyFixnum.zero(context.getRuntime());
        return context.getRuntime().getMath().fastFetchConstant("PI");
    }    

    /** numeric_polar
     * 
     */
    @JRubyMethod(name = "polar", compat = CompatVersion.RUBY1_9)
    public IRubyObject polar(ThreadContext context) {
        return context.getRuntime().newArray(f_abs(context, this), f_arg(context, this));
    }    

    /** numeric_real
     * 
     */
    @JRubyMethod(name = "conjugate", compat = CompatVersion.RUBY1_9)
    public IRubyObject conjugate(ThreadContext context) {
        return this;
    }

    public static class InvalidIntegerException extends NumberFormatException {
        private static final long serialVersionUID = 55019452543252148L;
        
        public InvalidIntegerException() {
            super();
        }
        public InvalidIntegerException(String message) {
            super(message);
        }
        public Throwable fillInStackTrace() {
            return this;
        }
    }
    
    public static class NumberTooLargeException extends NumberFormatException {
        private static final long serialVersionUID = -1835120694982699449L;
        public NumberTooLargeException() {
            super();
        }
        public NumberTooLargeException(String message) {
            super(message);
        }
        public Throwable fillInStackTrace() {
            return this;
        }
    }
}
/*
 ***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Chad Fowler <chadfowler@chadfowler.com>
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004-2006 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004-2005 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2006 Ola Bini <ola.bini@ki.se>
 * Copyright (C) 2006 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * Copyright (C) 2007 MenTaLguY <mental@rydia.net>
 * Copyright (C) 2007 William N Dortch <bill.dortch@gmail.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.concurrent.atomic.AtomicBoolean;

import org.jruby.common.IRubyWarnings.ID;
import org.jruby.evaluator.ASTInterpreter;
import org.jruby.exceptions.JumpException;
import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.runtime.Block;
import org.jruby.runtime.CallType;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.builtin.Variable;
import org.jruby.runtime.component.VariableEntry;
import org.jruby.util.IdUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.javasupport.JavaObject;
import org.jruby.javasupport.util.RuntimeHelpers;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.builtin.InstanceVariables;
import org.jruby.runtime.builtin.InternalVariables;
import org.jruby.runtime.marshal.CoreObjectType;
import org.jruby.util.TypeConverter;

/**
 * RubyObject is the only implementation of the
 * {@link org.jruby.runtime.builtin.IRubyObject}. Every Ruby object in JRuby
 * is represented by something that is an instance of RubyObject. In
 * some of the core class implementations, this means doing a subclass
 * that extends RubyObject, in other cases it means using a simple
 * RubyObject instance and the data field to store specific
 * information about the Ruby object.
 *
 * Some care has been taken to make the implementation be as
 * monomorphic as possible, so that the Java Hotspot engine can
 * improve performance of it. That is the reason for several patterns
 * that might seem odd in this class.
 *
 * The IRubyObject interface used to have lots of methods for
 * different things, but these have now mostly been refactored into
 * several interfaces that gives access to that specific part of the
 * object. This gives us the possibility to switch out that subsystem
 * without changing interfaces again. For example, instance variable
 * and internal variables are handled this way, but the implementation
 * in RubyObject only returns "this" in {@link #getInstanceVariables()} and
 * {@link #getInternalVariables()}.
 * 
 * @author  jpetersen
 */
@JRubyClass(name="Object", include="Kernel")
public class RubyObject implements Cloneable, IRubyObject, Serializable, CoreObjectType, InstanceVariables, InternalVariables {
    
    /**
     * It's not valid to create a totally empty RubyObject. Since the
     * RubyObject is always defined in relation to a runtime, that
     * means that creating RubyObjects from outside the class might
     * cause problems.
     */
    private RubyObject(){};

    /** 
     *  A value that is used as a null sentinel in among other places
     *  the RubyArray implementation. It will cause large problems to
     *  call any methods on this object.
     */
    public static final IRubyObject NEVER = new RubyObject();

    /**
     * A value that specifies an undefined value. This value is used
     * as a sentinel for undefined constant values, and other places
     * where neither null nor NEVER makes sense.
     */
    public static final IRubyObject UNDEF = new RubyObject();
    
    // The class of this object
    protected transient RubyClass metaClass;

    /**
     * The variableTable contains variables for an object, defined as:
     * <ul>
     * <li> instance variables
     * <li> class variables (for classes/modules)
     * <li> internal variables (such as those used when marshaling RubyRange and RubyException)
     * </ul>
     * 
     * Constants are stored separately, see {@link RubyModule}. 
     * 
     */
    protected transient volatile VariableTableEntry[] variableTable;
    protected transient int variableTableSize;
    protected transient int variableTableThreshold;

    // The dataStruct is a place where custom information can be
    // contained for core implementations that doesn't necessarily
    // want to go to the trouble of creating a subclass of
    // RubyObject. The OpenSSL implementation uses this heavily to
    // save holder objects containing Java cryptography objects.
    // Java integration uses this to store the Java object ref.
    protected transient Object dataStruct;

    protected int flags; // zeroed by jvm
    public static final int ALL_F = -1;
    public static final int FALSE_F = 1 << 0;

    /**
     * This flag is a bit funny. It's used to denote that this value
     * is nil. It's a bit counterintuitive for a Java programmer to
     * not use subclassing to handle this case, since we have a
     * RubyNil subclass anyway. Well, the reason for it being a flag
     * is that the {@link #isNil()} method is called extremely often. So often
     * that it gives a good speed boost to make it monomorphic and
     * final. It turns out using a flag for this actually gives us
     * better performance than having a polymorphic {@link #isNil()} method.
     */
    public static final int NIL_F = 1 << 1;

    public static final int FROZEN_F = 1 << 2;
    public static final int TAINTED_F = 1 << 3;

    public static final int FL_USHIFT = 4;
    
    public static final int USER0_F = (1<<(FL_USHIFT+0));
    public static final int USER1_F = (1<<(FL_USHIFT+1));
    public static final int USER2_F = (1<<(FL_USHIFT+2));
    public static final int USER3_F = (1<<(FL_USHIFT+3));
    public static final int USER4_F = (1<<(FL_USHIFT+4));
    public static final int USER5_F = (1<<(FL_USHIFT+5));
    public static final int USER6_F = (1<<(FL_USHIFT+6));
    public static final int USER7_F = (1<<(FL_USHIFT+7));

    /**
     * Sets or unsets a flag on this object. The only flags that are
     * guaranteed to be valid to use as the first argument is:
     *
     * <ul>
     *  <li>{@link #FALSE_F}</li>
     *  <li>{@link NIL_F}</li>
     *  <li>{@link FROZEN_F}</li>
     *  <li>{@link TAINTED_F}</li>
     *  <li>{@link USER0_F}</li>
     *  <li>{@link USER1_F}</li>
     *  <li>{@link USER2_F}</li>
     *  <li>{@link USER3_F}</li>
     *  <li>{@link USER4_F}</li>
     *  <li>{@link USER5_F}</li>
     *  <li>{@link USER6_F}</li>
     *  <li>{@link USER7_F}</li>
     * </ul>
     *
     * @param flag the actual flag to set or unset.
     * @param set if true, the flag will be set, if false, the flag will be unset.
     */
    public final void setFlag(int flag, boolean set) {
        if (set) {
            flags |= flag;
        } else {
            flags &= ~flag;
        }
    }
    
    /**
     * Get the value of a custom flag on this object. The only
     * guaranteed flags that can be sent in to this method is:
     *
     * <ul>
     *  <li>{@link #FALSE_F}</li>
     *  <li>{@link NIL_F}</li>
     *  <li>{@link FROZEN_F}</li>
     *  <li>{@link TAINTED_F}</li>
     *  <li>{@link USER0_F}</li>
     *  <li>{@link USER1_F}</li>
     *  <li>{@link USER2_F}</li>
     *  <li>{@link USER3_F}</li>
     *  <li>{@link USER4_F}</li>
     *  <li>{@link USER5_F}</li>
     *  <li>{@link USER6_F}</li>
     *  <li>{@link USER7_F}</li>
     * </ul>
     *
     * @param flag the flag to get
     * @return true if the flag is set, false otherwise
     */
    public final boolean getFlag(int flag) { 
        return (flags & flag) != 0;
    }
    
    private transient Finalizer finalizer;
    
    /**
     * Class that keeps track of the finalizers for the object under
     * operation.
     */
    public class Finalizer implements Finalizable {
        private long id;
        private List<IRubyObject> finalizers;
        private AtomicBoolean finalized;
        
        public Finalizer(long id) {
            this.id = id;
            this.finalized = new AtomicBoolean(false);
        }
        
        public void addFinalizer(IRubyObject finalizer) {
            if (finalizers == null) {
                finalizers = new ArrayList<IRubyObject>();
            }
            finalizers.add(finalizer);
        }

        public void removeFinalizers() {
            finalizers = null;
        }
    
        @Override
        public void finalize() {
            if (finalized.compareAndSet(false, true)) {
                if (finalizers != null) {
                    for (int i = 0; i < finalizers.size(); i++) {
                        IRubyObject finalizer = finalizers.get(i);                        
                        RuntimeHelpers.invoke(
                                finalizer.getRuntime().getCurrentContext(),
                                finalizer, "call", RubyObject.this.id());
                    }
                }
            }
        }
    }

    /** 
     * Standard path for object creation. Objects are entered into ObjectSpace
     * only if ObjectSpace is enabled.
     */
    public RubyObject(Ruby runtime, RubyClass metaClass) {
        this.metaClass = metaClass;
        
        if (runtime.isObjectSpaceEnabled()) addToObjectSpace(runtime);
        if (runtime.getSafeLevel() >= 3) taint(runtime);
    }

    /**
     * Path for objects who want to decide whether they don't want to be in
     * ObjectSpace even when it is on. (notably used by objects being
     * considered immediate, they'll always pass false here)
     */
    protected RubyObject(Ruby runtime, RubyClass metaClass, boolean useObjectSpace) {
        this.metaClass = metaClass;
        
        if (useObjectSpace) addToObjectSpace(runtime);
        if (runtime.getSafeLevel() >= 3) taint(runtime);
    }

    private void addToObjectSpace(Ruby runtime) {
        assert runtime.isObjectSpaceEnabled();
        runtime.getObjectSpace().add(this);
    }
    
    /**
     * Will create the Ruby class Object in the runtime
     * specified. This method needs to take the actual class as an
     * argument because of the Object class' central part in runtime
     * initialization.
     */
    public static RubyClass createObjectClass(Ruby runtime, RubyClass objectClass) {
        objectClass.index = ClassIndex.OBJECT;

        objectClass.defineAnnotatedMethods(ObjectMethods.class);

        return objectClass;
    }
    
    /**
     * Interestingly, the Object class doesn't really have that many
     * methods for itself. Instead almost all of the Object methods
     * are really defined on the Kernel module. This class is a holder
     * for all Object methods.
     *
     * @see RubyKernel
     */
    public static class ObjectMethods {
        @JRubyMethod(name = "initialize", visibility = Visibility.PRIVATE)
        public static IRubyObject intialize(IRubyObject self) {
            return self.getRuntime().getNil();
        }
    }
    
    /**
     * Default allocator instance for all Ruby objects. The only
     * reason to not use this allocator is if you actually need to
     * have all instances of something be a subclass of RubyObject.
     *
     * @see org.jruby.runtime.ObjectAllocator
     */
    public static final ObjectAllocator OBJECT_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return new RubyObject(runtime, klass);
        }
    };

    /**
     * Will make sure that this object is added to the current object
     * space.
     *
     * @see org.jruby.runtime.ObjectSpace
     */
    public void attachToObjectSpace() {
        getRuntime().getObjectSpace().add(this);
    }
    
    /**
     * This is overridden in the other concrete Java builtins to provide a fast way
     * to determine what type they are.
     *
     * Will generally return a value from org.jruby.runtime.ClassIndex
     *
     * @see org.jruby.runtime.ClassInde
     */
    public int getNativeTypeIndex() {
        return ClassIndex.OBJECT;
    }

    /**
     * Specifically polymorphic method that are meant to be overridden
     * by modules to specify that they are modules in an easy way.
     */
    public boolean isModule() {
        return false;
    }
    
    /**
     * Specifically polymorphic method that are meant to be overridden
     * by classes to specify that they are classes in an easy way.
     */
    public boolean isClass() {
        return false;
    }

    /**
     *  Is object immediate (def: Fixnum, Symbol, true, false, nil?).
     */
    public boolean isImmediate() {
    	return false;
    }

    /** rb_make_metaclass
     *
     * Will create a new meta class, insert this in the chain of
     * classes for this specific object, and return the generated meta
     * class.
     */
    public RubyClass makeMetaClass(RubyClass superClass) {
        MetaClass klass = new MetaClass(getRuntime(), superClass); // rb_class_boot
        setMetaClass(klass);

        klass.setAttached(this);
        klass.setMetaClass(superClass.getRealClass().getMetaClass());

        return klass;
    }

    /**
     * Will return the Java interface that most closely can represent
     * this object, when working through JAva integration
     * translations.
     */
    public Class getJavaClass() {
        if (dataGetStruct() instanceof JavaObject) {
            return ((JavaObject)dataGetStruct()).getValue().getClass();
        }
        return getClass();
    }
    
    /**
     * Simple helper to print any objects.
     */
    public static void puts(Object obj) {
        System.out.println(obj.toString());
    }

    /**
     * This method is just a wrapper around the Ruby "==" method,
     * provided so that RubyObjects can be used as keys in the Java
     * HashMap object underlying RubyHash.
     */
    @Override
    public boolean equals(Object other) {
        return other == this || 
                other instanceof IRubyObject && 
                callMethod(getRuntime().getCurrentContext(), MethodIndex.EQUALEQUAL, "==", (IRubyObject) other).isTrue();
    }

    /**
     * The default toString method is just a wrapper that calls the
     * Ruby "to_s" method.
     */
    @Override
    public String toString() {
        return RuntimeHelpers.invoke(getRuntime().getCurrentContext(), this, "to_s").toString();
    }

    /** 
     * Will return the runtime that this object is associated with.
     *
     * @return current runtime
     */
    public final Ruby getRuntime() {
        return getMetaClass().getClassRuntime();
    }

    /**
     * if exist return the meta-class else return the type of the object.
     *
     */
    public final RubyClass getMetaClass() {
        return metaClass;
    }

    /** 
     * Makes it possible to change the metaclass of an object. In
     * practice, this is a simple version of Smalltalks Become, except
     * that it doesn't work when we're dealing with subclasses. In
     * practice it's used to change the singleton/meta class used,
     * without changing the "real" inheritance chain.
     */
    public void setMetaClass(RubyClass metaClass) {
        this.metaClass = metaClass;
    }

    /**
     * Is this value frozen or not? Shortcut for doing
     * getFlag(FROZEN_F).
     *
     * @return true if this object is frozen, false otherwise
     */
    public boolean isFrozen() {
        return (flags & FROZEN_F) != 0;
    }

    /**
     * Sets whether this object is frozen or not. Shortcut for doing
     * setFlag(FROZEN_F, frozen).
     *
     * @param frozen should this object be frozen?
     */
    public void setFrozen(boolean frozen) {
        if (frozen) {
            flags |= FROZEN_F;
        } else {
            flags &= ~FROZEN_F;
        }
    }

    /** rb_frozen_class_p
     *
     * Helper to test whether this object is frozen, and if it is will
     * throw an exception based on the message.
     */
   protected final void testFrozen(String message) {
       if (isFrozen()) {
           throw getRuntime().newFrozenError(message + " " + getMetaClass().getName());
       }
   }

    /**
     * The actual method that checks frozen with the default frozen message from MRI.
     * If possible, call this instead of {@link #testFrozen}.
     */
   protected void checkFrozen() {
       testFrozen("can't modify frozen ");
   }

    /**
     * Gets the taint. Shortcut for getFlag(TAINTED_F).
     * 
     * @return true if this object is tainted
     */
    public boolean isTaint() {
        return (flags & TAINTED_F) != 0; 
    }

    /**
     * Sets the taint flag. Shortcut for setFlag(TAINTED_F, taint)
     *
     * @param taint should this object be tainted or not?
     */
    public void setTaint(boolean taint) {
        if (taint) {
            flags |= TAINTED_F;
        } else {
            flags &= ~TAINTED_F;
        }
    }

    /**
     * Does this object represent nil? See the docs for the {@link
     * #NIL_F} flag for more information.
     */
    public final boolean isNil() {
        return (flags & NIL_F) != 0;
    }

    /**
     * Is this value a true value or not? Based on the {@link #FALSE_F} flag.
     */
    public final boolean isTrue() {
        return (flags & FALSE_F) == 0;
    }

    /**
     * Is this value a false value or not? Based on the {@link #FALSE_F} flag.
     */
    public final boolean isFalse() {
        return (flags & FALSE_F) != 0;
    }

    /**
     * Does this object respond to the specified message? Uses a
     * shortcut if it can be proved that respond_to? haven't been
     * overridden.
     */
    public final boolean respondsTo(String name) {
        if(getMetaClass().searchMethod("respond_to?") == getRuntime().getRespondToMethod()) {
            return getMetaClass().isMethodBound(name, false);
        } else {
            return callMethod(getRuntime().getCurrentContext(),"respond_to?",getRuntime().newSymbol(name)).isTrue();
        }
    }

    /** rb_singleton_class
     * 
     * Note: this method is specialized for RubyFixnum, RubySymbol,
     * RubyNil and RubyBoolean
     *
     * Will either return the existing singleton class for this
     * object, or create a new one and return that.
     */    
    public RubyClass getSingletonClass() {
        RubyClass klass;
        
        if (getMetaClass().isSingleton() && ((MetaClass)getMetaClass()).getAttached() == this) {
            klass = getMetaClass();            
        } else {
            klass = makeMetaClass(getMetaClass());
        }
        
        klass.setTaint(isTaint());
        if (isFrozen()) klass.setFrozen(true);
        
        return klass;
    }
    
    /** rb_singleton_class_clone
     *
     * Will make sure that if the current objects class is a
     * singleton, it will get cloned.
     *
     * @return either a real class, or a clone of the current singleton class
     */
    protected RubyClass getSingletonClassClone() {
       RubyClass klass = getMetaClass();

       if (!klass.isSingleton()) return klass;

       MetaClass clone = new MetaClass(getRuntime());
       clone.flags = flags;

       if (this instanceof RubyClass) {
           clone.setMetaClass(clone);
       } else {
           clone.setMetaClass(klass.getSingletonClassClone());
       }

       clone.setSuperClass(klass.getSuperClass());

       if (klass.hasVariables()) {
           clone.syncVariables(klass.getVariableList());
       }

       klass.cloneMethods(clone);

       ((MetaClass)clone.getMetaClass()).setAttached(clone);

       ((MetaClass)clone).setAttached(((MetaClass)klass).getAttached());

       return clone;
    }

    /** init_copy
     * 
     * Initializes a copy with variable and special instance variable
     * information, and then call the initialize_copy Ruby method.
     */
    private static void initCopy(IRubyObject clone, RubyObject original) {
        assert !clone.isFrozen() : "frozen object (" + clone.getMetaClass().getName() + ") allocated";

        original.copySpecialInstanceVariables(clone);

        if (original.hasVariables()) {
            clone.syncVariables(original.getVariableList());
        }

        /* FIXME: finalizer should be dupped here */
        clone.callMethod(clone.getRuntime().getCurrentContext(), "initialize_copy", original);
    }

    /** OBJ_INFECT
     *
     * Infects this object with traits from the argument obj. In real
     * terms this currently means that if obj is tainted, this object
     * will get tainted too. It's possible to hijack this method to do
     * other infections if that would be interesting.
     */
    public IRubyObject infectBy(IRubyObject obj) {
        if (obj.isTaint()) setTaint(true);
        return this;
    }

    /**
     * The protocol for super method invocation is a bit complicated
     * in Ruby. In real terms it involves first finding the real
     * implementation class (the super class), getting the name of the
     * method to call from the frame, and then invoke that on the
     * super class with the current self as the actual object
     * invoking.
     */
    public IRubyObject callSuper(ThreadContext context, IRubyObject[] args, Block block) {
        RubyModule klazz = context.getFrameKlazz();

        RubyClass superClass = RuntimeHelpers.findImplementerIfNecessary(getMetaClass(), klazz).getSuperClass();
        
        if (superClass == null) {
            String name = context.getFrameName(); 
            return RuntimeHelpers.callMethodMissing(context, this, klazz.searchMethod(name), name, args, this, CallType.SUPER, block);
        }
        return RuntimeHelpers.invokeAs(context, superClass, this, context.getFrameName(), args, CallType.SUPER, block);
    }    

    /**
     * Will invoke a named method with no arguments and no block.
     */
    public final IRubyObject callMethod(ThreadContext context, String name) {
        return RuntimeHelpers.invoke(context, this, name);
    }

    /**
     * Will invoke a named method with one argument and no block with
     * functional invocation.
     */
     public final IRubyObject callMethod(ThreadContext context, String name, IRubyObject arg) {
        return RuntimeHelpers.invoke(context, this, name, arg);
    }

    /**
     * Will invoke a named method with the supplied arguments and no
     * block with functional invocation.
     */
    public final IRubyObject callMethod(ThreadContext context, String name, IRubyObject[] args) {
        return RuntimeHelpers.invoke(context, this, name, args);
    }

    /**
     * Will invoke a named method with the supplied arguments and
     * supplied block with functional invocation.
     */
    public final IRubyObject callMethod(ThreadContext context, String name, IRubyObject[] args, Block block) {
        return RuntimeHelpers.invoke(context, this, name, args, block);
    }

    /**
     * Will invoke an indexed method with the no arguments and no
     * block.
     */
    public final IRubyObject callMethod(ThreadContext context, int methodIndex, String name) {
        return RuntimeHelpers.invoke(context, this, name);
    }

    /**
     * Will invoke an indexed method with the one argument and no
     * block with a functional invocation.
     */
    public final IRubyObject callMethod(ThreadContext context, int methodIndex, String name, IRubyObject arg) {
        return RuntimeHelpers.invoke(context, this, name, arg, Block.NULL_BLOCK);
    }

    /**
     * Call the Ruby initialize method with the supplied arguments and block.
     */
    public final void callInit(IRubyObject[] args, Block block) {
        callMethod(getRuntime().getCurrentContext(), "initialize", args, block);
    }

    /** rb_to_id
     *
     * Will try to convert this object to a String using the Ruby
     * "to_str" if the object isn't already a String. If this still
     * doesn't work, will throw a Ruby TypeError.
     *
     */
    public String asJavaString() {
        IRubyObject asString = checkStringType();
        if(!asString.isNil()) return ((RubyString)asString).asJavaString();
        throw getRuntime().newTypeError(inspect().toString() + " is not a symbol");
    }

    /**
     * Tries to convert this object to a Ruby Array using the "to_ary"
     * method.
     */
    public RubyArray convertToArray() {
        return (RubyArray) TypeConverter.convertToType(this, getRuntime().getArray(), MethodIndex.TO_ARY, "to_ary");
    }

    /**
     * Tries to convert this object to a Ruby Hash using the "to_hash"
     * method.
     */
    public RubyHash convertToHash() {
        return (RubyHash)TypeConverter.convertToType(this, getRuntime().getHash(), MethodIndex.TO_HASH, "to_hash");
    }
    
    /**
     * Tries to convert this object to a Ruby Float using the "to_f"
     * method.
     */
    public RubyFloat convertToFloat() {
        return (RubyFloat) TypeConverter.convertToType(this, getRuntime().getFloat(), MethodIndex.TO_F, "to_f");
    }

    /**
     * Tries to convert this object to a Ruby Integer using the "to_int"
     * method.
     */
    public RubyInteger convertToInteger() {
        return convertToInteger(MethodIndex.TO_INT, "to_int");
    }

    /**
     * Tries to convert this object to a Ruby Integer using the
     * supplied conversion method.
     */
    public RubyInteger convertToInteger(int convertMethodIndex, String convertMethod) {
        IRubyObject val = TypeConverter.convertToType(this, getRuntime().getInteger(), convertMethodIndex, convertMethod, true);
        if (!(val instanceof RubyInteger)) throw getRuntime().newTypeError(getMetaClass().getName() + "#" + convertMethod + " should return Integer");
        return (RubyInteger)val;
    }

    /**
     * Tries to convert this object to a Ruby String using the
     * "to_str" method.
     */
    public RubyString convertToString() {
        return (RubyString) TypeConverter.convertToType(this, getRuntime().getString(), MethodIndex.TO_STR, "to_str");
    }
    
    /**
     * Tries to convert this object to the specified Ruby type, using
     * a specific conversion method.
     */
    public final IRubyObject convertToType(RubyClass target, int convertMethodIndex) {
        return TypeConverter.convertToType(this, target, convertMethodIndex, (String)MethodIndex.NAMES.get(convertMethodIndex));
    }

    /** rb_obj_as_string
     *
     * First converts this object into a String using the "to_s"
     * method, infects it with the current taint and returns it. If
     * to_s doesn't return a Ruby String, {@link #anyToString} is used
     * instead.
     */
    public RubyString asString() {
        IRubyObject str = RuntimeHelpers.invoke(getRuntime().getCurrentContext(), this, "to_s");
        
        if (!(str instanceof RubyString)) return (RubyString)anyToString();
        if (isTaint()) str.setTaint(true);
        return (RubyString) str;
    }
    
    /** rb_check_string_type
     *
     * Tries to return a coerced string representation of this object,
     * using "to_str". If that returns something other than a String
     * or nil, an empty String will be returned.
     *
     */
    public IRubyObject checkStringType() {
        IRubyObject str = TypeConverter.convertToTypeWithCheck(this, getRuntime().getString(), MethodIndex.TO_STR, "to_str");
        if(!str.isNil() && !(str instanceof RubyString)) {
            str = RubyString.newEmptyString(getRuntime());
        }
        return str;
    }

    /** rb_check_array_type
    *
    * Returns the result of trying to convert this object to an Array
    * with "to_ary".
    */    
    public IRubyObject checkArrayType() {
        return TypeConverter.convertToTypeWithCheck(this, getRuntime().getArray(), MethodIndex.TO_ARY, "to_ary");
    }

    /** specific_eval
     *
     * Evaluates the block or string inside of the context of this
     * object, using the supplied arguments. If a block is given, this
     * will be yielded in the specific context of this object. If no
     * block is given then a String-like object needs to be the first
     * argument, and this string will be evaluated. Second and third
     * arguments in the args-array is optional, but can contain the
     * filename and line of the string under evaluation.
     */
    @Deprecated
    public IRubyObject specificEval(ThreadContext context, RubyModule mod, IRubyObject[] args, Block block) {
        if (block.isGiven()) {
            if (args.length > 0) throw getRuntime().newArgumentError(args.length, 0);

            return yieldUnder(context, mod, block);
        }

        if (args.length == 0) {
            throw getRuntime().newArgumentError("block not supplied");
        } else if (args.length > 3) {
            String lastFuncName = context.getFrameName();
            throw getRuntime().newArgumentError(
                "wrong # of arguments: " + lastFuncName + "(src) or " + lastFuncName + "{..}");
        }
        
        // We just want the TypeError if the argument doesn't convert to a String (JRUBY-386)
        RubyString evalStr;
        if (args[0] instanceof RubyString) {
            evalStr = (RubyString)args[0];
        } else {
            evalStr = args[0].convertToString();
        }

        String file;
        int line;
        if (args.length > 1) {
            file = args[1].convertToString().asJavaString();
            if (args.length > 2) {
                line = (int)(args[2].convertToInteger().getLongValue() - 1);
            } else {
                line = 0;
            }
        } else {
            file = "(eval)";
            line = 0;
        }

        return evalUnder(context, mod, evalStr, file, line);
    }

    /** specific_eval
     *
     * Evaluates the block or string inside of the context of this
     * object, using the supplied arguments. If a block is given, this
     * will be yielded in the specific context of this object. If no
     * block is given then a String-like object needs to be the first
     * argument, and this string will be evaluated. Second and third
     * arguments in the args-array is optional, but can contain the
     * filename and line of the string under evaluation.
     */
    public IRubyObject specificEval(ThreadContext context, RubyModule mod, Block block) {
        if (block.isGiven()) {
            return yieldUnder(context, mod, block);
        } else {
            throw context.getRuntime().newArgumentError("block not supplied");
        }
    }

    /** specific_eval
     *
     * Evaluates the block or string inside of the context of this
     * object, using the supplied arguments. If a block is given, this
     * will be yielded in the specific context of this object. If no
     * block is given then a String-like object needs to be the first
     * argument, and this string will be evaluated. Second and third
     * arguments in the args-array is optional, but can contain the
     * filename and line of the string under evaluation.
     */
    public IRubyObject specificEval(ThreadContext context, RubyModule mod, IRubyObject arg, Block block) {
        if (block.isGiven()) throw context.getRuntime().newArgumentError(1, 0);
        
        // We just want the TypeError if the argument doesn't convert to a String (JRUBY-386)
        RubyString evalStr;
        if (arg instanceof RubyString) {
            evalStr = (RubyString)arg;
        } else {
            evalStr = arg.convertToString();
        }

        String file = "(eval)";
        int line = 0;

        return evalUnder(context, mod, evalStr, file, line);
    }

    /** specific_eval
     *
     * Evaluates the block or string inside of the context of this
     * object, using the supplied arguments. If a block is given, this
     * will be yielded in the specific context of this object. If no
     * block is given then a String-like object needs to be the first
     * argument, and this string will be evaluated. Second and third
     * arguments in the args-array is optional, but can contain the
     * filename and line of the string under evaluation.
     */
    public IRubyObject specificEval(ThreadContext context, RubyModule mod, IRubyObject arg0, IRubyObject arg1, Block block) {
        if (block.isGiven()) throw context.getRuntime().newArgumentError(2, 0);
        
        // We just want the TypeError if the argument doesn't convert to a String (JRUBY-386)
        RubyString evalStr;
        if (arg0 instanceof RubyString) {
            evalStr = (RubyString)arg0;
        } else {
            evalStr = arg0.convertToString();
        }

        String file = arg1.convertToString().asJavaString();
        int line = 0;

        return evalUnder(context, mod, evalStr, file, line);
    }

    /** specific_eval
     *
     * Evaluates the block or string inside of the context of this
     * object, using the supplied arguments. If a block is given, this
     * will be yielded in the specific context of this object. If no
     * block is given then a String-like object needs to be the first
     * argument, and this string will be evaluated. Second and third
     * arguments in the args-array is optional, but can contain the
     * filename and line of the string under evaluation.
     */
    public IRubyObject specificEval(ThreadContext context, RubyModule mod, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Block block) {
        if (block.isGiven()) throw context.getRuntime().newArgumentError(2, 0);
        
        // We just want the TypeError if the argument doesn't convert to a String (JRUBY-386)
        RubyString evalStr;
        if (arg0 instanceof RubyString) {
            evalStr = (RubyString)arg0;
        } else {
            evalStr = arg0.convertToString();
        }

        String file = arg1.convertToString().asJavaString();
        int line = (int)(arg2.convertToInteger().getLongValue() - 1);

        return evalUnder(context, mod, evalStr, file, line);
    }

    /**
     * Evaluates the string src with self set to the current object,
     * using the module under as the context.
     * @deprecated Call with an int line number and String file
     */
    public IRubyObject evalUnder(final ThreadContext context, RubyModule under, IRubyObject src, IRubyObject file, IRubyObject line) {
        return evalUnder(context, under, src.convertToString(), file.convertToString().toString(), (int) (line.convertToInteger().getLongValue() - 1));
    }

    /**
     * Evaluates the string src with self set to the current object,
     * using the module under as the context.
     */
    public IRubyObject evalUnder(final ThreadContext context, RubyModule under, RubyString src, String file, int line) {
        Visibility savedVisibility = context.getCurrentVisibility();
        context.setCurrentVisibility(Visibility.PUBLIC);
        context.preExecuteUnder(under, Block.NULL_BLOCK);
        try {
            return ASTInterpreter.evalSimple(context, this, src, 
                    file, line);
        } finally {
            context.postExecuteUnder();
            context.setCurrentVisibility(savedVisibility);
        }
    }

    /**
     * Will yield to the specific block changing the self to be the
     * current object instead of the self that is part of the frame
     * saved in the block frame. This method is the basis for the Ruby
     * instance_eval and module_eval methods. The arguments sent in to
     * it in the args array will be yielded to the block. This makes
     * it possible to emulate both instance_eval and instance_exec
     * with this implementation.
     */
    private IRubyObject yieldUnder(final ThreadContext context, RubyModule under, IRubyObject[] args, Block block) {
        context.preExecuteUnder(under, block);
        
        Visibility savedVisibility = block.getBinding().getVisibility();
        block.getBinding().setVisibility(Visibility.PUBLIC);
        
        try {
            IRubyObject valueInYield;
            boolean aValue;
            if (args.length == 1) {
                valueInYield = args[0];
                aValue = false;
            } else {
                valueInYield = RubyArray.newArrayNoCopy(context.getRuntime(), args);
                aValue = true;
            }

            // FIXME: This is an ugly hack to resolve JRUBY-1381; I'm not proud of it
            block = block.cloneBlock();
            block.getBinding().setSelf(RubyObject.this);
            block.getBinding().getFrame().setSelf(RubyObject.this);
            // end hack

            return block.yield(context, valueInYield, RubyObject.this, context.getRubyClass(), aValue);
            //TODO: Should next and return also catch here?
        } catch (JumpException.BreakJump bj) {
            return (IRubyObject) bj.getValue();
        } finally {
            block.getBinding().setVisibility(savedVisibility);
            
            context.postExecuteUnder();
        }
    }

    /**
     * Will yield to the specific block changing the self to be the
     * current object instead of the self that is part of the frame
     * saved in the block frame. This method is the basis for the Ruby
     * instance_eval and module_eval methods. The arguments sent in to
     * it in the args array will be yielded to the block. This makes
     * it possible to emulate both instance_eval and instance_exec
     * with this implementation.
     */
    private IRubyObject yieldUnder(final ThreadContext context, RubyModule under, Block block) {
        context.preExecuteUnder(under, block);
        
        Visibility savedVisibility = block.getBinding().getVisibility();
        block.getBinding().setVisibility(Visibility.PUBLIC);
        
        try {
            // FIXME: This is an ugly hack to resolve JRUBY-1381; I'm not proud of it
            block = block.cloneBlock();
            block.getBinding().setSelf(RubyObject.this);
            block.getBinding().getFrame().setSelf(RubyObject.this);
            // end hack

            return block.yield(context, this, this, context.getRubyClass(), false);
            //TODO: Should next and return also catch here?
        } catch (JumpException.BreakJump bj) {
            return (IRubyObject) bj.getValue();
        } finally {
            block.getBinding().setVisibility(savedVisibility);
            
            context.postExecuteUnder();
        }
    }

    // Methods of the Object class (rb_obj_*):

    /** rb_obj_equal
     *
     * Will by default use identity equality to compare objects. This
     * follows the Ruby semantics.
     */
    @JRubyMethod(name = "==", required = 1)
    public IRubyObject op_equal(ThreadContext context, IRubyObject obj) {
        return this == obj ? context.getRuntime().getTrue() : context.getRuntime().getFalse();
    }
    
    /** rb_obj_equal
     *
     * Will use Java identity equality.
     */
    @JRubyMethod(name = "equal?", required = 1)
    public IRubyObject equal_p(ThreadContext context, IRubyObject obj) {
        return this == obj ? context.getRuntime().getTrue() : context.getRuntime().getFalse();
    }

    /** method used for Hash key comparison (specialized for String, Symbol and Fixnum)
     * 
     * Will by default just call the Ruby method "eql?"
     */
    public boolean eql(IRubyObject other) {
        return callMethod(getRuntime().getCurrentContext(), MethodIndex.EQL_P, "eql?", other).isTrue();
    }

    /** rb_obj_equal
     * 
     * Just like "==" and "equal?", "eql?" will use identity equality for Object.
     */    
    @JRubyMethod(name = "eql?", required = 1)
    public IRubyObject eql_p(IRubyObject obj) {
        return this == obj ? getRuntime().getTrue() : getRuntime().getFalse();
    }
    
    /** rb_equal
     * 
     * The Ruby "===" method is used by default in case/when
     * statements. The Object implementation first checks Java identity
     * equality and then calls the "==" method too.
     */
    @JRubyMethod(name = "===", required = 1)
    public IRubyObject op_eqq(ThreadContext context, IRubyObject other) {
        return context.getRuntime().newBoolean(equalInternal(context, this, other));
    }

    /**
     * Helper method for checking equality, first using Java identity
     * equality, and then calling the "==" method.
     */
    protected static boolean equalInternal(final ThreadContext context, final IRubyObject that, final IRubyObject other){
        return that == other || that.callMethod(context, MethodIndex.EQUALEQUAL, "==", other).isTrue();
    }

    /**
     * Helper method for checking equality, first using Java identity
     * equality, and then calling the "eql?" method.
     */
    protected static boolean eqlInternal(final ThreadContext context, final IRubyObject that, final IRubyObject other){
        return that == other || that.callMethod(context, MethodIndex.EQL_P, "eql?", other).isTrue();
    }

    /** rb_obj_init_copy
     * 
     * Initializes this object as a copy of the original, that is the
     * parameter to this object. Will make sure that the argument
     * actually has the same real class as this object. It shouldn't
     * be possible to initialize an object with something totally
     * different.
     */
    @JRubyMethod(name = "initialize_copy", required = 1, visibility = Visibility.PRIVATE)
    public IRubyObject initialize_copy(IRubyObject original) {
	    if (this == original) return this;
	    checkFrozen();

        if (getMetaClass().getRealClass() != original.getMetaClass().getRealClass()) {
            throw getRuntime().newTypeError("initialize_copy should take same class object");
	    }

	    return this;
	}

    /** obj_respond_to
     *
     * respond_to?( aSymbol, includePriv=false ) -> true or false
     *
     * Returns true if this object responds to the given method. Private
     * methods are included in the search only if the optional second
     * parameter evaluates to true.
     *
     * @return true if this responds to the given method
     *
     * !!! For some reason MRI shows the arity of respond_to? as -1, when it should be -2; that's why this is rest instead of required, optional = 1
     *
     * Going back to splitting according to method arity. MRI is wrong
     * about most of these anyway, and since we have arity splitting
     * in both the compiler and the interpreter, the performance
     * benefit is important for this method.
     */
    @JRubyMethod(name = "respond_to?")
    public RubyBoolean respond_to_p(IRubyObject mname) {
        String name = mname.asJavaString();
        return getRuntime().newBoolean(getMetaClass().isMethodBound(name, true));
    }

    /** obj_respond_to
     *
     * respond_to?( aSymbol, includePriv=false ) -> true or false
     *
     * Returns true if this object responds to the given method. Private
     * methods are included in the search only if the optional second
     * parameter evaluates to true.
     *
     * @return true if this responds to the given method
     *
     * !!! For some reason MRI shows the arity of respond_to? as -1, when it should be -2; that's why this is rest instead of required, optional = 1
     *
     * Going back to splitting according to method arity. MRI is wrong
     * about most of these anyway, and since we have arity splitting
     * in both the compiler and the interpreter, the performance
     * benefit is important for this method.
     */
    @JRubyMethod(name = "respond_to?")
    public RubyBoolean respond_to_p(IRubyObject mname, IRubyObject includePrivate) {
        String name = mname.asJavaString();
        return getRuntime().newBoolean(getMetaClass().isMethodBound(name, !includePrivate.isTrue()));
    }

    /** rb_obj_id 
     *
     * Return the internal id of an object.
     *
     * FIXME: Should this be renamed to match its ruby name?
     */
    @JRubyMethod(name = {"object_id", "__id__"})
    public synchronized IRubyObject id() {
        return getRuntime().newFixnum(getRuntime().getObjectSpace().idOf(this));
    }

    /** rb_obj_id_obsolete
     * 
     * Old id version. This one is bound to the "id" name and will emit a deprecation warning.
     */
    @JRubyMethod(name = "id")
    public synchronized IRubyObject id_deprecated() {
        getRuntime().getWarnings().warn(ID.DEPRECATED_METHOD, "Object#id will be deprecated; use Object#object_id", "Object#id", "Object#object_id");
        return id();
    }

    /** rb_obj_id
     *
     * Will return the hash code of this object. In comparison to MRI,
     * this method will use the Java identity hash code instead of
     * using rb_obj_id, since the usage of id in JRuby will incur the
     * cost of some. ObjectSpace maintenance.
     */    
    @JRubyMethod(name = "hash")
    public RubyFixnum hash() {
        return getRuntime().newFixnum(super.hashCode());
    }

    /**
     * Override the Object#hashCode method to make sure that the Ruby
     * hash is actually used as the hashcode for Ruby objects. If the
     * Ruby "hash" method doesn't return a number, the Object#hashCode
     * implementation will be used instead.
     */
    @Override
    public int hashCode() {
        IRubyObject hashValue = callMethod(getRuntime().getCurrentContext(), MethodIndex.HASH, "hash");
        
        if (hashValue instanceof RubyFixnum) return (int) RubyNumeric.fix2long(hashValue); 
        
        return super.hashCode();
    }

    /** rb_obj_class
     *
     * Returns the real class of this object, excluding any
     * singleton/meta class in the inheritance chain.
     */
    @JRubyMethod(name = "class")
    public RubyClass type() {
        return getMetaClass().getRealClass();
    }

    /** rb_obj_type
     *
     * The deprecated version of type, that emits a deprecation
     * warning.
     */
    @JRubyMethod(name = "type")
    public RubyClass type_deprecated() {
        getRuntime().getWarnings().warn(ID.DEPRECATED_METHOD, "Object#type is deprecated; use Object#class", "Object#type", "Object#class");
        return type();
    }

    /** rb_obj_clone
     *
     * This method should be overridden only by: Proc, Method,
     * UnboundedMethod, Binding. It will use the defined allocated of
     * the object, then clone the singleton class, taint the object,
     * call initCopy and then copy frozen state.
     */
    @JRubyMethod(name = "clone", frame = true)
    public IRubyObject rbClone() {
        if (isImmediate()) throw getRuntime().newTypeError("can't clone " + getMetaClass().getName());
        
        // We're cloning ourselves, so we know the result should be a RubyObject
        RubyObject clone = (RubyObject)getMetaClass().getRealClass().allocate();
        clone.setMetaClass(getSingletonClassClone());
        if (isTaint()) clone.setTaint(true);

        initCopy(clone, this);

        if (isFrozen()) clone.setFrozen(true);
        return clone;
    }

    /** rb_obj_dup
     *
     * This method should be overridden only by: Proc
     *
     * Will allocate a new instance of the real class of this object,
     * and then initialize that copy. It's different from {@link
     * #rbClone} in that it doesn't copy the singleton class.
     */
    @JRubyMethod(name = "dup")
    public IRubyObject dup() {
        if (isImmediate()) throw getRuntime().newTypeError("can't dup " + getMetaClass().getName());

        IRubyObject dup = getMetaClass().getRealClass().allocate();
        if (isTaint()) dup.setTaint(true);

        initCopy(dup, this);

        return dup;
    }
    
    /** 
     * Lots of MRI objects keep their state in non-lookupable ivars
     * (e:g. Range, Struct, etc). This method is responsible for
     * dupping our java field equivalents
     */
    protected void copySpecialInstanceVariables(IRubyObject clone) {
    }    

    /** rb_obj_display
     *
     *  call-seq:
     *     obj.display(port=$>)    => nil
     *  
     *  Prints <i>obj</i> on the given port (default <code>$></code>).
     *  Equivalent to:
     *     
     *     def display(port=$>)
     *       port.write self
     *     end
     *     
     *  For example:
     *     
     *     1.display
     *     "cat".display
     *     [ 4, 5, 6 ].display
     *     puts
     *     
     *  <em>produces:</em>
     *     
     *     1cat456
     * 
     */
    @JRubyMethod(name = "display", optional = 1)
    public IRubyObject display(ThreadContext context, IRubyObject[] args) {
        IRubyObject port = args.length == 0 ? context.getRuntime().getGlobalVariables().get("$>") : args[0];

        port.callMethod(context, "write", this);

        return context.getRuntime().getNil();
    }

    /** rb_obj_tainted
     *
     *  call-seq:
     *     obj.tainted?    => true or false
     *  
     *  Returns <code>true</code> if the object is tainted.
     *
     */
    @JRubyMethod(name = "tainted?")
    public RubyBoolean tainted_p(ThreadContext context) {
        return context.getRuntime().newBoolean(isTaint());
    }

    /** rb_obj_taint
     *
     *  call-seq:
     *     obj.taint -> obj
     *  
     *  Marks <i>obj</i> as tainted---if the <code>$SAFE</code> level is
     *  set appropriately, many method calls which might alter the running
     *  programs environment will refuse to accept tainted strings.
     */
    @JRubyMethod(name = "taint")
    public IRubyObject taint(ThreadContext context) {
        taint(context.getRuntime());
        return this;
    }
    
    private void taint(Ruby runtime) {
        runtime.secure(4);
        if (!isTaint()) {
        	testFrozen("object");
            setTaint(true);
        }
    }

    /** rb_obj_untaint
     *
     *  call-seq:
     *     obj.untaint    => obj
     *  
     *  Removes the taint from <i>obj</i>.
     * 
     *  Only callable in if more secure than 3.
     */
    @JRubyMethod(name = "untaint")
    public IRubyObject untaint(ThreadContext context) {
        context.getRuntime().secure(3);
        
        if (isTaint()) {
            testFrozen("object");
            setTaint(false);
        }
        
        return this;
    }

    /** rb_obj_freeze
     *
     *  call-seq:
     *     obj.freeze    => obj
     *  
     *  Prevents further modifications to <i>obj</i>. A
     *  <code>TypeError</code> will be raised if modification is attempted.
     *  There is no way to unfreeze a frozen object. See also
     *  <code>Object#frozen?</code>.
     *     
     *     a = [ "a", "b", "c" ]
     *     a.freeze
     *     a << "z"
     *     
     *  <em>produces:</em>
     *     
     *     prog.rb:3:in `<<': can't modify frozen array (TypeError)
     *     	from prog.rb:3
     */
    @JRubyMethod(name = "freeze")
    public IRubyObject freeze(ThreadContext context) {
        if ((flags & FROZEN_F) == 0) {
            if (context.getRuntime().getSafeLevel() >= 4 && isTaint()) {
                throw context.getRuntime().newSecurityError("Insecure: can't freeze object");
            }
            flags |= FROZEN_F;
        }
        return this;
    }

    /** rb_obj_frozen_p
     *
     *  call-seq:
     *     obj.frozen?    => true or false
     *  
     *  Returns the freeze status of <i>obj</i>.
     *     
     *     a = [ "a", "b", "c" ]
     *     a.freeze    #=> ["a", "b", "c"]
     *     a.frozen?   #=> true
     */    
    @JRubyMethod(name = "frozen?")
    public RubyBoolean frozen_p(ThreadContext context) {
        return context.getRuntime().newBoolean(isFrozen());
    }

    /** inspect_obj
     * 
     * The internal helper method that takes care of the part of the
     * inspection that inspects instance variables.
     */
    private StringBuilder inspectObj(StringBuilder part) {
        ThreadContext context = getRuntime().getCurrentContext();
        String sep = "";
        
        for (Variable<IRubyObject> ivar : getInstanceVariableList()) {
            part.append(sep).append(" ").append(ivar.getName()).append("=");
            part.append(ivar.getValue().callMethod(context, "inspect"));
            sep = ",";
        }
        part.append(">");
        return part;
    }

    /** rb_inspect
     * 
     * The internal helper that ensures a RubyString instance is returned
     * so dangerous casting can be omitted
     * Prefered over callMethod(context, "inspect") 
     */
    static RubyString inspect(ThreadContext context, IRubyObject object) {
        return RubyString.objAsString(context, object.callMethod(context, "inspect"));
    }    

    /** rb_obj_inspect
     *
     *  call-seq:
     *     obj.inspect   => string
     *  
     *  Returns a string containing a human-readable representation of
     *  <i>obj</i>. If not overridden, uses the <code>to_s</code> method to
     *  generate the string.
     *     
     *     [ 1, 2, 3..4, 'five' ].inspect   #=> "[1, 2, 3..4, \"five\"]"
     *     Time.new.inspect                 #=> "Wed Apr 09 08:54:39 CDT 2003"
     */
    @JRubyMethod(name = "inspect")
    public IRubyObject inspect() {
        Ruby runtime = getRuntime();
        if ((!isImmediate()) &&
                // TYPE(obj) == T_OBJECT
                !(this instanceof RubyClass) &&
                this != runtime.getObject() &&
                this != runtime.getModule() &&
                !(this instanceof RubyModule) &&
                // TODO: should have #hasInstanceVariables method, though
                // this will work here:
                hasVariables()) {

            StringBuilder part = new StringBuilder();
            String cname = getMetaClass().getRealClass().getName();
            part.append("#<").append(cname).append(":0x");
            part.append(Integer.toHexString(System.identityHashCode(this)));

            if (runtime.isInspecting(this)) {
                /* 6:tags 16:addr 1:eos */
                part.append(" ...>");
                return runtime.newString(part.toString());
            }
            try {
                runtime.registerInspecting(this);
                return runtime.newString(inspectObj(part).toString());
            } finally {
                runtime.unregisterInspecting(this);
            }
        }

        if (isNil()) return RubyNil.inspect(this);
        return RuntimeHelpers.invoke(runtime.getCurrentContext(), this, "to_s");
    }

    /** rb_obj_is_instance_of
     *
     *  call-seq:
     *     obj.instance_of?(class)    => true or false
     *  
     *  Returns <code>true</code> if <i>obj</i> is an instance of the given
     *  class. See also <code>Object#kind_of?</code>.
     */
    @JRubyMethod(name = "instance_of?", required = 1)
    public RubyBoolean instance_of_p(ThreadContext context, IRubyObject type) {
        if (type() == type) {
            return context.getRuntime().getTrue();
        } else if (!(type instanceof RubyModule)) {
            throw context.getRuntime().newTypeError("class or module required");
        } else {
            return context.getRuntime().getFalse();
        }
    }


    /** rb_obj_is_kind_of
     *
     *  call-seq:
     *     obj.is_a?(class)       => true or false
     *     obj.kind_of?(class)    => true or false
     *  
     *  Returns <code>true</code> if <i>class</i> is the class of
     *  <i>obj</i>, or if <i>class</i> is one of the superclasses of
     *  <i>obj</i> or modules included in <i>obj</i>.
     *     
     *     module M;    end
     *     class A
     *       include M
     *     end
     *     class B < A; end
     *     class C < B; end
     *     b = B.new
     *     b.instance_of? A   #=> false
     *     b.instance_of? B   #=> true
     *     b.instance_of? C   #=> false
     *     b.instance_of? M   #=> false
     *     b.kind_of? A       #=> true
     *     b.kind_of? B       #=> true
     *     b.kind_of? C       #=> false
     *     b.kind_of? M       #=> true
     */
    @JRubyMethod(name = {"kind_of?", "is_a?"}, required = 1)
    public RubyBoolean kind_of_p(ThreadContext context, IRubyObject type) {
        // TODO: Generalize this type-checking code into IRubyObject helper.
        if (!(type instanceof RubyModule)) {
            // TODO: newTypeError does not offer enough for ruby error string...
            throw context.getRuntime().newTypeError("class or module required");
        }

        return context.getRuntime().newBoolean(((RubyModule)type).isInstance(this));
    }

    /** rb_obj_methods
     *
     *  call-seq:
     *     obj.methods    => array
     *  
     *  Returns a list of the names of methods publicly accessible in
     *  <i>obj</i>. This will include all the methods accessible in
     *  <i>obj</i>'s ancestors.
     *     
     *     class Klass
     *       def kMethod()
     *       end
     *     end
     *     k = Klass.new
     *     k.methods[0..9]    #=> ["kMethod", "freeze", "nil?", "is_a?", 
     *                             "class", "instance_variable_set",
     *                              "methods", "extend", "__send__", "instance_eval"]
     *     k.methods.length   #=> 42
     */
    @JRubyMethod(name = "methods", optional = 1)
    public IRubyObject methods(ThreadContext context, IRubyObject[] args) {
        boolean all = true;
        if (args.length == 1) {
            all = args[0].isTrue();
        }

        RubyArray singletonMethods = null;
        if (getMetaClass().isSingleton()) {
            singletonMethods =
                getMetaClass().instance_methods(new IRubyObject[] {context.getRuntime().getFalse()});
            if (all) {
                singletonMethods.concat(getMetaClass().getSuperClass().instance_methods(new IRubyObject[] {context.getRuntime().getTrue()}));
            }
        } else {
            if (all) {
                singletonMethods = getMetaClass().instance_methods(new IRubyObject[] {context.getRuntime().getTrue()});
            } else {
                singletonMethods = context.getRuntime().newEmptyArray();
            }
        }
        
        return singletonMethods;
    }

    /** rb_obj_public_methods
     *
     *  call-seq:
     *     obj.public_methods(all=true)   => array
     *  
     *  Returns the list of public methods accessible to <i>obj</i>. If
     *  the <i>all</i> parameter is set to <code>false</code>, only those methods
     *  in the receiver will be listed.
     */
    @JRubyMethod(name = "public_methods", optional = 1)
    public IRubyObject public_methods(ThreadContext context, IRubyObject[] args) {
        if (args.length == 0) {
            args = new IRubyObject[] { context.getRuntime().getTrue() };
        }

        return getMetaClass().public_instance_methods(args);
    }

    /** rb_obj_protected_methods
     *
     *  call-seq:
     *     obj.protected_methods(all=true)   => array
     *  
     *  Returns the list of protected methods accessible to <i>obj</i>. If
     *  the <i>all</i> parameter is set to <code>false</code>, only those methods
     *  in the receiver will be listed.
     *
     *  Internally this implementation uses the 
     *  {@link RubyModule#protected_instance_methods} method.
     */
    @JRubyMethod(name = "protected_methods", optional = 1)
    public IRubyObject protected_methods(ThreadContext context, IRubyObject[] args) {
        if (args.length == 0) {
            args = new IRubyObject[] { context.getRuntime().getTrue() };
        }

        return getMetaClass().protected_instance_methods(args);
    }

    /** rb_obj_private_methods
     *
     *  call-seq:
     *     obj.private_methods(all=true)   => array
     *  
     *  Returns the list of private methods accessible to <i>obj</i>. If
     *  the <i>all</i> parameter is set to <code>false</code>, only those methods
     *  in the receiver will be listed.
     *
     *  Internally this implementation uses the 
     *  {@link RubyModule#private_instance_methods} method.
     */
    @JRubyMethod(name = "private_methods", optional = 1)
    public IRubyObject private_methods(ThreadContext context, IRubyObject[] args) {
        if (args.length == 0) {
            args = new IRubyObject[] { context.getRuntime().getTrue() };
        }

        return getMetaClass().private_instance_methods(args);
    }

    /** rb_obj_singleton_methods
     *
     *  call-seq:
     *     obj.singleton_methods(all=true)    => array
     *  
     *  Returns an array of the names of singleton methods for <i>obj</i>.
     *  If the optional <i>all</i> parameter is true, the list will include
     *  methods in modules included in <i>obj</i>.
     *     
     *     module Other
     *       def three() end
     *     end
     *     
     *     class Single
     *       def Single.four() end
     *     end
     *     
     *     a = Single.new
     *     
     *     def a.one()
     *     end
     *     
     *     class << a
     *       include Other
     *       def two()
     *       end
     *     end
     *     
     *     Single.singleton_methods    #=> ["four"]
     *     a.singleton_methods(false)  #=> ["two", "one"]
     *     a.singleton_methods         #=> ["two", "one", "three"]
     */
    // TODO: This is almost RubyModule#instance_methods on the metaClass.  Perhaps refactor.
    @JRubyMethod(name = "singleton_methods", optional = 1)
    public RubyArray singleton_methods(ThreadContext context, IRubyObject[] args) {
        boolean all = true;
        if(args.length == 1) {
            all = args[0].isTrue();
        }

        RubyArray singletonMethods;
        if (getMetaClass().isSingleton()) {
            singletonMethods = getMetaClass().instance_methods(new IRubyObject[] {context.getRuntime().getFalse()});
            if (all) {
                RubyClass superClass = getMetaClass().getSuperClass();
                while (superClass.isIncluded()) {
                    singletonMethods.concat(superClass.instance_methods(new IRubyObject[] {context.getRuntime().getFalse()}));
                    superClass = superClass.getSuperClass();
                }
            }
        } else {
            singletonMethods = context.getRuntime().newEmptyArray();
        }
        
        return singletonMethods;
    }

    /** rb_obj_method
     *
     *  call-seq:
     *     obj.method(sym)    => method
     *  
     *  Looks up the named method as a receiver in <i>obj</i>, returning a
     *  <code>Method</code> object (or raising <code>NameError</code>). The
     *  <code>Method</code> object acts as a closure in <i>obj</i>'s object
     *  instance, so instance variables and the value of <code>self</code>
     *  remain available.
     *     
     *     class Demo
     *       def initialize(n)
     *         @iv = n
     *       end
     *       def hello()
     *         "Hello, @iv = #{@iv}"
     *       end
     *     end
     *     
     *     k = Demo.new(99)
     *     m = k.method(:hello)
     *     m.call   #=> "Hello, @iv = 99"
     *     
     *     l = Demo.new('Fred')
     *     m = l.method("hello")
     *     m.call   #=> "Hello, @iv = Fred"
     */
    @JRubyMethod(name = "method", required = 1)
    public IRubyObject method(IRubyObject symbol) {
        return getMetaClass().newMethod(this, symbol.asJavaString(), true);
    }

    /**
     * Internal method that helps to convert any object into the
     * format of a class name and a hex string inside of #<>.
     */
    public IRubyObject anyToString() {
        String cname = getMetaClass().getRealClass().getName();
        /* 6:tags 16:addr 1:eos */
        RubyString str = getRuntime().newString("#<" + cname + ":0x" + Integer.toHexString(System.identityHashCode(this)) + ">");
        str.setTaint(isTaint());
        return str;
    }

    /** rb_any_to_s
     *
     *  call-seq:
     *     obj.to_s    => string
     *  
     *  Returns a string representing <i>obj</i>. The default
     *  <code>to_s</code> prints the object's class and an encoding of the
     *  object id. As a special case, the top-level object that is the
     *  initial execution context of Ruby programs returns ``main.''
     */
    @JRubyMethod(name = "to_s")
    public IRubyObject to_s() {
    	return anyToString();
    }

    /** rb_any_to_a
     *
     *  call-seq:
     *     obj.to_a -> anArray
     *  
     *  Returns an array representation of <i>obj</i>. For objects of class
     *  <code>Object</code> and others that don't explicitly override the
     *  method, the return value is an array containing <code>self</code>. 
     *  However, this latter behavior will soon be obsolete.
     *     
     *     self.to_a       #=> -:1: warning: default `to_a' will be obsolete
     *     "hello".to_a    #=> ["hello"]
     *     Time.new.to_a   #=> [39, 54, 8, 9, 4, 2003, 3, 99, true, "CDT"]
     * 
     *  The default to_a method is deprecated.
     */    
    @JRubyMethod(name = "to_a", visibility = Visibility.PUBLIC)
    public RubyArray to_a() {
        getRuntime().getWarnings().warn(ID.DEPRECATED_METHOD, "default 'to_a' will be obsolete", "to_a");
        return getRuntime().newArray(this);
    }

    /** rb_obj_instance_eval
     *
     *  call-seq:
     *     obj.instance_eval(string [, filename [, lineno]] )   => obj
     *     obj.instance_eval {| | block }                       => obj
     *  
     *  Evaluates a string containing Ruby source code, or the given block,
     *  within the context of the receiver (_obj_). In order to set the
     *  context, the variable +self+ is set to _obj_ while
     *  the code is executing, giving the code access to _obj_'s
     *  instance variables. In the version of <code>instance_eval</code>
     *  that takes a +String+, the optional second and third
     *  parameters supply a filename and starting line number that are used
     *  when reporting compilation errors.
     *     
     *     class Klass
     *       def initialize
     *         @secret = 99
     *       end
     *     end
     *     k = Klass.new
     *     k.instance_eval { @secret }   #=> 99
     */
    @JRubyMethod(name = "instance_eval", frame = true)
    public IRubyObject instance_eval(ThreadContext context, Block block) {
        return specificEval(context, getInstanceEvalClass(), block);
    }
    @JRubyMethod(name = "instance_eval", frame = true)
    public IRubyObject instance_eval(ThreadContext context, IRubyObject arg0, Block block) {
        return specificEval(context, getInstanceEvalClass(), arg0, block);
    }
    @JRubyMethod(name = "instance_eval", frame = true)
    public IRubyObject instance_eval(ThreadContext context, IRubyObject arg0, IRubyObject arg1, Block block) {
        return specificEval(context, getInstanceEvalClass(), arg0, arg1, block);
    }
    @JRubyMethod(name = "instance_eval", frame = true)
    public IRubyObject instance_eval(ThreadContext context, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Block block) {
        return specificEval(context, getInstanceEvalClass(), arg0, arg1, arg2, block);
    }
    @Deprecated
    public IRubyObject instance_eval(ThreadContext context, IRubyObject[] args, Block block) {
        RubyModule klazz;
        
        if (isImmediate()) {
            // Ruby uses Qnil here, we use "dummy" because we need a class
            klazz = context.getRuntime().getDummy();
        } else {
            klazz = getSingletonClass();
        }
        
        return specificEval(context, klazz, args, block);
    }
    
    private RubyModule getInstanceEvalClass() {
        if (isImmediate()) {
            // Ruby uses Qnil here, we use "dummy" because we need a class
            return getRuntime().getDummy();
        } else {
            return getSingletonClass();
        }
    }

    /** rb_obj_instance_exec
     *
     *  call-seq:
     *     obj.instance_exec(arg...) {|var...| block }                       => obj
     *
     *  Executes the given block within the context of the receiver
     *  (_obj_). In order to set the context, the variable +self+ is set
     *  to _obj_ while the code is executing, giving the code access to
     *  _obj_'s instance variables.  Arguments are passed as block parameters.
     *
     *     class Klass
     *       def initialize
     *         @secret = 99
     *       end
     *     end
     *     k = Klass.new
     *     k.instance_exec(5) {|x| @secret+x }   #=> 104
     */
    @JRubyMethod(name = "instance_exec", optional = 3, frame = true)
    public IRubyObject instance_exec(ThreadContext context, IRubyObject[] args, Block block) {
        if (!block.isGiven()) throw context.getRuntime().newArgumentError("block not supplied");

        RubyModule klazz;
        if (isImmediate()) {
            // Ruby uses Qnil here, we use "dummy" because we need a class
            klazz = context.getRuntime().getDummy();          
        } else {
            klazz = getSingletonClass();
        }

        return yieldUnder(context, klazz, args, block);
    }

    /** rb_obj_extend
     *
     *  call-seq:
     *     obj.extend(module, ...)    => obj
     *  
     *  Adds to _obj_ the instance methods from each module given as a
     *  parameter.
     *     
     *     module Mod
     *       def hello
     *         "Hello from Mod.\n"
     *       end
     *     end
     *     
     *     class Klass
     *       def hello
     *         "Hello from Klass.\n"
     *       end
     *     end
     *     
     *     k = Klass.new
     *     k.hello         #=> "Hello from Klass.\n"
     *     k.extend(Mod)   #=> #<Klass:0x401b3bc8>
     *     k.hello         #=> "Hello from Mod.\n"
     */
    @JRubyMethod(name = "extend", required = 1, rest = true)
    public IRubyObject extend(IRubyObject[] args) {
        Ruby runtime = getRuntime();
        
        // Make sure all arguments are modules before calling the callbacks
        for (int i = 0; i < args.length; i++) {
            if (!args[i].isModule()) throw runtime.newTypeError(args[i], runtime.getModule()); 
        }

        ThreadContext context = runtime.getCurrentContext();
                
        // MRI extends in order from last to first
        for (int i = args.length - 1; i >= 0; i--) {
            args[i].callMethod(context, "extend_object", this);
            args[i].callMethod(context, "extended", this);
        }
        return this;
    }

    /** rb_obj_dummy
     *
     * Default initialize method. This one gets defined in some other
     * place as a Ruby method.
     */
    public IRubyObject initialize() {
        return getRuntime().getNil();
    }

    /** rb_f_send
     *
     * send( aSymbol  [, args  ]*   ) -> anObject
     *
     * Invokes the method identified by aSymbol, passing it any arguments
     * specified. You can use __send__ if the name send clashes with an
     * existing method in this object.
     *
     * <pre>
     * class Klass
     *   def hello(*args)
     *     "Hello " + args.join(' ')
     *   end
     * end
     *
     * k = Klass.new
     * k.send :hello, "gentle", "readers"
     * </pre>
     *
     * @return the result of invoking the method identified by aSymbol.
     */
    @JRubyMethod(name = {"send", "__send__"})
    public IRubyObject send(ThreadContext context, Block block) {
        throw context.getRuntime().newArgumentError(0, 1);
    }
    @JRubyMethod(name = {"send", "__send__"})
    public IRubyObject send(ThreadContext context, IRubyObject arg0, Block block) {
        String name = arg0.asJavaString();

        return getMetaClass().finvoke(context, this, name, block);
    }
    @JRubyMethod(name = {"send", "__send__"})
    public IRubyObject send(ThreadContext context, IRubyObject arg0, IRubyObject arg1, Block block) {
        String name = arg0.asJavaString();

        return getMetaClass().finvoke(context, this, name, arg1, block);
    }
    @JRubyMethod(name = {"send", "__send__"})
    public IRubyObject send(ThreadContext context, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Block block) {
        String name = arg0.asJavaString();

        return getMetaClass().finvoke(context, this, name, arg1, arg2, block);
    }
    @JRubyMethod(name = {"send", "__send__"}, rest = true)
    public IRubyObject send(ThreadContext context, IRubyObject[] args, Block block) {
        String name = args[0].asJavaString();
        int newArgsLength = args.length - 1;

        IRubyObject[] newArgs;
        if (newArgsLength == 0) {
            newArgs = IRubyObject.NULL_ARRAY;
        } else {
            newArgs = new IRubyObject[newArgsLength];
            System.arraycopy(args, 1, newArgs, 0, newArgs.length);
        }

        return getMetaClass().finvoke(context, this, name, newArgs, block);
    }

    /** rb_false
     *
     * call_seq:
     *   nil.nil?               => true
     *   <anything_else>.nil?   => false
     *
     * Only the object <i>nil</i> responds <code>true</code> to <code>nil?</code>.
     */
    @JRubyMethod(name = "nil?")
    public IRubyObject nil_p(ThreadContext context) {
    	return context.getRuntime().getFalse();
    }

    /** rb_obj_pattern_match
     *
     *  call-seq:
     *     obj =~ other  => false
     *  
     *  Pattern Match---Overridden by descendents (notably
     *  <code>Regexp</code> and <code>String</code>) to provide meaningful
     *  pattern-match semantics.
     */    
    @JRubyMethod(name = "=~", required = 1)
    public IRubyObject op_match(ThreadContext context, IRubyObject arg) {
    	return context.getRuntime().getFalse();
    }
    
    public IRubyObject to_java() {
        throw getRuntime().newTypeError(getMetaClass().getBaseName() + " cannot coerce to a Java type.");
    }

    public IRubyObject as(Class javaClass) {
        throw getRuntime().newTypeError(getMetaClass().getBaseName() + " cannot coerce to a Java type.");
    }
    
    /**
     * @see org.jruby.runtime.builtin.IRubyObject#getType()
     */
    public RubyClass getType() {
        return type();
    }

    /**
     * @see org.jruby.runtime.builtin.IRubyObject#dataWrapStruct()
     */
    public synchronized void dataWrapStruct(Object obj) {
        this.dataStruct = obj;
    }

    /**
     * @see org.jruby.runtime.builtin.IRubyObject#dataGetStruct()
     */
    public synchronized Object dataGetStruct() {
        return dataStruct;
    }

    /**
     * Adds the specified object as a finalizer for this object.
     */ 
    public void addFinalizer(IRubyObject finalizer) {
        if (this.finalizer == null) {
            this.finalizer = new Finalizer(getRuntime().getObjectSpace().idOf(this));
            getRuntime().addFinalizer(this.finalizer);
        }
        this.finalizer.addFinalizer(finalizer);
    }

    /**
     * Remove all the finalizers for this object.
     */
    public void removeFinalizers() {
        if (finalizer != null) {
            finalizer.removeFinalizers();
            finalizer = null;
            getRuntime().removeFinalizer(this.finalizer);
        }
    }


    //
    // INSTANCE VARIABLE RUBY METHODS
    //
    
    /** rb_obj_ivar_defined
     *
     *  call-seq:
     *     obj.instance_variable_defined?(symbol)    => true or false
     *
     *  Returns <code>true</code> if the given instance variable is
     *  defined in <i>obj</i>.
     *
     *     class Fred
     *       def initialize(p1, p2)
     *         @a, @b = p1, p2
     *       end
     *     end
     *     fred = Fred.new('cat', 99)
     *     fred.instance_variable_defined?(:@a)    #=> true
     *     fred.instance_variable_defined?("@b")   #=> true
     *     fred.instance_variable_defined?("@c")   #=> false
     */
    @JRubyMethod(name = "instance_variable_defined?", required = 1)
    public IRubyObject instance_variable_defined_p(ThreadContext context, IRubyObject name) {
        if (variableTableContains(validateInstanceVariable(name.asJavaString()))) {
            return context.getRuntime().getTrue();
        }
        return context.getRuntime().getFalse();
    }

    /** rb_obj_ivar_get
     *
     *  call-seq:
     *     obj.instance_variable_get(symbol)    => obj
     *
     *  Returns the value of the given instance variable, or nil if the
     *  instance variable is not set. The <code>@</code> part of the
     *  variable name should be included for regular instance
     *  variables. Throws a <code>NameError</code> exception if the
     *  supplied symbol is not valid as an instance variable name.
     *     
     *     class Fred
     *       def initialize(p1, p2)
     *         @a, @b = p1, p2
     *       end
     *     end
     *     fred = Fred.new('cat', 99)
     *     fred.instance_variable_get(:@a)    #=> "cat"
     *     fred.instance_variable_get("@b")   #=> 99
     */
    @JRubyMethod(name = "instance_variable_get", required = 1)
    public IRubyObject instance_variable_get(ThreadContext context, IRubyObject name) {
        IRubyObject value;
        if ((value = variableTableFetch(validateInstanceVariable(name.asJavaString()))) != null) {
            return value;
        }
        return context.getRuntime().getNil();
    }

    /** rb_obj_ivar_set
     *
     *  call-seq:
     *     obj.instance_variable_set(symbol, obj)    => obj
     *  
     *  Sets the instance variable names by <i>symbol</i> to
     *  <i>object</i>, thereby frustrating the efforts of the class's
     *  author to attempt to provide proper encapsulation. The variable
     *  did not have to exist prior to this call.
     *     
     *     class Fred
     *       def initialize(p1, p2)
     *         @a, @b = p1, p2
     *       end
     *     end
     *     fred = Fred.new('cat', 99)
     *     fred.instance_variable_set(:@a, 'dog')   #=> "dog"
     *     fred.instance_variable_set(:@c, 'cat')   #=> "cat"
     *     fred.inspect                             #=> "#<Fred:0x401b3da8 @a=\"dog\", @b=99, @c=\"cat\">"
     */
    @JRubyMethod(name = "instance_variable_set", required = 2)
    public IRubyObject instance_variable_set(IRubyObject name, IRubyObject value) {
        ensureInstanceVariablesSettable();
        return variableTableStore(validateInstanceVariable(name.asJavaString()), value);
    }

    /** rb_obj_remove_instance_variable
     *
     *  call-seq:
     *     obj.remove_instance_variable(symbol)    => obj
     *  
     *  Removes the named instance variable from <i>obj</i>, returning that
     *  variable's value.
     *     
     *     class Dummy
     *       attr_reader :var
     *       def initialize
     *         @var = 99
     *       end
     *       def remove
     *         remove_instance_variable(:@var)
     *       end
     *     end
     *     d = Dummy.new
     *     d.var      #=> 99
     *     d.remove   #=> 99
     *     d.var      #=> nil
     */
    @JRubyMethod(name = "remove_instance_variable", required = 1, frame = true, visibility = Visibility.PRIVATE)
    public IRubyObject remove_instance_variable(ThreadContext context, IRubyObject name, Block block) {
        ensureInstanceVariablesSettable();
        IRubyObject value;
        if ((value = variableTableRemove(validateInstanceVariable(name.asJavaString()))) != null) {
            return value;
        }
        throw context.getRuntime().newNameError("instance variable " + name.asJavaString() + " not defined", name.asJavaString());
    }

    /** rb_obj_instance_variables
     *
     *  call-seq:
     *     obj.instance_variables    => array
     *  
     *  Returns an array of instance variable names for the receiver. Note
     *  that simply defining an accessor does not create the corresponding
     *  instance variable.
     *     
     *     class Fred
     *       attr_accessor :a1
     *       def initialize
     *         @iv = 3
     *       end
     *     end
     *     Fred.new.instance_variables   #=> ["@iv"]
     */    
    @JRubyMethod(name = "instance_variables")
    public RubyArray instance_variables(ThreadContext context) {
        Ruby runtime = context.getRuntime();
        List<String> nameList = getInstanceVariableNameList();

        RubyArray array = runtime.newArray(nameList.size());

        for (String name : nameList) {
            array.append(runtime.newString(name));
        }

        return array;
    }

    //
    // INSTANCE VARIABLE API METHODS
    //

    /**
     * Dummy method to avoid a cast, and to avoid polluting the
     * IRubyObject interface with all the instance variable management
     * methods.
     */    
    public InstanceVariables getInstanceVariables() {
        return this;
    }
    
    /**
     * @see org.jruby.runtime.builtin.InstanceVariables#hasInstanceVariable
     */
    public boolean hasInstanceVariable(String name) {
        assert IdUtil.isInstanceVariable(name);
        return variableTableContains(name);
    }
    
    /**
     * @see org.jruby.runtime.builtin.InstanceVariables#fastHasInstanceVariable
     */
    public boolean fastHasInstanceVariable(String internedName) {
        assert IdUtil.isInstanceVariable(internedName);
        return variableTableFastContains(internedName);
    }
    
    /**
     * @see org.jruby.runtime.builtin.InstanceVariables#getInstanceVariable
     */
    public IRubyObject getInstanceVariable(String name) {
        assert IdUtil.isInstanceVariable(name);
        return variableTableFetch(name);
    }
    
    /**
     * @see org.jruby.runtime.builtin.InstanceVariables#fastGetInstanceVariable
     */
    public IRubyObject fastGetInstanceVariable(String internedName) {
        assert IdUtil.isInstanceVariable(internedName);
        return variableTableFastFetch(internedName);
    }

    /** rb_iv_set / rb_ivar_set
    *
    * @see org.jruby.runtime.builtin.InstanceVariables#setInstanceVariable
    */
    public IRubyObject setInstanceVariable(String name, IRubyObject value) {
        assert IdUtil.isInstanceVariable(name) && value != null;
        ensureInstanceVariablesSettable();
        return variableTableStore(name, value);
    }

    /**
     * @see org.jruby.runtime.builtin.InstanceVariables#fastSetInstanceVariable
     */    
    public IRubyObject fastSetInstanceVariable(String internedName, IRubyObject value) {
        assert IdUtil.isInstanceVariable(internedName) && value != null;
        ensureInstanceVariablesSettable();
        return variableTableFastStore(internedName, value);
     }

    /**
     * @see org.jruby.runtime.builtin.InstanceVariables#removeInstanceVariable
     */    
    public IRubyObject removeInstanceVariable(String name) {
        assert IdUtil.isInstanceVariable(name);
        ensureInstanceVariablesSettable();
        return variableTableRemove(name);
    }

    /**
     * @see org.jruby.runtime.builtin.InstanceVariables#getInstanceVariableList
     */    
    public List<Variable<IRubyObject>> getInstanceVariableList() {
        VariableTableEntry[] table = variableTableGetTable();
        ArrayList<Variable<IRubyObject>> list = new ArrayList<Variable<IRubyObject>>();
        IRubyObject readValue;
        for (int i = table.length; --i >= 0; ) {
            for (VariableTableEntry e = table[i]; e != null; e = e.next) {
                if (IdUtil.isInstanceVariable(e.name)) {
                    if ((readValue = e.value) == null) readValue = variableTableReadLocked(e);
                    list.add(new VariableEntry<IRubyObject>(e.name, readValue));
                }
            }
        }
        return list;
    }

    /**
     * @see org.jruby.runtime.builtin.InstanceVariables#getInstanceVariableNameList
     */    
    public List<String> getInstanceVariableNameList() {
        VariableTableEntry[] table = variableTableGetTable();
        ArrayList<String> list = new ArrayList<String>();
        for (int i = table.length; --i >= 0; ) {
            for (VariableTableEntry e = table[i]; e != null; e = e.next) {
                if (IdUtil.isInstanceVariable(e.name)) {
                    list.add(e.name);
                }
            }
        }
        return list;
    }

    /**
     * The error message used when some one tries to modify an
     * instance variable in a high security setting.
     */
    protected static final String ERR_INSECURE_SET_INST_VAR  = "Insecure: can't modify instance variable";

    /**
     * Checks if the name parameter represents a legal instance variable name, and otherwise throws a Ruby NameError
     */
    protected String validateInstanceVariable(String name) {
        if (IdUtil.isValidInstanceVariableName(name)) return name;

        throw getRuntime().newNameError("`" + name + "' is not allowable as an instance variable name", name);
    }

    /**
     * Makes sure that instance variables can be set on this object,
     * including information about whether this object is frozen, or
     * tainted. Will throw a suitable exception in that case.
     */
    protected void ensureInstanceVariablesSettable() {
        if (!isFrozen() && (getRuntime().getSafeLevel() < 4 || isTaint())) {
            return;
        }
        
        if (getRuntime().getSafeLevel() >= 4 && !isTaint()) {
            throw getRuntime().newSecurityError(ERR_INSECURE_SET_INST_VAR);
        }
        if (isFrozen()) {
            if (this instanceof RubyModule) {
                throw getRuntime().newFrozenError("class/module ");
            } else {
                throw getRuntime().newFrozenError("");
            }
        }
    }

    //
    // INTERNAL VARIABLE METHODS
    //
    
    /**
     * Dummy method to avoid a cast, and to avoid polluting the
     * IRubyObject interface with all the instance variable management
     * methods.
     */    
    public InternalVariables getInternalVariables() {
        return this;
    }

    /**
     * @see org.jruby.runtime.builtin.InternalVariables#hasInternalVariable
     */    
    public boolean hasInternalVariable(String name) {
        assert !isRubyVariable(name);
        return variableTableContains(name);
    }

    /**
     * @see org.jruby.runtime.builtin.InternalVariables#fastHasInternalVariable
     */    
    public boolean fastHasInternalVariable(String internedName) {
        assert !isRubyVariable(internedName);
        return variableTableFastContains(internedName);
    }

    /**
     * @see org.jruby.runtime.builtin.InternalVariables#getInternalVariable
     */    
    public IRubyObject getInternalVariable(String name) {
        assert !isRubyVariable(name);
        return variableTableFetch(name);
    }

    /**
     * @see org.jruby.runtime.builtin.InternalVariables#fastGetInternalVariable
     */    
    public IRubyObject fastGetInternalVariable(String internedName) {
        assert !isRubyVariable(internedName);
        return variableTableFastFetch(internedName);
    }

    /**
     * @see org.jruby.runtime.builtin.InternalVariables#setInternalVariable
     */    
    public void setInternalVariable(String name, IRubyObject value) {
        assert !isRubyVariable(name);
        variableTableStore(name, value);
    }

    /**
     * @see org.jruby.runtime.builtin.InternalVariables#fastSetInternalVariable
     */    
    public void fastSetInternalVariable(String internedName, IRubyObject value) {
        assert !isRubyVariable(internedName);
        variableTableFastStore(internedName, value);
    }

    /**
     * @see org.jruby.runtime.builtin.InternalVariables#removeInternalVariable
     */    
    public IRubyObject removeInternalVariable(String name) {
        assert !isRubyVariable(name);
        return variableTableRemove(name);
    }

    /**
     * Sync one variable table with another - this is used to make
     * rbClone work correctly.
     */
    public void syncVariables(List<Variable<IRubyObject>> variables) {
        variableTableSync(variables);
    }

    /**
     * @see org.jruby.runtime.builtin.InternalVariables#getInternalVariableList
     */    
    public List<Variable<IRubyObject>> getInternalVariableList() {
        VariableTableEntry[] table = variableTableGetTable();
        ArrayList<Variable<IRubyObject>> list = new ArrayList<Variable<IRubyObject>>();
        IRubyObject readValue;
        for (int i = table.length; --i >= 0; ) {
            for (VariableTableEntry e = table[i]; e != null; e = e.next) {
                if (!isRubyVariable(e.name)) {
                    if ((readValue = e.value) == null) readValue = variableTableReadLocked(e);
                    list.add(new VariableEntry<IRubyObject>(e.name, readValue));
                }
            }
        }
        return list;
    }

    
    //
    // COMMON VARIABLE METHODS
    //

    /**
     * Returns true if object has any variables, defined as:
     * <ul>
     * <li> instance variables
     * <li> class variables
     * <li> constants
     * <li> internal variables, such as those used when marshaling Ranges and Exceptions
     * </ul>
     * @return true if object has any variables, else false
     */
    public boolean hasVariables() {
        return variableTableGetSize() > 0;
    }

    /**
     * Returns the amount of instance variables, class variables,
     * constants and internal variables this object has.
     */
    public int getVariableCount() {
        return variableTableGetSize();
    }
    
    /**
     * Gets a list of all variables in this object.
     */
    // TODO: must override in RubyModule to pick up constants
    public List<Variable<IRubyObject>> getVariableList() {
        VariableTableEntry[] table = variableTableGetTable();
        ArrayList<Variable<IRubyObject>> list = new ArrayList<Variable<IRubyObject>>();
        IRubyObject readValue;
        for (int i = table.length; --i >= 0; ) {
            for (VariableTableEntry e = table[i]; e != null; e = e.next) {
                if ((readValue = e.value) == null) readValue = variableTableReadLocked(e);
                list.add(new VariableEntry<IRubyObject>(e.name, readValue));
            }
        }
        return list;
    }

    /**
     * Gets a name list of all variables in this object.
     */
   // TODO: must override in RubyModule to pick up constants
   public List<String> getVariableNameList() {
        VariableTableEntry[] table = variableTableGetTable();
        ArrayList<String> list = new ArrayList<String>();
        for (int i = table.length; --i >= 0; ) {
            for (VariableTableEntry e = table[i]; e != null; e = e.next) {
                list.add(e.name);
            }
        }
        return list;
    }
    
    /**
     * Gets internal access to the getmap for variables.
     */
    @SuppressWarnings("unchecked")
    @Deprecated // born deprecated
    public Map getVariableMap() {
        return variableTableGetMap();
    }

    /**
     * Check the syntax of a Ruby variable, including that it's longer
     * than zero characters, and starts with either an @ or a capital
     * letter.
     */
    // FIXME: this should go somewhere more generic -- maybe IdUtil
    protected static final boolean isRubyVariable(String name) {
        char c;
        return name.length() > 0 && ((c = name.charAt(0)) == '@' || (c <= 'Z' && c >= 'A'));
    }
    
    //
    // VARIABLE TABLE METHODS, ETC.
    //
    
    protected static final int VARIABLE_TABLE_DEFAULT_CAPACITY = 8; // MUST be power of 2!
    protected static final int VARIABLE_TABLE_MAXIMUM_CAPACITY = 1 << 30;
    protected static final float VARIABLE_TABLE_LOAD_FACTOR = 0.75f;
    protected static final VariableTableEntry[] VARIABLE_TABLE_EMPTY_TABLE = new VariableTableEntry[0];

    /**
     * Every entry in the variable map is represented by an instance
     * of this class.
     */
    protected static final class VariableTableEntry {
        final int hash;
        final String name;
        volatile IRubyObject value;
        final VariableTableEntry next;
        
        VariableTableEntry(int hash, String name, IRubyObject value, VariableTableEntry next) {
            assert name == name.intern() : name + " is not interned";
            this.hash = hash;
            this.name = name;
            this.value = value;
            this.next = next;
        }
    }

    /**
     * Reads the value of the specified entry, locked on the current
     * object.
     */    
    protected synchronized IRubyObject variableTableReadLocked(VariableTableEntry entry) {
        return entry.value;
    }

    /**
     * Checks if the variable table contains a variable of the
     * specified name.
     */
    protected boolean variableTableContains(String name) {
        VariableTableEntry[] table;
        if ((table = variableTable) != null) {
            int hash = name.hashCode();
            for (VariableTableEntry e = table[hash & (table.length - 1)]; e != null; e = e.next) {
                if (hash == e.hash && name.equals(e.name)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Checks if the variable table contains the the variable of the
     * specified name, where the precondition is that the name must be
     * an interned Java String.
     */
    protected boolean variableTableFastContains(String internedName) {
        assert internedName == internedName.intern() : internedName + " not interned";
        VariableTableEntry[] table;
        if ((table = variableTable) != null) {
            for (VariableTableEntry e = table[internedName.hashCode() & (table.length - 1)]; e != null; e = e.next) {
                if (internedName == e.name) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Fetch an object from the variable table based on the name.
     *
     * @return the object or null if not found
     */
    protected IRubyObject variableTableFetch(String name) {
        VariableTableEntry[] table;
        IRubyObject readValue;
        if ((table = variableTable) != null) {
            int hash = name.hashCode();
            for (VariableTableEntry e = table[hash & (table.length - 1)]; e != null; e = e.next) {
                if (hash == e.hash && name.equals(e.name)) {
                    if ((readValue = e.value) != null) return readValue;
                    return variableTableReadLocked(e);
                }
            }
        }
        return null;
    }

    /**
     * Fetch an object from the variable table based on the name,
     * where the name must be an interned Java String.
     *
     * @return the object or null if not found
     */
    protected IRubyObject variableTableFastFetch(String internedName) {
        assert internedName == internedName.intern() : internedName + " not interned";
        VariableTableEntry[] table;
        IRubyObject readValue;
        if ((table = variableTable) != null) {
            for (VariableTableEntry e = table[internedName.hashCode() & (table.length - 1)]; e != null; e = e.next) {
                if (internedName == e.name) {
                    if ((readValue = e.value) != null) return readValue;
                    return variableTableReadLocked(e);
                }
            }
        }
        return null;
    }

    /**
     * Store a value in the variable store under the specific name.
     */
    protected IRubyObject variableTableStore(String name, IRubyObject value) {
        int hash = name.hashCode();
        synchronized(this) {
            VariableTableEntry[] table;
            VariableTableEntry e;
            if ((table = variableTable) == null) {
                table =  new VariableTableEntry[VARIABLE_TABLE_DEFAULT_CAPACITY];
                e = new VariableTableEntry(hash, name.intern(), value, null);
                table[hash & (VARIABLE_TABLE_DEFAULT_CAPACITY - 1)] = e;
                variableTableThreshold = (int)(VARIABLE_TABLE_DEFAULT_CAPACITY * VARIABLE_TABLE_LOAD_FACTOR);
                variableTableSize = 1;
                variableTable = table;
                return value;
            }
            int potentialNewSize;
            if ((potentialNewSize = variableTableSize + 1) > variableTableThreshold) {
                table = variableTableRehash();
            }
            int index;
            for (e = table[index = hash & (table.length - 1)]; e != null; e = e.next) {
                if (hash == e.hash && name.equals(e.name)) {
                    e.value = value;
                    return value;
                }
            }
            e = new VariableTableEntry(hash, name.intern(), value, table[index]);
            table[index] = e;
            variableTableSize = potentialNewSize;
            variableTable = table; // write-volatile
        }
        return value;
    }

    /**
     * Will store the value under the specified name, where the name
     * needs to be an interned Java String.
     */
    protected IRubyObject variableTableFastStore(String internedName, IRubyObject value) {
        assert internedName == internedName.intern() : internedName + " not interned";
        int hash = internedName.hashCode();
        synchronized(this) {
            VariableTableEntry[] table;
            VariableTableEntry e;
            if ((table = variableTable) == null) {
                table =  new VariableTableEntry[VARIABLE_TABLE_DEFAULT_CAPACITY];
                e = new VariableTableEntry(hash, internedName, value, null);
                table[hash & (VARIABLE_TABLE_DEFAULT_CAPACITY - 1)] = e;
                variableTableThreshold = (int)(VARIABLE_TABLE_DEFAULT_CAPACITY * VARIABLE_TABLE_LOAD_FACTOR);
                variableTableSize = 1;
                variableTable = table;
                return value;
            }
            int potentialNewSize;
            if ((potentialNewSize = variableTableSize + 1) > variableTableThreshold) {
                table = variableTableRehash();
            }
            int index;
            for (e = table[index = hash & (table.length - 1)]; e != null; e = e.next) {
                if (internedName == e.name) {
                    e.value = value;
                    return value;
                }
            }
            e = new VariableTableEntry(hash, internedName, value, table[index]);
            table[index] = e;
            variableTableSize = potentialNewSize;
            variableTable = table; // write-volatile
        }
        return value;
    }

    /**
     * Removes the entry with the specified name from the variable
     * table, and returning the removed value.
     */
    protected IRubyObject variableTableRemove(String name) {
        synchronized(this) {
            VariableTableEntry[] table;
            if ((table = variableTable) != null) {
                int hash = name.hashCode();
                int index = hash & (table.length - 1);
                VariableTableEntry first = table[index];
                VariableTableEntry e;
                for (e = first; e != null; e = e.next) {
                    if (hash == e.hash && name.equals(e.name)) {
                        IRubyObject oldValue = e.value;
                        // All entries following removed node can stay
                        // in list, but all preceding ones need to be
                        // cloned.
                        VariableTableEntry newFirst = e.next;
                        for (VariableTableEntry p = first; p != e; p = p.next) {
                            newFirst = new VariableTableEntry(p.hash, p.name, p.value, newFirst);
                        }
                        table[index] = newFirst;
                        variableTableSize--;
                        variableTable = table; // write-volatile 
                        return oldValue;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Get the actual table used to save variable entries.
     */
    protected VariableTableEntry[] variableTableGetTable() {
        VariableTableEntry[] table;
        if ((table = variableTable) != null) {
            return table;
        }
        return VARIABLE_TABLE_EMPTY_TABLE;
    }

    /**
     * Get the size of the variable table.
     */
    protected int variableTableGetSize() {
        if (variableTable != null) {
            return variableTableSize;
        }
        return 0;
    }

    /**
     * Synchronize the variable table with the argument. In real terms
     * this means copy all entries into a newly allocated table.
     */
    protected void variableTableSync(List<Variable<IRubyObject>> vars) {
        synchronized(this) {
            variableTableSize = 0;
            variableTableThreshold = (int)(VARIABLE_TABLE_DEFAULT_CAPACITY * VARIABLE_TABLE_LOAD_FACTOR);
            variableTable =  new VariableTableEntry[VARIABLE_TABLE_DEFAULT_CAPACITY];
            for (Variable<IRubyObject> var : vars) {
                variableTableStore(var.getName(), var.getValue());
            }
        }
    }

    /**
     * Rehashes the variable table. Must be called from a synchronized
     * block.
     */
    // MUST be called from synchronized/locked block!
    // should only be called by variableTableStore/variableTableFastStore
    protected final VariableTableEntry[] variableTableRehash() {
        VariableTableEntry[] oldTable = variableTable;
        int oldCapacity;
        if ((oldCapacity = oldTable.length) >= VARIABLE_TABLE_MAXIMUM_CAPACITY) {
            return oldTable;
        }

        int newCapacity = oldCapacity << 1;
        VariableTableEntry[] newTable = new VariableTableEntry[newCapacity];
        variableTableThreshold = (int)(newCapacity * VARIABLE_TABLE_LOAD_FACTOR);
        int sizeMask = newCapacity - 1;
        VariableTableEntry e;
        for (int i = oldCapacity; --i >= 0; ) {
            // We need to guarantee that any existing reads of old Map can
            //  proceed. So we cannot yet null out each bin.
            e = oldTable[i];

            if (e != null) {
                VariableTableEntry next = e.next;
                int idx = e.hash & sizeMask;

                //  Single node on list
                if (next == null)
                    newTable[idx] = e;

                else {
                    // Reuse trailing consecutive sequence at same slot
                    VariableTableEntry lastRun = e;
                    int lastIdx = idx;
                    for (VariableTableEntry last = next;
                         last != null;
                         last = last.next) {
                        int k = last.hash & sizeMask;
                        if (k != lastIdx) {
                            lastIdx = k;
                            lastRun = last;
                        }
                    }
                    newTable[lastIdx] = lastRun;

                    // Clone all remaining nodes
                    for (VariableTableEntry p = e; p != lastRun; p = p.next) {
                        int k = p.hash & sizeMask;
                        VariableTableEntry m = new VariableTableEntry(p.hash, p.name, p.value, newTable[k]);
                        newTable[k] = m;
                    }
                }
            }
        }
        variableTable = newTable;
        return newTable;
    }

    /**
     * Method to help ease transition to new variables implementation.
     * Will likely be deprecated in the near future.
     */
    @SuppressWarnings("unchecked")
    protected Map variableTableGetMap() {
        HashMap map = new HashMap();
        VariableTableEntry[] table;
        IRubyObject readValue;
        if ((table = variableTable) != null) {
            for (int i = table.length; --i >= 0; ) {
                for (VariableTableEntry e = table[i]; e != null; e = e.next) {
                    if ((readValue = e.value) == null) readValue = variableTableReadLocked(e);
                    map.put(e.name, readValue);
                }
            }
        }
        return map;
    }

    /**
     * Method to help ease transition to new variables implementation.
     * Will likely be deprecated in the near future.
     */
    @SuppressWarnings("unchecked")
    protected Map variableTableGetMap(Map map) {
        VariableTableEntry[] table;
        IRubyObject readValue;
        if ((table = variableTable) != null) {
            for (int i = table.length; --i >= 0; ) {
                for (VariableTableEntry e = table[i]; e != null; e = e.next) {
                    if ((readValue = e.value) == null) readValue = variableTableReadLocked(e);
                    map.put(e.name, readValue);
                }
            }
        }
        return map;
    }

    /**
     * Tries to support Java serialization of Ruby objects. This is
     * still experimental and might not work.
     */
    // NOTE: Serialization is primarily supported for testing purposes, and there is no general
    // guarantee that serialization will work correctly. Specifically, instance variables pointing
    // at symbols, threads, modules, classes, and other unserializable types are not detected.
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        // write out ivar count followed by name/value pairs
        List<String> names = getInstanceVariableNameList();
        out.writeInt(names.size());
        for (String name : names) {
            out.writeObject(name);
            out.writeObject(getInstanceVariables().getInstanceVariable(name));
        }
    }

    /**
     * Tries to support Java unserialization of Ruby objects. This is
     * still experimental and might not work.
     */
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        // rest in ivar count followed by name/value pairs
        int ivarCount = in.readInt();
        for (int i = 0; i < ivarCount; i++) {
            setInstanceVariable((String)in.readObject(), (IRubyObject)in.readObject());
        }
    }

}

package org.jruby;

import org.jruby.runtime.Block;
import org.jruby.runtime.builtin.IRubyObject;

/**
 *
 * @author nicksieger
 */
public interface RubyObjectAdapter {

    boolean isKindOf(IRubyObject value, RubyModule rubyModule);

    IRubyObject setInstanceVariable(IRubyObject obj, String variableName, IRubyObject value);

    IRubyObject[] convertToJavaArray(IRubyObject array);

    RubyInteger convertToRubyInteger(IRubyObject obj);

    IRubyObject getInstanceVariable(IRubyObject obj, String variableName);

    RubyString convertToRubyString(IRubyObject obj);
    
    // These call* assume ThreadContext = receiver.getRuntime().getCurrentContext()
    IRubyObject callMethod(IRubyObject receiver, String methodName);

    IRubyObject callMethod(IRubyObject receiver, String methodName, IRubyObject singleArg);

    IRubyObject callMethod(IRubyObject receiver, String methodName, IRubyObject[] args);

    IRubyObject callMethod(IRubyObject receiver, String methodName, IRubyObject[] args, Block block);

    IRubyObject callSuper(IRubyObject receiver, IRubyObject[] args);

    IRubyObject callSuper(IRubyObject receiver, IRubyObject[] args, Block block);
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.util.Iterator;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;
import org.jruby.runtime.Block;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;

@JRubyModule(name="ObjectSpace")
public class RubyObjectSpace {

    /** Create the ObjectSpace module and add it to the Ruby runtime.
     * 
     */
    public static RubyModule createObjectSpaceModule(Ruby runtime) {
        RubyModule objectSpaceModule = runtime.defineModule("ObjectSpace");
        runtime.setObjectSpaceModule(objectSpaceModule);
        
        objectSpaceModule.defineAnnotatedMethods(RubyObjectSpace.class);

        return objectSpaceModule;
    }

    @JRubyMethod(name = "define_finalizer", required = 1, optional = 1, frame = true, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject define_finalizer(IRubyObject recv, IRubyObject[] args, Block block) {
        Ruby runtime = recv.getRuntime();
        IRubyObject finalizer = null;
        if (args.length == 2) {
            finalizer = args[1];
            if (!finalizer.respondsTo("call")) {
                throw runtime.newArgumentError("wrong type argument "
                        + finalizer.getType() + " (should be callable)");
            }
        } else {
            finalizer = runtime.newProc(Block.Type.PROC, block);
        }
        IRubyObject obj = args[0];
        runtime.getObjectSpace().addFinalizer(obj, finalizer);
        return runtime.newArray(runtime.newFixnum(runtime.getSafeLevel()), finalizer);
    }

    @JRubyMethod(name = "undefine_finalizer", required = 1, frame = true, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject undefine_finalizer(IRubyObject recv, IRubyObject arg1, Block block) {
        recv.getRuntime().getObjectSpace().removeFinalizers(RubyNumeric.fix2long(arg1.id()));
        return recv;
    }

    @JRubyMethod(name = "_id2ref", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject id2ref(IRubyObject recv, IRubyObject id) {
        Ruby runtime = id.getRuntime();
        if (!(id instanceof RubyFixnum)) {
            throw recv.getRuntime().newTypeError(id, recv.getRuntime().getFixnum());
        }
        RubyFixnum idFixnum = (RubyFixnum) id;
        long longId = idFixnum.getLongValue();
        if (longId == 0) {
            return runtime.getFalse();
        } else if (longId == 2) {
            return runtime.getTrue();
        } else if (longId == 4) {
            return runtime.getNil();
        } else if (longId % 2 != 0) {
            // odd
            return runtime.newFixnum((longId - 1) / 2);
        } else {
            IRubyObject object = runtime.getObjectSpace().id2ref(longId);
            if (object == null) {
                return runtime.getNil();
            }
            return object;
        }
    }
    
    @JRubyMethod(name = "each_object", optional = 1, frame = true, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject each_object(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        RubyModule rubyClass;
        if (args.length == 0) {
            rubyClass = recv.getRuntime().getObject();
        } else {
            if (!(args[0] instanceof RubyModule)) throw recv.getRuntime().newTypeError("class or module required");
            rubyClass = (RubyModule) args[0];
        }
        Ruby runtime = recv.getRuntime();
        int count = 0;
        if (rubyClass != runtime.getClassClass()) {
            if (!runtime.isObjectSpaceEnabled()) {
                throw runtime.newRuntimeError("ObjectSpace is disabled; each_object will only work with Class, pass +O to enable");
            }
            Iterator iter = recv.getRuntime().getObjectSpace().iterator(rubyClass);
            
            IRubyObject obj = null;
            while ((obj = (IRubyObject)iter.next()) != null) {
                count++;
                block.yield(context, obj);
            }
        } else {
            Iterator iter = runtime.getObject().subclasses(true).iterator();
            
            while (iter.hasNext()) {
                count++;
                block.yield(context, (IRubyObject)iter.next());
            }
        }
        return recv.getRuntime().newFixnum(count);
    }

    @JRubyMethod(name = "garbage_collect", module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject garbage_collect(IRubyObject recv) {
        return RubyGC.start(recv);
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2002-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.callback.Callback;

/**
 *
 * @author  jpetersen
 */
@JRubyModule(name="Precision")
public class RubyPrecision {
    
    public static RubyModule createPrecisionModule(Ruby runtime) {
        RubyModule precisionModule = runtime.defineModule("Precision");
        runtime.setPrecision(precisionModule);
        
        precisionModule.defineAnnotatedMethods(RubyPrecision.class);
        
        return precisionModule;
    }

    public static IRubyObject induced_from(IRubyObject receiver, IRubyObject source, Block block) {
        throw receiver.getRuntime().newTypeError("Undefined conversion from " + source.getMetaClass().getName() + " into " + ((RubyClass)receiver).getName());
    }

    @JRubyMethod(name = "append_features", required = 1, frame = true, module = true)
    public static IRubyObject append_features(IRubyObject receiver, IRubyObject include, Block block) {
        if (include instanceof RubyModule) {
            ((RubyModule) include).includeModule(receiver);
            include.getSingletonClass().defineMethod("induced_from", new Callback() {

                public IRubyObject execute(IRubyObject recv, IRubyObject[] args, Block block) {
                    Arity.checkArgumentCount(recv.getRuntime(), args, 1, 1);
                    
                    return RubyPrecision.induced_from(recv, args[0], block);
                }

                public Arity getArity() {
                    return Arity.ONE_ARGUMENT;
                }
            });
        }
        return receiver;
    }
    
    
    @JRubyMethod(name = "prec", required = 1, frame = true)
    public static IRubyObject prec(ThreadContext context, IRubyObject receiver, IRubyObject type, Block block) {
        return type.callMethod(context, "induced_from", receiver);
    }

    @JRubyMethod(name = "prec_i", frame = true)
    public static IRubyObject prec_i(ThreadContext context, IRubyObject receiver, Block block) {
        return receiver.getRuntime().getInteger().callMethod(context, "induced_from", receiver);
    }

    @JRubyMethod(name = "prec_f", frame = true)
    public static IRubyObject prec_f(ThreadContext context, IRubyObject receiver, Block block) {
        return receiver.getRuntime().getFloat().callMethod(context, "induced_from", receiver);
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Chad Fowler <chadfowler@chadfowler.com>
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2002-2005 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2005 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2007 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.exceptions.JumpException;
import org.jruby.internal.runtime.JumpTarget;
import org.jruby.java.MiniJava;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;

/**
 * @author  jpetersen
 */
@JRubyClass(name="Proc")
public class RubyProc extends RubyObject implements JumpTarget {
    private Block block = Block.NULL_BLOCK;
    private Block.Type type;
    private String file;
    private int line;

    public RubyProc(Ruby runtime, RubyClass rubyClass, Block.Type type) {
        super(runtime, rubyClass);
        
        this.type = type;
    }
    
    private static ObjectAllocator PROC_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            RubyProc instance = RubyProc.newProc(runtime, Block.Type.PROC);

            instance.setMetaClass(klass);

            return instance;
        }
    };

    public static RubyClass createProcClass(Ruby runtime) {
        RubyClass procClass = runtime.defineClass("Proc", runtime.getObject(), PROC_ALLOCATOR);
        runtime.setProc(procClass);
        
        procClass.defineAnnotatedMethods(RubyProc.class);
        
        return procClass;
    }

    public Block getBlock() {
        return block;
    }

    // Proc class

    public static RubyProc newProc(Ruby runtime, Block.Type type) {
        return new RubyProc(runtime, runtime.getProc(), type);
    }
    public static RubyProc newProc(Ruby runtime, Block block, Block.Type type) {
        RubyProc proc = new RubyProc(runtime, runtime.getProc(), type);
        proc.callInit(NULL_ARRAY, block);
        
        return proc;
    }
    
    /**
     * Create a new instance of a Proc object.  We override this method (from RubyClass)
     * since we need to deal with special case of Proc.new with no arguments or block arg.  In 
     * this case, we need to check previous frame for a block to consume.
     */
    @JRubyMethod(name = "new", rest = true, frame = true, meta = true)
    public static IRubyObject newInstance(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        // No passed in block, lets check next outer frame for one ('Proc.new')
        if (!block.isGiven()) {
            block = context.getPreviousFrame().getBlock();
        }
        
        if (block.isGiven() && block.getProcObject() != null) {
            return block.getProcObject();
        }
        
        IRubyObject obj = ((RubyClass) recv).allocate();
        
        obj.callMethod(context, "initialize", args, block);
        return obj;
    }
    
    @JRubyMethod(name = "initialize", frame = true, visibility = Visibility.PRIVATE)
    public IRubyObject initialize(ThreadContext context, Block procBlock) {
        if (!procBlock.isGiven()) {
            throw getRuntime().newArgumentError("tried to create Proc object without a block");
        }
        
        if (type == Block.Type.LAMBDA && procBlock == null) {
            // TODO: warn "tried to create Proc object without a block"
        }
        
        block = procBlock.cloneBlock();
        block.type = type;
        block.setProcObject(this);

        file = context.getFile();
        line = context.getLine();
        return this;
    }
    
    @JRubyMethod(name = "clone")
    public IRubyObject rbClone() {
    	RubyProc newProc = new RubyProc(getRuntime(), getRuntime().getProc(), type);
    	newProc.block = getBlock();
    	newProc.file = file;
    	newProc.line = line;
    	// TODO: CLONE_SETUP here
    	return newProc;
    }

    @JRubyMethod(name = "dup")
    public IRubyObject dup() {
        RubyProc newProc = new RubyProc(getRuntime(), getRuntime().getProc(), type);
        newProc.block = getBlock();
        newProc.file = file;
        newProc.line = line;
        return newProc;
    }
    
    @JRubyMethod(name = "==", required = 1)
    public IRubyObject op_equal(IRubyObject other) {
        if (!(other instanceof RubyProc)) return getRuntime().getFalse();
        
        if (this == other || this.block == ((RubyProc)other).block) {
            return getRuntime().getTrue();
        }
        
        return getRuntime().getFalse();
    }
    
    @JRubyMethod(name = "to_s")
    public IRubyObject to_s() {
        return RubyString.newString(getRuntime(), 
                "#<Proc:0x" + Integer.toString(block.hashCode(), 16) + "@" + 
                file + ":" + (line + 1) + ">");
    }

    @JRubyMethod(name = "binding")
    public IRubyObject binding() {
        return getRuntime().newBinding(block.getBinding());
    }

    @JRubyMethod(name = {"call", "[]"}, rest = true, frame = true)
    public IRubyObject call(ThreadContext context, IRubyObject[] args) {
        return call(context, args, null);
    }
    
    public IRubyObject call(ThreadContext context, IRubyObject[] args, IRubyObject self) {
        assert args != null;
        
        Ruby runtime = getRuntime();
        Block newBlock = block.cloneBlock();
        JumpTarget jumpTarget = newBlock.getBinding().getFrame().getJumpTarget();
        
        try {
            if (self != null) newBlock.getBinding().setSelf(self);
            
            return newBlock.call(context, args);
        } catch (JumpException.BreakJump bj) {
            switch(block.type) {
            case LAMBDA: if (bj.getTarget() == jumpTarget) {
                return (IRubyObject) bj.getValue();
            } else {
                throw runtime.newLocalJumpError("break", (IRubyObject)bj.getValue(), "unexpected break");
            }
            case PROC:
                if (newBlock.isEscaped()) {
                    throw runtime.newLocalJumpError("break", (IRubyObject)bj.getValue(), "break from proc-closure");
                } else {
                    throw bj;
                }
            default: throw bj;
            }
        } catch (JumpException.ReturnJump rj) {
            Object target = rj.getTarget();
            
            if (target == jumpTarget && block.type == Block.Type.LAMBDA) return (IRubyObject) rj.getValue();

            if (type == Block.Type.THREAD) {
                throw runtime.newThreadError("return can't jump across threads");
            }
            throw rj;
        } catch (JumpException.RetryJump rj) {
            throw runtime.newLocalJumpError("retry", (IRubyObject)rj.getValue(), "retry not supported outside rescue");
        }
    }

    @JRubyMethod(name = "arity")
    public RubyFixnum arity() {
        return getRuntime().newFixnum(block.arity().getValue());
    }
    
    @JRubyMethod(name = "to_proc")
    public RubyProc to_proc() {
    	return this;
    }
    
    public IRubyObject as(Class asClass) {
        final Ruby ruby = getRuntime();
        if (!asClass.isInterface()) {
            throw ruby.newTypeError(asClass.getCanonicalName() + " is not an interface");
        }

        return MiniJava.javaToRuby(ruby, Proxy.newProxyInstance(Ruby.getClassLoader(), new Class[] {asClass}, new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                IRubyObject[] rubyArgs = new IRubyObject[args.length + 1];
                rubyArgs[0] = RubySymbol.newSymbol(ruby, method.getName());
                for (int i = 1; i < rubyArgs.length; i++) {
                    rubyArgs[i] = MiniJava.javaToRuby(ruby, args[i - 1]);
                }
                return MiniJava.rubyToJava(call(ruby.getCurrentContext(), rubyArgs));
            }
        }));
    }
}
/*
 **** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;
import org.jruby.ext.posix.POSIX;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.BlockCallback;
import org.jruby.runtime.CallBlock;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;


/**
 */

@JRubyModule(name="Process")
public class RubyProcess {

    public static RubyModule createProcessModule(Ruby runtime) {
        RubyModule process = runtime.defineModule("Process");
        runtime.setProcess(process);
        
        // TODO: NOT_ALLOCATABLE_ALLOCATOR is probably ok here. Confirm. JRUBY-415
        RubyClass process_status = process.defineClassUnder("Status", runtime.getObject(), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
        runtime.setProcStatus(process_status);
        
        RubyModule process_uid = process.defineModuleUnder("UID");
        runtime.setProcUID(process_uid);
        
        RubyModule process_gid = process.defineModuleUnder("GID");
        runtime.setProcGID(process_gid);
        
        RubyModule process_sys = process.defineModuleUnder("Sys");
        runtime.setProcSys(process_sys);
        
        process.defineAnnotatedMethods(RubyProcess.class);
        process_status.defineAnnotatedMethods(RubyStatus.class);
        process_uid.defineAnnotatedMethods(UserID.class);
        process_gid.defineAnnotatedMethods(GroupID.class);
        process_sys.defineAnnotatedMethods(Sys.class);

        process.defineConstant("PRIO_PROCESS", runtime.newFixnum(0));
        process.defineConstant("PRIO_PGRP", runtime.newFixnum(1));
        process.defineConstant("PRIO_USER", runtime.newFixnum(2));
        
        process.defineConstant("WNOHANG", runtime.newFixnum(1));
        
        return process;
    }

    @JRubyClass(name="Process::Status")
    public static class RubyStatus extends RubyObject {
        private long status = 0L;
        
        private static final long EXIT_SUCCESS = 0L;
        public RubyStatus(Ruby runtime, RubyClass metaClass, long status) {
            super(runtime, metaClass);
            this.status = status;
        }
        
        public static RubyStatus newProcessStatus(Ruby runtime, long status) {
            return new RubyStatus(runtime, runtime.getProcStatus(), status);
        }
        
        // Bunch of methods still not implemented
        @JRubyMethod(name = {"to_int", "pid", "stopped?", "stopsig", "signaled?", "termsig?", "exited?", "coredump?"})
        public IRubyObject not_implemented() {
            String error = "Process::Status#" + getRuntime().getCurrentContext().getFrameName() + " not implemented";
            throw getRuntime().newNotImplementedError(error);
        }
        
        @JRubyMethod(name = {"&"})
        public IRubyObject not_implemented1(IRubyObject arg) {
            String error = "Process::Status#" + getRuntime().getCurrentContext().getFrameName() + " not implemented";
            throw getRuntime().newNotImplementedError(error);
        }
        
        @JRubyMethod
        public IRubyObject exitstatus() {
            return getRuntime().newFixnum(status);
        }
        
        @JRubyMethod(name = ">>")
        public IRubyObject op_rshift(IRubyObject other) {
            long shiftValue = other.convertToInteger().getLongValue();
            return getRuntime().newFixnum(status >> shiftValue);
        }
        
        @JRubyMethod(name = "==")
        public IRubyObject op_equal(ThreadContext context, IRubyObject other) {
            return other.callMethod(context, MethodIndex.EQUALEQUAL, "==", this.to_i());
        }

        @JRubyMethod
        public IRubyObject to_i() {
            return getRuntime().newFixnum(shiftedValue());
        }
        
        @JRubyMethod
        public IRubyObject to_s() {
            return getRuntime().newString(String.valueOf(shiftedValue()));
        }
        
        @JRubyMethod
        public IRubyObject inspect() {
            return getRuntime().newString("#<Process::Status: pid=????,exited(" + String.valueOf(status) + ")>");
        }
        
        @JRubyMethod(name = "success?")
        public IRubyObject success_p() {
            return getRuntime().newBoolean(status == EXIT_SUCCESS);
        }
        
        private long shiftedValue() {
            return status << 8;
        }
    }

    @JRubyModule(name="Process::UID")
    public static class UserID {
        @JRubyMethod(name = "change_privilege", module = true)
        public static IRubyObject change_privilege(IRubyObject self, IRubyObject arg) {
            throw self.getRuntime().newNotImplementedError("Process::UID::change_privilege not implemented yet");
        }
        
        @JRubyMethod(name = "eid", module = true)
        public static IRubyObject eid(IRubyObject self) {
            return euid(self);
        }
        
        @JRubyMethod(name = "eid=", module = true)
        public static IRubyObject eid(IRubyObject self, IRubyObject arg) {
            return euid_set(self, arg);
        }
        
        @JRubyMethod(name = "grant_privilege", module = true)
        public static IRubyObject grant_privilege(IRubyObject self, IRubyObject arg) {
            throw self.getRuntime().newNotImplementedError("Process::UID::grant_privilege not implemented yet");
        }
        
        @JRubyMethod(name = "re_exchange", module = true)
        public static IRubyObject re_exchange(ThreadContext context, IRubyObject self) {
            return switch_rb(context, self, Block.NULL_BLOCK);
        }
        
        @JRubyMethod(name = "re_exchangeable?", module = true)
        public static IRubyObject re_exchangeable_p(IRubyObject self) {
            throw self.getRuntime().newNotImplementedError("Process::UID::re_exchangeable? not implemented yet");
        }
        
        @JRubyMethod(name = "rid", module = true)
        public static IRubyObject rid(IRubyObject self) {
            return uid(self);
        }
        
        @JRubyMethod(name = "sid_available?", module = true)
        public static IRubyObject sid_available_p(IRubyObject self) {
            throw self.getRuntime().newNotImplementedError("Process::UID::sid_available not implemented yet");
        }
        
        @JRubyMethod(name = "switch", module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject switch_rb(ThreadContext context, IRubyObject self, Block block) {
            Ruby runtime = self.getRuntime();
            int uid = runtime.getPosix().getuid();
            int euid = runtime.getPosix().geteuid();
            
            if (block.isGiven()) {
                try {
                    runtime.getPosix().seteuid(uid);
                    runtime.getPosix().setuid(euid);
                    
                    return block.yield(context, runtime.getNil());
                } finally {
                    runtime.getPosix().seteuid(euid);
                    runtime.getPosix().setuid(uid);
                }
            } else {
                runtime.getPosix().seteuid(uid);
                runtime.getPosix().setuid(euid);
                
                return RubyFixnum.zero(runtime);
            }
        }
    }
    
    @JRubyModule(name="Process::GID")
    public static class GroupID {
        @JRubyMethod(name = "change_privilege", module = true)
        public static IRubyObject change_privilege(IRubyObject self, IRubyObject arg) {
            throw self.getRuntime().newNotImplementedError("Process::GID::change_privilege not implemented yet");
        }
        
        @JRubyMethod(name = "eid", module = true)
        public static IRubyObject eid(IRubyObject self) {
            return egid(self);
        }
        
        @JRubyMethod(name = "eid=", module = true)
        public static IRubyObject eid(IRubyObject self, IRubyObject arg) {
            return RubyProcess.egid_set(self, arg);
        }
        
        @JRubyMethod(name = "grant_privilege", module = true)
        public static IRubyObject grant_privilege(IRubyObject self, IRubyObject arg) {
            throw self.getRuntime().newNotImplementedError("Process::GID::grant_privilege not implemented yet");
        }
        
        @JRubyMethod(name = "re_exchange", module = true)
        public static IRubyObject re_exchange(ThreadContext context, IRubyObject self) {
            return switch_rb(context, self, Block.NULL_BLOCK);
        }
        
        @JRubyMethod(name = "re_exchangeable?", module = true)
        public static IRubyObject re_exchangeable_p(IRubyObject self) {
            throw self.getRuntime().newNotImplementedError("Process::GID::re_exchangeable? not implemented yet");
        }
        
        @JRubyMethod(name = "rid", module = true)
        public static IRubyObject rid(IRubyObject self) {
            return gid(self);
        }
        
        @JRubyMethod(name = "sid_available?", module = true)
        public static IRubyObject sid_available_p(IRubyObject self) {
            throw self.getRuntime().newNotImplementedError("Process::GID::sid_available not implemented yet");
        }
        
        @JRubyMethod(name = "switch", module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject switch_rb(ThreadContext context, IRubyObject self, Block block) {
            Ruby runtime = self.getRuntime();
            int gid = runtime.getPosix().getgid();
            int egid = runtime.getPosix().getegid();
            
            if (block.isGiven()) {
                try {
                    runtime.getPosix().setegid(gid);
                    runtime.getPosix().setgid(egid);
                    
                    return block.yield(context, runtime.getNil());
                } finally {
                    runtime.getPosix().setegid(egid);
                    runtime.getPosix().setgid(gid);
                }
            } else {
                runtime.getPosix().setegid(gid);
                runtime.getPosix().setgid(egid);
                
                return RubyFixnum.zero(runtime);
            }
        }
    }
    
    @JRubyModule(name="Process::Sys")
    public static class Sys {
        @JRubyMethod(name = "getegid", module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject getegid(IRubyObject self) {
            return egid(self);
        }
        
        @JRubyMethod(name = "geteuid", module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject geteuid(IRubyObject self) {
            return euid(self);
        }
        
        @JRubyMethod(name = "getgid", module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject getgid(IRubyObject self) {
            return gid(self);
        }
        
        @JRubyMethod(name = "getuid", module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject getuid(IRubyObject self) {
            return uid(self);
        }

        @JRubyMethod(name = "setegid", module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject setegid(IRubyObject recv, IRubyObject arg) {
            return egid_set(recv, arg);
        }

        @JRubyMethod(name = "seteuid", module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject seteuid(IRubyObject recv, IRubyObject arg) {
            return euid_set(recv, arg);
        }

        @JRubyMethod(name = "setgid", module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject setgid(IRubyObject recv, IRubyObject arg) {
            return gid_set(recv, arg);
        }

        @JRubyMethod(name = "setuid", module = true, visibility = Visibility.PRIVATE)
        public static IRubyObject setuid(IRubyObject recv, IRubyObject arg) {
            return uid_set(recv, arg);
        }
    }

    @JRubyMethod(name = "abort", optional = 1, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject abort(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        return RubyKernel.abort(context, recv, args);
    }

    @JRubyMethod(name = "exit!", optional = 1, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject exit_bang(IRubyObject recv, IRubyObject[] args) {
        return RubyKernel.exit_bang(recv, args);
    }

    @JRubyMethod(name = "groups", module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject groups(IRubyObject recv) {
        throw recv.getRuntime().newNotImplementedError("Process#groups not yet implemented");
    }

    @JRubyMethod(name = "setrlimit", rest = true, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject setrlimit(IRubyObject recv, IRubyObject[] args) {
        throw recv.getRuntime().newNotImplementedError("Process#setrlimit not yet implemented");
    }

    @JRubyMethod(name = "getpgrp", module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject getpgrp(IRubyObject recv) {
        return recv.getRuntime().newFixnum(recv.getRuntime().getPosix().getpgrp());
    }

    @JRubyMethod(name = "groups=", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject groups_set(IRubyObject recv, IRubyObject arg) {
        throw recv.getRuntime().newNotImplementedError("Process#groups not yet implemented");
    }

    @JRubyMethod(name = "waitpid", rest = true, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject waitpid(IRubyObject recv, IRubyObject[] args) {
        Ruby runtime = recv.getRuntime();
        int pid = -1;
        int flags = 0;
        if (args.length > 0) {
            pid = (int)args[0].convertToInteger().getLongValue();
        }
        if (args.length > 1) {
            flags = (int)args[1].convertToInteger().getLongValue();
        }
        
        int[] status = new int[1];
        pid = runtime.getPosix().waitpid(pid, status, flags);
        
        if (pid == -1) {
            throw runtime.newErrnoECHILDError();
        }
        
        runtime.getGlobalVariables().set(
                "$?", 
                RubyProcess.RubyStatus.newProcessStatus(runtime, status[0]));
        return runtime.newFixnum(pid);
    }

    @JRubyMethod(name = "wait", rest = true, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject wait(IRubyObject recv, IRubyObject[] args) {
        Ruby runtime = recv.getRuntime();
        
        if (args.length > 0) {
            return waitpid(recv, args);
        }
        
        int[] status = new int[1];
        int pid = runtime.getPosix().wait(status);
        
        if (pid == -1) {
            throw runtime.newErrnoECHILDError();
        }
        
        runtime.getGlobalVariables().set(
                "$?", 
                RubyProcess.RubyStatus.newProcessStatus(runtime, status[0]));
        return runtime.newFixnum(pid);
    }

    @JRubyMethod(name = "waitall", module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject waitall(IRubyObject recv) {
        Ruby runtime = recv.getRuntime();
        POSIX posix = runtime.getPosix();
        RubyArray results = recv.getRuntime().newArray();
        
        int[] status = new int[1];
        int result = posix.wait(status);
        while (result != -1) {
            results.append(runtime.newArray(runtime.newFixnum(result), RubyProcess.RubyStatus.newProcessStatus(runtime, status[0])));
            result = posix.wait(status);
        }
        
        return results;
    }

    @JRubyMethod(name = "setsid", module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject setsid(IRubyObject recv) {
        return recv.getRuntime().newFixnum(recv.getRuntime().getPosix().setsid());
    }

    @JRubyMethod(name = "setpgrp", module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject setpgrp(IRubyObject recv) {
        return recv.getRuntime().newFixnum(recv.getRuntime().getPosix().setpgid(0, 0));
    }

    @JRubyMethod(name = "egid=", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject egid_set(IRubyObject recv, IRubyObject arg) {
        recv.getRuntime().getPosix().setegid((int)arg.convertToInteger().getLongValue());
        return RubyFixnum.zero(recv.getRuntime());
    }

    @JRubyMethod(name = "euid", module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject euid(IRubyObject recv) {
        return recv.getRuntime().newFixnum(recv.getRuntime().getPosix().geteuid());
    }

    @JRubyMethod(name = "uid=", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject uid_set(IRubyObject recv, IRubyObject arg) {
        recv.getRuntime().getPosix().setuid((int)arg.convertToInteger().getLongValue());
        return RubyFixnum.zero(recv.getRuntime());
    }

    @JRubyMethod(name = "gid", module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject gid(IRubyObject recv) {
        return recv.getRuntime().newFixnum(recv.getRuntime().getPosix().getgid());
    }

    @JRubyMethod(name = "maxgroups", module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject maxgroups(IRubyObject recv) {
        throw recv.getRuntime().newNotImplementedError("Process#maxgroups not yet implemented");
    }

    @JRubyMethod(name = "getpriority", required = 2, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject getpriority(IRubyObject recv, IRubyObject arg1, IRubyObject arg2) {
        int which = (int)arg1.convertToInteger().getLongValue();
        int who = (int)arg2.convertToInteger().getLongValue();
        int result = recv.getRuntime().getPosix().getpriority(which, who);
        
        return recv.getRuntime().newFixnum(result);
    }

    @JRubyMethod(name = "uid", module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject uid(IRubyObject recv) {
        return recv.getRuntime().newFixnum(recv.getRuntime().getPosix().getuid());
    }

    @JRubyMethod(name = "waitpid2", rest = true, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject waitpid2(IRubyObject recv, IRubyObject[] args) {
        Ruby runtime = recv.getRuntime();
        int pid = -1;
        int flags = 0;
        if (args.length > 0) {
            pid = (int)args[0].convertToInteger().getLongValue();
        }
        if (args.length > 1) {
            flags = (int)args[1].convertToInteger().getLongValue();
        }
        
        int[] status = new int[1];
        pid = runtime.getPosix().waitpid(pid, status, flags);
        
        if (pid == -1) {
            throw runtime.newErrnoECHILDError();
        }
        
        return runtime.newArray(runtime.newFixnum(pid), RubyProcess.RubyStatus.newProcessStatus(runtime, status[0]));
    }

    @JRubyMethod(name = "initgroups", required = 2, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject initgroups(IRubyObject recv, IRubyObject arg1, IRubyObject arg2) {
        throw recv.getRuntime().newNotImplementedError("Process#initgroups not yet implemented");
    }

    @JRubyMethod(name = "maxgroups=", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject maxgroups_set(IRubyObject recv, IRubyObject arg) {
        throw recv.getRuntime().newNotImplementedError("Process#maxgroups_set not yet implemented");
    }

    @JRubyMethod(name = "ppid", module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject ppid(IRubyObject recv) {
        return recv.getRuntime().newFixnum(recv.getRuntime().getPosix().getppid());
    }

    @JRubyMethod(name = "gid=", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject gid_set(IRubyObject recv, IRubyObject arg) {
        return recv.getRuntime().newFixnum(recv.getRuntime().getPosix().setgid((int)arg.convertToInteger().getLongValue()));
    }

    @JRubyMethod(name = "wait2", rest = true, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject wait2(IRubyObject recv, IRubyObject[] args) {
        return waitpid2(recv, args);
    }

    @JRubyMethod(name = "euid=", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject euid_set(IRubyObject recv, IRubyObject arg) {
        recv.getRuntime().getPosix().seteuid((int)arg.convertToInteger().getLongValue());
        return RubyFixnum.zero(recv.getRuntime());
    }

    @JRubyMethod(name = "setpriority", required = 3, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject setpriority(IRubyObject recv, IRubyObject arg1, IRubyObject arg2, IRubyObject arg3) {
        int which = (int)arg1.convertToInteger().getLongValue();
        int who = (int)arg2.convertToInteger().getLongValue();
        int prio = (int)arg3.convertToInteger().getLongValue();
        int result = recv.getRuntime().getPosix().setpriority(which, who, prio);
        
        return recv.getRuntime().newFixnum(result);
    }

    @JRubyMethod(name = "setpgid", required = 2, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject setpgid(IRubyObject recv, IRubyObject arg1, IRubyObject arg2) {
        int pid = (int)arg1.convertToInteger().getLongValue();
        int gid = (int)arg2.convertToInteger().getLongValue();
        return recv.getRuntime().newFixnum(recv.getRuntime().getPosix().setpgid(pid, gid));
    }

    @JRubyMethod(name = "getpgid", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject getpgid(IRubyObject recv, IRubyObject arg) {
        return recv.getRuntime().newFixnum(recv.getRuntime().getPosix().getpgid((int)arg.convertToInteger().getLongValue()));
    }

    @JRubyMethod(name = "getrlimit", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject getrlimit(IRubyObject recv, IRubyObject arg) {
        throw recv.getRuntime().newNotImplementedError("Process#getrlimit not yet implemented");
    }

    @JRubyMethod(name = "egid", module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject egid(IRubyObject recv) {
        return recv.getRuntime().newFixnum(recv.getRuntime().getPosix().getegid());
    }
    
    private static String[] signals = new String[] {"EXIT", "HUP", "INT", "QUIT", "ILL", "TRAP", 
        "ABRT", "POLL", "FPE", "KILL", "BUS", "SEGV", "SYS", "PIPE", "ALRM", "TERM", "URG", "STOP",
        "TSTP", "CONT", "CHLD", "TTIN", "TTOU", "XCPU", "XFSZ", "VTALRM", "PROF", "USR1", "USR2"};
    
    private static int parseSignalString(Ruby runtime, String value) {
        int startIndex = 0;
        boolean negative = value.startsWith("-");
        
        if (negative) startIndex++;
        
        boolean signalString = value.startsWith("SIG", startIndex);
        
        if (signalString) startIndex += 3;
       
        String signalName = value.substring(startIndex);
        
        // FIXME: This table will get moved into POSIX library so we can get all actual supported
        // signals.  This is a quick fix to support basic signals until that happens.
        for (int i = 0; i < signals.length; i++) {
            if (signals[i].equals(signalName)) return negative ? -i : i;
        }
        
        throw runtime.newArgumentError("unsupported name `SIG" + signalName + "'");
    }

    @JRubyMethod(name = "kill", rest = true, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject kill(IRubyObject recv, IRubyObject[] args) {
        if (args.length < 2) {
            throw recv.getRuntime().newArgumentError("wrong number of arguments -- kill(sig, pid...)");
        }
        
        Ruby runtime = recv.getRuntime();
        int signal;
        if (args[0] instanceof RubyFixnum) {
            signal = (int) ((RubyFixnum) args[0]).getLongValue();
        } else if (args[0] instanceof RubySymbol) {
            signal = parseSignalString(runtime, args[0].toString());
        } else if (args[0] instanceof RubyString) {
            signal = parseSignalString(runtime, args[0].toString());
        } else {
            signal = parseSignalString(runtime, args[0].checkStringType().toString());
        }

        boolean processGroupKill = signal < 0;
        
        if (processGroupKill) signal = -signal;
        
        POSIX posix = runtime.getPosix();
        for (int i = 1; i < args.length; i++) {
            int pid = RubyNumeric.num2int(args[i]);

            // FIXME: It may be possible to killpg on systems which support it.  POSIX library
            // needs to tell whether a particular method works or not
            if (pid == 0) pid = runtime.getPosix().getpid();
            posix.kill(processGroupKill ? -pid : pid, signal);            
        }
        
        return runtime.newFixnum(args.length - 1);

    }

    @JRubyMethod(name = "detach", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject detach(ThreadContext context, IRubyObject recv, IRubyObject arg) {
        final int pid = (int)arg.convertToInteger().getLongValue();
        Ruby runtime = recv.getRuntime();
        
        BlockCallback callback = new BlockCallback() {
            public IRubyObject call(ThreadContext context, IRubyObject[] args, Block block) {
                int[] status = new int[1];
                int result = context.getRuntime().getPosix().waitpid(pid, status, 0);
                
                return context.getRuntime().newFixnum(result);
            }
        };
        
        return RubyThread.newInstance(
                runtime.getThread(),
                IRubyObject.NULL_ARRAY,
                CallBlock.newCallClosure(recv, (RubyModule)recv, Arity.NO_ARGUMENTS, callback, context));
    }
    
    @JRubyMethod(name = "times", frame = true, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject times(IRubyObject recv, Block unusedBlock) {
        Ruby runtime = recv.getRuntime();
        double currentTime = System.currentTimeMillis() / 1000.0;
        double startTime = runtime.getStartTime() / 1000.0;
        RubyFloat zero = runtime.newFloat(0.0);
        return RubyStruct.newStruct(runtime.getTmsStruct(), 
                new IRubyObject[] { runtime.newFloat(currentTime - startTime), zero, zero, zero }, 
                Block.NULL_BLOCK);
    }

    @JRubyMethod(name = "pid", module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject pid(IRubyObject recv) {
        return recv.getRuntime().newFixnum(recv.getRuntime().getPosix().getpid());
    }
    
    @JRubyMethod(name = "fork", module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject fork(ThreadContext context, IRubyObject recv, Block block) {
        return RubyKernel.fork(context, recv, block);
    }
    
    @JRubyMethod(name = "exit", optional = 1, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject exit(IRubyObject recv, IRubyObject[] args) {
        return RubyKernel.exit(recv, args);
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Chad Fowler <chadfowler@chadfowler.com>
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001 Ed Sinjiashvili <slorcim@users.sourceforge.net>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2002-2006 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2005 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2006 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.io.IOException;
import java.util.List;

import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.BlockCallback;
import org.jruby.runtime.CallBlock;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ObjectMarshal;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.builtin.Variable;
import org.jruby.runtime.component.VariableEntry;
import org.jruby.runtime.marshal.MarshalStream;
import org.jruby.runtime.marshal.UnmarshalStream;

/**
 * @author jpetersen
 */
@JRubyClass(name="Range", include="Enumerable")
public class RubyRange extends RubyObject {
    private IRubyObject begin;
    private IRubyObject end;
    private boolean isExclusive;

    public static RubyClass createRangeClass(Ruby runtime) {
        RubyClass result = runtime.defineClass("Range", runtime.getObject(), RANGE_ALLOCATOR);
        runtime.setRange(result);
        result.kindOf = new RubyModule.KindOf() {
            public boolean isKindOf(IRubyObject obj, RubyModule type) {
                return obj instanceof RubyRange;
            }
        };
        
        result.setMarshal(RANGE_MARSHAL);
        result.includeModule(runtime.getEnumerable());

        // We override Enumerable#member? since ranges in 1.8.1 are continuous.
        //        result.defineMethod("member?", callbackFactory.getMethod("include_p", RubyKernel.IRUBY_OBJECT));
        //        result.defineMethod("===", callbackFactory.getMethod("include_p", RubyKernel.IRUBY_OBJECT));

        result.defineAnnotatedMethods(RubyRange.class);
        return result;
    }

    private static final ObjectAllocator RANGE_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return new RubyRange(runtime, klass);
        }
    };    

    private RubyRange(Ruby runtime, RubyClass klass) {
        super(runtime, klass);
        begin = end = runtime.getNil();
    }

    public static RubyRange newRange(Ruby runtime, ThreadContext context, IRubyObject begin, IRubyObject end, boolean isExclusive) {
        RubyRange range = new RubyRange(runtime, runtime.getRange());
        range.init(context, begin, end, isExclusive);
        return range;
    }

    public static RubyRange newExclusiveRange(Ruby runtime, ThreadContext context, IRubyObject begin, IRubyObject end) {
        RubyRange range = new RubyRange(runtime, runtime.getRange());
        range.init(context, begin, end, true);
        return range;
    }

    public static RubyRange newInclusiveRange(Ruby runtime, ThreadContext context, IRubyObject begin, IRubyObject end) {
        RubyRange range = new RubyRange(runtime, runtime.getRange());
        range.init(context, begin, end, false);
        return range;
    }

    protected void copySpecialInstanceVariables(IRubyObject clone) {
        RubyRange range = (RubyRange)clone;
        range.begin = begin;
        range.end = end;
        range.isExclusive = isExclusive;
    }

    final long[] begLen(long len, int err){
        long beg = RubyNumeric.num2long(this.begin);
        long end = RubyNumeric.num2long(this.end);

        if (beg < 0) {
            beg += len;
            if (beg < 0) {
                if (err != 0) throw getRuntime().newRangeError(beg + ".." + (isExclusive ? "." : "") + end + " out of range");
                return null;
            }
        }

        if (err == 0 || err == 2) {
            if (beg > len) {
                if (err != 0) throw getRuntime().newRangeError(beg + ".." + (isExclusive ? "." : "") + end + " out of range");
                return null;
            }
            if (end > len) end = len;
        }

        if (end < 0) end += len;
        if (!isExclusive) end++;
        len = end - beg;
        if (len < 0) len = 0;

        return new long[]{beg, len};
    }    

    private void init(ThreadContext context, IRubyObject begin, IRubyObject end, boolean isExclusive) {
        if (!(begin instanceof RubyFixnum && end instanceof RubyFixnum)) {
            try {
                IRubyObject result = begin.callMethod(context, MethodIndex.OP_SPACESHIP, "<=>", end);
                if (result.isNil()) throw getRuntime().newArgumentError("bad value for range");
            } catch (RaiseException re) {
                throw getRuntime().newArgumentError("bad value for range");
            }
        }

        this.begin = begin;
        this.end = end;
        this.isExclusive = isExclusive;
    }
    
    @JRubyMethod(name = "initialize", required = 2, optional = 1, frame = true)
    public IRubyObject initialize(ThreadContext context, IRubyObject[] args, Block unusedBlock) {
        init(context, args[0], args[1], args.length > 2 && args[2].isTrue());
        return getRuntime().getNil();
    }

    @JRubyMethod(name = {"first", "begin"})
    public IRubyObject first() {
        return begin;
    }

    @JRubyMethod(name = {"last", "end"})
    public IRubyObject last() {
        return end;
    }
    
    @JRubyMethod(name = "hash")
    public RubyFixnum hash(ThreadContext context) {
        long hash = isExclusive ? 1 : 0;
        long h = hash;
        
        long v = begin.callMethod(context, MethodIndex.HASH, "hash").convertToInteger().getLongValue();
        hash ^= v << 1;
        v = end.callMethod(context, MethodIndex.HASH, "hash").convertToInteger().getLongValue();
        hash ^= v << 9;
        hash ^= h << 24;
        return getRuntime().newFixnum(hash);
    }
    
    private static byte[] DOTDOTDOT = "...".getBytes();
    private static byte[] DOTDOT = "..".getBytes();

    @JRubyMethod(name = "inspect")
    public IRubyObject inspect(ThreadContext context) {
        RubyString str = inspect(context, begin).strDup(context.getRuntime());
        RubyString str2 = inspect(context, end);

        str.cat(isExclusive ? DOTDOTDOT : DOTDOT);
        str.concat(str2);
        str.infectBy(str2);
        return str;
    }
    
    @JRubyMethod(name = "to_s")
    public IRubyObject to_s(ThreadContext context) {
        RubyString str = RubyString.objAsString(context, begin).strDup(context.getRuntime());
        RubyString str2 = RubyString.objAsString(context, end);

        str.cat(isExclusive ? DOTDOTDOT : DOTDOT);
        str.concat(str2);
        str.infectBy(str2);
        return str;

    }

    @JRubyMethod(name = "exclude_end?")
    public RubyBoolean exclude_end_p() {
        return getRuntime().newBoolean(isExclusive);
    }

    @JRubyMethod(name = "==", required = 1)
    public IRubyObject op_equal(ThreadContext context, IRubyObject other) {
        if (this == other) return getRuntime().getTrue();
        if (!(other instanceof RubyRange)) return getRuntime().getFalse();
        RubyRange otherRange = (RubyRange) other;

        if (equalInternal(context, begin, otherRange.begin) &&
            equalInternal(context, end, otherRange.end) &&
            isExclusive == otherRange.isExclusive) return getRuntime().getTrue();

        return getRuntime().getFalse();
    }

    @JRubyMethod(name = "eql?", required = 1)
    public IRubyObject eql_p(ThreadContext context, IRubyObject other) {
        if (this == other) return getRuntime().getTrue();
        if (!(other instanceof RubyRange)) return getRuntime().getFalse();
        RubyRange otherRange = (RubyRange)other;

        if (eqlInternal(context, begin, otherRange.begin) &&
            eqlInternal(context, end, otherRange.end) &&
            isExclusive == otherRange.isExclusive) return getRuntime().getTrue();

        return getRuntime().getFalse();
    }

    private static abstract class RangeCallBack {
        abstract void call(ThreadContext context, IRubyObject arg);
    }

    private static final class StepBlockCallBack extends RangeCallBack implements BlockCallback {
        final Block block;
        IRubyObject iter;
        final IRubyObject step;

        StepBlockCallBack(Block block, IRubyObject iter, IRubyObject step) {
            this.block = block;
            this.iter = iter;
            this.step = step;
        }

        public IRubyObject call(ThreadContext context, IRubyObject[] args, Block originalBlock) {
            call(context, args[0]);
            return context.getRuntime().getNil();
        }

        void call(ThreadContext context, IRubyObject arg) {
            if (iter instanceof RubyFixnum) {
                iter = RubyFixnum.newFixnum(context.getRuntime(), ((RubyFixnum)iter).getLongValue() - 1);                
            } else {
                iter = iter.callMethod(context, MethodIndex.OP_MINUS, "-", RubyFixnum.one(context.getRuntime()));
            }
            if (iter == RubyFixnum.zero(context.getRuntime())) {
                block.yield(context, arg);
                iter = step;
            }
        }
    }

    private IRubyObject rangeLt(ThreadContext context, IRubyObject a, IRubyObject b) {
        IRubyObject result = a.callMethod(context, MethodIndex.OP_SPACESHIP, "<=>", b);
        if (result.isNil()) return null;
        return RubyComparable.cmpint(context, result, a, b) < 0 ? getRuntime().getTrue() : null;
    }

    private IRubyObject rangeLe(ThreadContext context, IRubyObject a, IRubyObject b) {
        IRubyObject result = a.callMethod(context, MethodIndex.OP_SPACESHIP, "<=>", b);
        if (result.isNil()) return null;
        int c = RubyComparable.cmpint(context, result, a, b);
        if (c == 0) return RubyFixnum.zero(getRuntime());
        return c < 0 ? getRuntime().getTrue() : null;
    }    

    private void rangeEach(ThreadContext context, RangeCallBack callback) {
        IRubyObject v = begin;
        if (isExclusive) {
            while (rangeLt(context, v, end) != null) {
                callback.call(context, v);
                v = v.callMethod(context, "succ");
            }
        } else {
            IRubyObject c;
            while ((c = rangeLe(context, v, end)) != null && c.isTrue()) {
                callback.call(context, v);
                if (c == RubyFixnum.zero(getRuntime())) break;
                v = v.callMethod(context, "succ");
            }
        }
    }

    @JRubyMethod(name = "each", frame = true)
    public IRubyObject each(ThreadContext context, final Block block) {
        final Ruby runtime = context.getRuntime();

        if (begin instanceof RubyFixnum && end instanceof RubyFixnum) {
            long lim = ((RubyFixnum) end).getLongValue();
            if (!isExclusive) lim++;

            for (long i = ((RubyFixnum) begin).getLongValue(); i < lim; i++) {
                block.yield(context, RubyFixnum.newFixnum(runtime, i));
            }
        } else if (begin instanceof RubyString) {
            ((RubyString) begin).upto(context, end, isExclusive, block);
        } else {
            if (!begin.respondsTo("succ")) throw getRuntime().newTypeError(
                    "can't iterate from " + begin.getMetaClass().getName());
            rangeEach(context, new RangeCallBack() {
                @Override
                void call(ThreadContext context, IRubyObject arg) {
                    block.yield(context, arg);
                }
            });
        }
        return this;
    }

    @JRubyMethod(name = "step", optional = 1, frame = true)
    public IRubyObject step(ThreadContext context, IRubyObject[] args, Block block) {
        final Ruby runtime = context.getRuntime();
        final IRubyObject step;
        if (args.length == 0) {
            step = RubyFixnum.one(runtime);
        } else {
            step = args[0];
        }

        long unit = RubyNumeric.num2long(step);
        if (unit < 0) throw runtime.newArgumentError("step can't be negative");

        if (begin instanceof RubyFixnum && end instanceof RubyFixnum) {
            if (unit == 0) throw runtime.newArgumentError("step can't be 0");

            long e = ((RubyFixnum)end).getLongValue();
            if (!isExclusive) e++;
            
            for (long i = ((RubyFixnum)begin).getLongValue(); i < e; i += unit) {
                block.yield(context, RubyFixnum.newFixnum(runtime, i));
            }
        } else {
            IRubyObject tmp = begin.checkStringType();
            if (!tmp.isNil()) {
                if (unit == 0) throw runtime.newArgumentError("step can't be 0");
                // rb_iterate((VALUE(*)_((VALUE)))str_step, (VALUE)args, step_i, (VALUE)iter);
                StepBlockCallBack callback = new StepBlockCallBack(block, RubyFixnum.one(runtime), step);
                Block blockCallback = CallBlock.newCallClosure(this, runtime.getRange(), Arity.singleArgument(), callback, context);
                ((RubyString)tmp).upto(context, end, isExclusive, blockCallback);
            } else if (begin instanceof RubyNumeric) {
                if (equalInternal(context, step, RubyFixnum.zero(runtime))) {
                    throw runtime.newArgumentError("step can't be 0");
                }
                final String method;
                final int methodIndex;
                if (isExclusive) {
                    method = "<";
                    methodIndex = MethodIndex.OP_LT;
                } else {
                    method = "<=";
                    methodIndex = MethodIndex.OP_LE;
                }
                IRubyObject beg = begin;
                while (beg.callMethod(context, methodIndex, method, end).isTrue()) {
                    block.yield(context, beg);
                    beg = beg.callMethod(context, MethodIndex.OP_PLUS, "+", step);
                }
            } else {
                if (unit == 0) throw runtime.newArgumentError("step can't be 0");
                if (!begin.respondsTo("succ")) throw runtime.newTypeError(
                        "can't iterate from " + begin.getMetaClass().getName());
                // range_each_func(range, step_i, b, e, args);
                rangeEach(context, new StepBlockCallBack(block, RubyFixnum.one(runtime), step));
            }
        }
        return this;
    }

    @JRubyMethod(name = {"include?", "member?", "==="}, required = 1)
    public RubyBoolean include_p(ThreadContext context, IRubyObject obj) {
        if (rangeLe(context, begin, obj) != null) {
            if (isExclusive) {
                if (rangeLt(context, obj, end) != null) return context.getRuntime().getTrue();
            } else {
                if (rangeLe(context, obj, end) != null) return context.getRuntime().getTrue();
            }
        }
        return context.getRuntime().getFalse();
    }

    private static final ObjectMarshal RANGE_MARSHAL = new ObjectMarshal() {
        public void marshalTo(Ruby runtime, Object obj, RubyClass type,
                              MarshalStream marshalStream) throws IOException {
            RubyRange range = (RubyRange)obj;

            marshalStream.registerLinkTarget(range);
            List<Variable<IRubyObject>> attrs = range.getVariableList();

            attrs.add(new VariableEntry<IRubyObject>("begin", range.begin));
            attrs.add(new VariableEntry<IRubyObject>("end", range.end));
            attrs.add(new VariableEntry<IRubyObject>("excl", range.isExclusive ? runtime.getTrue() : runtime.getFalse()));

            marshalStream.dumpVariables(attrs);
        }

        public Object unmarshalFrom(Ruby runtime, RubyClass type,
                                    UnmarshalStream unmarshalStream) throws IOException {
            RubyRange range = (RubyRange)type.allocate();
            
            unmarshalStream.registerLinkTarget(range);

            unmarshalStream.defaultVariablesUnmarshal(range);
            
            range.begin = range.removeInternalVariable("begin");
            range.end = range.removeInternalVariable("end");
            range.isExclusive = range.removeInternalVariable("excl").isTrue();

            return range;
        }
    };
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import static org.jruby.util.Numeric.f_add;
import static org.jruby.util.Numeric.f_cmp;
import static org.jruby.util.Numeric.f_div;
import static org.jruby.util.Numeric.f_equal_p;
import static org.jruby.util.Numeric.f_expt;
import static org.jruby.util.Numeric.f_floor;
import static org.jruby.util.Numeric.f_gcd;
import static org.jruby.util.Numeric.f_idiv;
import static org.jruby.util.Numeric.f_mul;
import static org.jruby.util.Numeric.f_negate;
import static org.jruby.util.Numeric.f_negative_p;
import static org.jruby.util.Numeric.f_one_p;
import static org.jruby.util.Numeric.f_rshift;
import static org.jruby.util.Numeric.f_sub;
import static org.jruby.util.Numeric.f_to_f;
import static org.jruby.util.Numeric.f_to_i;
import static org.jruby.util.Numeric.f_to_r;
import static org.jruby.util.Numeric.f_to_s;
import static org.jruby.util.Numeric.f_truncate;
import static org.jruby.util.Numeric.f_xor;
import static org.jruby.util.Numeric.f_zero_p;
import static org.jruby.util.Numeric.i_gcd;
import static org.jruby.util.Numeric.i_ilog2;
import static org.jruby.util.Numeric.ldexp;

import org.joni.encoding.specific.ASCIIEncoding;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.common.IRubyWarnings;
import org.jruby.javasupport.util.RuntimeHelpers;
import org.jruby.runtime.Arity;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.Frame;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;
import org.jruby.util.Numeric;

/**
 *  1.9 rational.c as of revision: 18876
 */

@JRubyClass(name = "Rational", parent = "Numeric", include = "Precision")
public class RubyRational extends RubyNumeric {
    
    public static RubyClass createRationalClass(Ruby runtime) {
        RubyClass rationalc = runtime.defineClass("Rational", runtime.getNumeric(), RATIONAL_ALLOCATOR); // because one can Complex.send(:allocate)
        runtime.setRational(rationalc);

        rationalc.index = ClassIndex.RATIONAL;
        rationalc.kindOf = new RubyModule.KindOf() {
            @Override
            public boolean isKindOf(IRubyObject obj, RubyModule type) {
                return obj instanceof RubyRational;
            }
        };

        ThreadContext context = runtime.getCurrentContext();
        rationalc.callMethod(context, "private_class_method", runtime.newSymbol("allocate"));

        rationalc.defineAnnotatedMethods(RubyRational.class);

        return rationalc;
    }

    private static ObjectAllocator RATIONAL_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return new RubyRational(runtime, klass, RubyFixnum.zero(runtime), RubyFixnum.one(runtime));
        }
    }; 
    
    /** internal
     * 
     */
    private RubyRational(Ruby runtime, IRubyObject clazz, IRubyObject num, IRubyObject den) {
        super(runtime, (RubyClass)clazz);
        this.num = num;
        this.den = den;
    }

    /** rb_rational_raw
     * 
     */
    static RubyRational newRationalRaw(Ruby runtime, IRubyObject x, RubyObject y) {
        return new RubyRational(runtime, runtime.getRational(), x, y);
    }

    /** rb_rational_raw1
     * 
     */
    static RubyRational newRationalRaw(Ruby runtime, IRubyObject x) {
        return new RubyRational(runtime, runtime.getRational(), x, RubyFixnum.one(runtime));
    }

    /** rb_rational_new1
     * 
     */
    static IRubyObject newRationalCanonicalize(ThreadContext context, IRubyObject x) {
        return newRationalCanonicalize(context, x, RubyFixnum.one(context.getRuntime()));
    }

    /** rb_rational_new
     * 
     */
    private static IRubyObject newRationalCanonicalize(ThreadContext context, IRubyObject x, IRubyObject y) {
        return canonicalizeInternal(context, context.getRuntime().getRational(), x, y);
    }
    
    /** f_rational_new2
     * 
     */
    private static IRubyObject newRational(ThreadContext context, IRubyObject clazz, IRubyObject x, IRubyObject y) {
        assert x instanceof RubyRational && y instanceof RubyRational;
        return canonicalizeInternal(context, clazz, x, y);
    }

    /** f_rational_new1
     * 
     */
    private static IRubyObject newRational(ThreadContext context, IRubyObject clazz, IRubyObject x) {
        assert x instanceof RubyRational;
        return canonicalizeInternal(context, clazz, x, RubyFixnum.one(context.getRuntime()));
    }

    /** f_rational_new_no_reduce2
     * 
     */
    private static IRubyObject newRationalNoReduce(ThreadContext context, IRubyObject clazz, IRubyObject x, IRubyObject y) {
        assert x instanceof RubyRational && y instanceof RubyRational;
        return canonicalizeInternalNoReduce(context, clazz, x, y);
    }

    /** f_rational_new_no_reduce1
     * 
     */
    private static IRubyObject newRationalNoReduce(ThreadContext context, IRubyObject clazz, IRubyObject x) {
        assert x instanceof RubyRational;
        return canonicalizeInternalNoReduce(context, clazz, x, RubyFixnum.one(context.getRuntime()));
    }

    /** f_rational_new_bang2
     * 
     */
    private static RubyRational newRationalBang(ThreadContext context, IRubyObject clazz, IRubyObject x, IRubyObject y) { 
        assert !f_negative_p(context, y) && !(f_zero_p(context, y));
        return new RubyRational(context.getRuntime(), clazz, x, y);
    }

    /** f_rational_new_bang1
     * 
     */
    private static RubyRational newRationalBang(ThreadContext context, IRubyObject clazz, IRubyObject x) { 
        return newRationalBang(context, clazz, x, RubyFixnum.one(context.getRuntime()));
    }
    
    private IRubyObject num;
    private IRubyObject den;

    /** nurat_s_new_bang
     * 
     */
    @Deprecated
    public static IRubyObject newInstanceBang(ThreadContext context, IRubyObject recv, IRubyObject[]args) {
        switch (args.length) {
        case 1: return newInstanceBang(context, recv, args[0]);
        case 2: return newInstanceBang(context, recv, args[0], args[1]);
        }
        Arity.raiseArgumentError(context.getRuntime(), args.length, 1, 1);
        return null;
    }

    @JRubyMethod(name = "new!", meta = true, visibility = Visibility.PRIVATE)
    public static IRubyObject newInstanceBang(ThreadContext context, IRubyObject recv, IRubyObject num) {
        return newInstanceBang(context, recv, num, RubyFixnum.one(context.getRuntime()));
    }

    @JRubyMethod(name = "new!", meta = true, visibility = Visibility.PRIVATE)
    public static IRubyObject newInstanceBang(ThreadContext context, IRubyObject recv, IRubyObject num, IRubyObject den) {
        if (!(num instanceof RubyInteger)) num = f_to_i(context, num);
        if (!(den instanceof RubyInteger)) den = f_to_i(context, den);
        
        Ruby runtime = context.getRuntime();
        IRubyObject res = f_cmp(context, den, RubyFixnum.zero(runtime));
        if (res == RubyFixnum.minus_one(runtime)) {
            num = f_negate(context, num);
            den = f_negate(context, den);
        } else if (res == RubyFixnum.zero(runtime)) {
            throw runtime.newZeroDivisionError();
        }

        return new RubyRational(runtime, recv, num, den);
    }
    
    /** nurat_int_check
     * 
     */
    private static void intCheck(IRubyObject num) {
        if (!(num instanceof RubyFixnum ) && !(num instanceof RubyBignum)) throw num.getRuntime().newArgumentError("not an integer");
    }
    
    /** nurat_s_canonicalize_internal
     * 
     */
    private static IRubyObject canonicalizeInternal(ThreadContext context, IRubyObject clazz, IRubyObject num, IRubyObject den) {
        Ruby runtime = context.getRuntime();
        IRubyObject res = f_cmp(context, den, RubyFixnum.zero(runtime));
        if (res == RubyFixnum.minus_one(runtime)) {
            num = f_negate(context, num);
            den = f_negate(context, den);
        } else if (res == RubyFixnum.zero(runtime)) {
            throw runtime.newZeroDivisionError();            
        }

        IRubyObject gcd = f_gcd(context, num, den);
        num = f_idiv(context, num, gcd);
        den = f_idiv(context, den, gcd);
       
        if (f_one_p(context, den) && ((RubyModule)clazz).fastHasConstant("Unify")) return num;
        return new RubyRational(context.getRuntime(), clazz, num, den);
    }
    
    /** nurat_s_canonicalize_internal_no_reduce
     * 
     */
    private static IRubyObject canonicalizeInternalNoReduce(ThreadContext context, IRubyObject clazz, IRubyObject num, IRubyObject den) {
        Ruby runtime = context.getRuntime();
        IRubyObject res = f_cmp(context, den, RubyFixnum.zero(runtime));
        if (res == RubyFixnum.minus_one(runtime)) {
            num = f_negate(context, num);
            den = f_negate(context, den);
        } else if (res == RubyFixnum.zero(runtime)) {
            throw runtime.newZeroDivisionError();            
        }        

        if (f_equal_p(context, den, RubyFixnum.one(runtime)) && ((RubyModule)clazz).fastHasConstant("Unify")) return num;
        return new RubyRational(context.getRuntime(), clazz, num, den);
    }
    
    /** nurat_s_new
     * 
     */
    @Deprecated
    public static IRubyObject newInstance(ThreadContext context, IRubyObject clazz, IRubyObject[]args) {
        switch (args.length) {
        case 1: return newInstance(context, clazz, args[0]);
        case 2: return newInstance(context, clazz, args[0], args[1]);
        }
        Arity.raiseArgumentError(context.getRuntime(), args.length, 1, 1);
        return null;
    }

    @JRubyMethod(name = "new", meta = true)
    public static IRubyObject newInstance(ThreadContext context, IRubyObject clazz, IRubyObject num) {
        intCheck(num);
        return canonicalizeInternal(context, clazz, num, RubyFixnum.one(context.getRuntime()));
    }

    @JRubyMethod(name = "new", meta = true)
    public static IRubyObject newInstance(ThreadContext context, IRubyObject clazz, IRubyObject num, IRubyObject den) {
        intCheck(num);
        intCheck(den);
        return canonicalizeInternal(context, clazz, num, den);
    }
    
    /** rb_Rational1
     * 
     */
    public static IRubyObject newRationalConvert(ThreadContext context, IRubyObject x) {
        return newRationalConvert(context, x, RubyFixnum.one(context.getRuntime()));
    }

    /** rb_Rational/rb_Rational2
     * 
     */
    public static IRubyObject newRationalConvert(ThreadContext context, IRubyObject x, IRubyObject y) {
        return convert(context, context.getRuntime().getRational(), x, y);
    }
    
    @Deprecated
    public static IRubyObject convert(ThreadContext context, IRubyObject clazz, IRubyObject[]args) {
        switch (args.length) {
        case 0: return convert(context, clazz);
        case 1: return convert(context, clazz, args[0]);        
        case 2: return convert(context, clazz, args[0], args[1]);
        }
        Arity.raiseArgumentError(context.getRuntime(), args.length, 0, 2);
        return null;
    }

    /** nurat_s_convert
     * 
     */
    @JRubyMethod(name = "convert", meta = true, visibility = Visibility.PRIVATE)
    public static IRubyObject convert(ThreadContext context, IRubyObject recv) {
        IRubyObject nil = context.getRuntime().getNil();
        return convertCommon(context, recv, nil, nil);
    }
    
    /** nurat_s_convert
     * 
     */
    @JRubyMethod(name = "convert", meta = true, visibility = Visibility.PRIVATE)
    public static IRubyObject convert(ThreadContext context, IRubyObject recv, IRubyObject a1) {
        return convertCommon(context, recv, a1, context.getRuntime().getNil());
    }

    /** nurat_s_convert
     * 
     */
    @JRubyMethod(name = "convert", meta = true, visibility = Visibility.PRIVATE)
    public static IRubyObject convert(ThreadContext context, IRubyObject recv, IRubyObject a1, IRubyObject a2) {
        return convertCommon(context, recv, a1, a2);
    }
    
    private static IRubyObject convertCommon(ThreadContext context, IRubyObject recv, IRubyObject a1, IRubyObject a2) {
        if (a1 instanceof RubyComplex) {
            RubyComplex a1Complex = (RubyComplex)a1;
            if (a1Complex.getImage() instanceof RubyFloat || !f_zero_p(context, a1Complex.getImage())) {            
                IRubyObject s = f_to_s(context, a1);
                throw context.getRuntime().newArgumentError("can't accept " + s.convertToString());
            }
            a1 = a1Complex.getReal();
        }
        if (a2 instanceof RubyComplex) {
            RubyComplex a2Complex = (RubyComplex)a2;
            if (a2Complex.getImage() instanceof RubyFloat || !f_zero_p(context, a2Complex.getImage())) {            
                IRubyObject s = f_to_s(context, a2);
                throw context.getRuntime().newArgumentError("can't accept " + s.convertToString());
            }
            a2 = a2Complex.getReal();
        }
        
        Frame frame = context.getCurrentFrame();
        IRubyObject backref = frame.getBackRef();
        if (backref != null && backref instanceof RubyMatchData) ((RubyMatchData)backref).use();
        
        if (a1 instanceof RubyFloat) {
            a1 = f_to_r(context, a1);
        } else if (a1 instanceof RubyString) {
            a1 = str_to_r_strict(context, a1);
        }
        
        if (a2 instanceof RubyFloat) {
            a2 = f_to_r(context, a2);
        } else if (a2 instanceof RubyString) {
            a2 = str_to_r_strict(context, a2);
        }
        
        frame.setBackRef(backref);

        if (a1 instanceof RubyRational) {
            if (a2.isNil() || f_zero_p(context, a2)) return a1;
            return f_div(context, a1, a2);
        }
        
        if (a2 instanceof RubyRational) {
            return f_div(context, a1, a2);
        }

        return a2.isNil() ? newInstance(context, recv, a1) : newInstance(context, recv, a1, a2);
    }
    
    /** nurat_s_induced_from
     * 
     */
    @JRubyMethod(name = "induced_from", meta = true)
    public static IRubyObject induced_from(ThreadContext context, IRubyObject recv, IRubyObject arg) {
        return f_to_r(context, arg);
    }
    
    /** nurat_numerator
     * 
     */
    @JRubyMethod(name = "numerator")
    public IRubyObject numerator(ThreadContext context) {
        return num;
    }

    /** nurat_denominator
     * 
     */
    @JRubyMethod(name = "denominator")
    public IRubyObject denominator(ThreadContext context) {
        return den;
    }

    /** f_imul
     * 
     */
    private static IRubyObject f_imul(ThreadContext context, long a, long b) {
        Ruby runtime = context.getRuntime();
        if (a == 0 || b == 0) {
            return RubyFixnum.zero(runtime);
        } else if (a == 1) {
            return RubyFixnum.newFixnum(runtime, b);
        } else if (b == 1) {
            return RubyFixnum.newFixnum(runtime, a);
        }

        long c = a * b;
        if(c / a != b) {
            return RubyBignum.newBignum(runtime, a).op_mul(context, RubyBignum.newBignum(runtime, b));
        }
        return RubyFixnum.newFixnum(runtime, c);
    }
    
    /** f_addsub
     * 
     */
    private IRubyObject f_addsub(ThreadContext context, IRubyObject anum, IRubyObject aden, IRubyObject bnum, IRubyObject bden, boolean plus) {
        Ruby runtime = context.getRuntime();
        IRubyObject num, den, g, a, b;
        if (anum instanceof RubyFixnum && aden instanceof RubyFixnum &&
            bnum instanceof RubyFixnum && bden instanceof RubyFixnum) {
            long an = ((RubyFixnum)anum).getLongValue();
            long ad = ((RubyFixnum)aden).getLongValue();
            long bn = ((RubyFixnum)bnum).getLongValue();
            long bd = ((RubyFixnum)bden).getLongValue();
            long ig = i_gcd(ad, bd);

            g = RubyFixnum.newFixnum(runtime, ig);
            a = f_imul(context, an, bd / ig);
            b = f_imul(context, bn, ad / ig);
        } else {
            g = f_gcd(context, aden, bden);
            a = f_mul(context, anum, f_idiv(context, bden, g));
            b = f_mul(context, bnum, f_idiv(context, aden, g));
        }

        IRubyObject c = plus ? f_add(context, a, b) : f_sub(context, a, b);

        b = f_idiv(context, aden, g);
        g = f_gcd(context, c, g);
        num = f_idiv(context, c, g);
        a = f_idiv(context, bden, g);
        den = f_mul(context, a, b);
        
        return RubyRational.newRationalNoReduce(context, getMetaClass(), num, den);
    }
    
    /** nurat_add
     * 
     */
    @JRubyMethod(name = "+")
    public IRubyObject op_add(ThreadContext context, IRubyObject other) {
        switch (other.getMetaClass().index) {
        case ClassIndex.FIXNUM:
        case ClassIndex.BIGNUM:
            return f_addsub(context, num, den, other, RubyFixnum.one(context.getRuntime()), true);
        case ClassIndex.FLOAT:
            return f_add(context, f_to_f(context, this), other);
        case ClassIndex.RATIONAL:
            RubyRational otherRational = (RubyRational)other;
            return f_addsub(context, num, den, otherRational.num, otherRational.den, true);
        }
        return coerceBin(context, "+", other);
    }
    
    /** nurat_sub
     * 
     */
    @JRubyMethod(name = "-")
    public IRubyObject op_sub(ThreadContext context, IRubyObject other) {
        switch (other.getMetaClass().index) {
        case ClassIndex.FIXNUM:
        case ClassIndex.BIGNUM:
            return f_addsub(context, num, den, other, RubyFixnum.one(context.getRuntime()), false);
        case ClassIndex.FLOAT:
            return f_sub(context, f_to_f(context, this), other);
        case ClassIndex.RATIONAL:
            RubyRational otherRational = (RubyRational)other;
            return f_addsub(context, num, den, otherRational.num, otherRational.den, false);
        }
        return coerceBin(context, "-", other);
    }
    
    /** f_muldiv
     * 
     */
    private IRubyObject f_muldiv(ThreadContext context, IRubyObject anum, IRubyObject aden, IRubyObject bnum, IRubyObject bden, boolean mult) {
        if (!mult) {
            if (f_negative_p(context, bnum)) {
                anum = f_negate(context, anum);
                bnum = f_negate(context, bnum);
            }
            IRubyObject tmp =  bnum;
            bnum = bden;
            bden = tmp;
        }
        
        final IRubyObject num, den;
        if (anum instanceof RubyFixnum && aden instanceof RubyFixnum &&
            bnum instanceof RubyFixnum && bden instanceof RubyFixnum) {
            long an = ((RubyFixnum)anum).getLongValue();
            long ad = ((RubyFixnum)aden).getLongValue();
            long bn = ((RubyFixnum)bnum).getLongValue();
            long bd = ((RubyFixnum)bden).getLongValue();
            long g1 = i_gcd(an, bd);
            long g2 = i_gcd(ad, bn);
            
            num = f_imul(context, an / g1, bn / g2);
            den = f_imul(context, ad / g2, bd / g1);
        } else {
            IRubyObject g1 = f_gcd(context, anum, bden); 
            IRubyObject g2 = f_gcd(context, aden, bnum);
            
            num = f_mul(context, f_idiv(context, anum, g1), f_idiv(context, bnum, g2));
            den = f_mul(context, f_idiv(context, aden, g2), f_idiv(context, bden, g1));
        }
        return RubyRational.newRationalNoReduce(context, getMetaClass(), num, den);
                
    }

    /** nurat_mul
     * 
     */
    @JRubyMethod(name = "*")
    public IRubyObject op_mul(ThreadContext context, IRubyObject other) {
        switch (other.getMetaClass().index) {
        case ClassIndex.FIXNUM:
        case ClassIndex.BIGNUM:
            return f_muldiv(context, num, den, other, RubyFixnum.one(context.getRuntime()), true);
        case ClassIndex.FLOAT:
            return f_mul(context, f_to_f(context, this), other);
        case ClassIndex.RATIONAL:
            RubyRational otherRational = (RubyRational)other;
            return f_muldiv(context, num, den, otherRational.num, otherRational.den, true);
        }
        return coerceBin(context, "*", other);
    }
    
    /** nurat_div
     * 
     */
    @JRubyMethod(name = "/")
    public IRubyObject op_div(ThreadContext context, IRubyObject other) {
        switch (other.getMetaClass().index) {
        case ClassIndex.FIXNUM:
        case ClassIndex.BIGNUM:
            if (f_zero_p(context, other)) throw context.getRuntime().newZeroDivisionError();
            return f_muldiv(context, num, den, other, RubyFixnum.one(context.getRuntime()), false);
        case ClassIndex.FLOAT:
            return f_to_f(context, this).callMethod(context, "/", other);
        case ClassIndex.RATIONAL:
            if (f_zero_p(context, other)) throw context.getRuntime().newZeroDivisionError();
            RubyRational otherRational = (RubyRational)other;
            return f_muldiv(context, num, den, otherRational.num, otherRational.den, false);
        }
        return coerceBin(context, "/", other);
    }

    /** nurat_fdiv
     * 
     */
    @JRubyMethod(name = "fdiv")
    public IRubyObject op_fdiv(ThreadContext context, IRubyObject other) {
        return f_div(context, f_to_f(context, this), other);
    }

    /** nurat_expt
     * 
     */
    @JRubyMethod(name = "**")
    public IRubyObject op_expt(ThreadContext context, IRubyObject other) {
        Ruby runtime = context.getRuntime();

        if (f_zero_p(context, other)) {
            return RubyRational.newRationalBang(context, getMetaClass(), RubyFixnum.one(runtime));
        }

        if (other instanceof RubyRational) {
            RubyRational otherRational = (RubyRational)other;
            if (f_one_p(context, otherRational.den)) other = otherRational.num;
        }

        switch (other.getMetaClass().index) {
        case ClassIndex.FIXNUM:
        case ClassIndex.BIGNUM:
            final IRubyObject tnum, tden;
            IRubyObject res = f_cmp(context, other, RubyFixnum.zero(runtime));
            if (res == RubyFixnum.one(runtime)) {
                tnum = f_expt(context, num, other);
                tden = f_expt(context, den, other);
            } else if (res == RubyFixnum.minus_one(runtime)){
                tnum = f_expt(context, den, f_negate(context, other));
                tden = f_expt(context, num, f_negate(context, other));
            } else {
                tnum = tden = RubyFixnum.one(runtime);
            }
            return RubyRational.newRational(context, getMetaClass(), tnum, tden);
        case ClassIndex.FLOAT:
        case ClassIndex.RATIONAL:
            return f_expt(context, f_to_f(context, this), other);
        }
        return coerceBin(context, "**", other);
    }

    
    /** nurat_cmp
     * 
     */
    @JRubyMethod(name = "<=>")
    public IRubyObject op_cmp(ThreadContext context, IRubyObject other) {
        switch (other.getMetaClass().index) {
        case ClassIndex.FIXNUM:
        case ClassIndex.BIGNUM:
            if (den instanceof RubyFixnum && ((RubyFixnum)den).getLongValue() == 1) return f_cmp(context, num, other);
            return f_cmp(context, this, RubyRational.newRationalBang(context, getMetaClass(), other));
        
        case ClassIndex.FLOAT:
            return f_cmp(context, f_to_f(context, this), other);
        
        case ClassIndex.RATIONAL:
            RubyRational otherRational = (RubyRational)other;
            final IRubyObject num1, num2;
            if (num instanceof RubyFixnum && den instanceof RubyFixnum &&
                otherRational.num instanceof RubyFixnum && otherRational.den instanceof RubyFixnum) {
                num1 = f_imul(context, ((RubyFixnum)num).getLongValue(), ((RubyFixnum)otherRational.den).getLongValue());
                num2 = f_imul(context, ((RubyFixnum)otherRational.num).getLongValue(), ((RubyFixnum)den).getLongValue());
            } else {
                num1 = f_mul(context, num, otherRational.den);
                num2 = f_mul(context, otherRational.num, den);
            }
            return f_cmp(context, f_sub(context, num1, num2), RubyFixnum.zero(context.getRuntime()));
        }
        return coerceBin(context, "<=>", other);             
    }

    /** nurat_equal_p
     * 
     */
    @JRubyMethod(name = "==")
    public IRubyObject op_equal(ThreadContext context, IRubyObject other) {
        Ruby runtime = context.getRuntime();
        switch (other.getMetaClass().index) {
        case ClassIndex.FIXNUM:
        case ClassIndex.BIGNUM:
            if (f_zero_p(context, num) && f_zero_p(context, den)) return runtime.getTrue();
            if (!(den instanceof RubyFixnum) || ((RubyFixnum)den).getLongValue() != 1) return runtime.getFalse();
            if (f_equal_p(context, num, other)) return runtime.getTrue();
            return runtime.getFalse();
            
        case ClassIndex.FLOAT:
            return f_equal_p(context, f_to_f(context, this), other) ? runtime.getTrue() : runtime.getFalse();
            
        case ClassIndex.RATIONAL:
            RubyRational otherRational = (RubyRational)other;
            if (f_zero_p(context, num) && f_zero_p(context, otherRational.num)) return runtime.getTrue();
            if (f_equal_p(context, num, otherRational.num) && f_equal_p(context, den, otherRational.den)) return runtime.getTrue();
            return runtime.getFalse();
        }
        return f_equal_p(context, other, this) ? runtime.getTrue() : runtime.getFalse();
    }

    /** nurat_coerce
     * 
     */
    @JRubyMethod(name = "coerce")
    public IRubyObject op_coerce(ThreadContext context, IRubyObject other) {
        Ruby runtime = context.getRuntime();
        switch (other.getMetaClass().index) {
        case ClassIndex.FIXNUM:
        case ClassIndex.BIGNUM:
            return runtime.newArray(RubyRational.newRationalBang(context, getMetaClass(), other), this);
        case ClassIndex.FLOAT:
            return runtime.newArray(other, f_to_f(context, this));
        }
        throw runtime.newTypeError(other.getMetaClass() + " can't be coerced into " + getMetaClass());
    }

    /** nurat_idiv
     * 
     */
    @JRubyMethod(name = "div")
    public IRubyObject op_idiv(ThreadContext context, IRubyObject other) {
        return f_floor(context, f_div(context, this, other));
    }

    /** nurat_mod
     * 
     */
    @JRubyMethod(name = {"modulo", "%"})
    public IRubyObject op_mod(ThreadContext context, IRubyObject other) {
        IRubyObject val = f_floor(context, f_div(context, this, other));
        return f_sub(context, this, f_mul(context, other, val));
    }

    /** nurat_divmod
     * 
     */
    @JRubyMethod(name = "divmod")
    public IRubyObject op_divmod(ThreadContext context, IRubyObject other) {
        IRubyObject val = f_floor(context, f_div(context, this, other)); 
        return context.getRuntime().newArray(val, f_sub(context, this, f_mul(context, other, val)));
    }

    /** nurat_rem
     * 
     */
    @JRubyMethod(name = "remainder")
    public IRubyObject op_rem(ThreadContext context, IRubyObject other) {
        IRubyObject val = f_truncate(context, f_div(context, this, other));
        return f_sub(context, this, f_mul(context, other, val));
    }

    /** nurat_abs
     * 
     */
    @JRubyMethod(name = "abs")
    public IRubyObject op_abs(ThreadContext context) {
        if (!f_negative_p(context, this)) return this;
        return f_negate(context, this);
    }

    /** nurat_floor
     * 
     */
    @JRubyMethod(name = "floor")
    public IRubyObject op_floor(ThreadContext context) {
        return f_idiv(context, num, den);
    }

    /** nurat_ceil
     * 
     */
    @JRubyMethod(name = "ceil")
    public IRubyObject op_ceil(ThreadContext context) {
        return f_negate(context, f_idiv(context, f_negate(context, num), den));
    }

    /** nurat_truncate
     * 
     */
    @JRubyMethod(name = {"truncate", "to_i"})
    public IRubyObject op_truncate(ThreadContext context) {
        if (f_negative_p(context, num)) {
            return f_negate(context, f_idiv(context, f_negate(context, num), den));
        }
        return f_idiv(context, num, den);
    }

    /** nurat_round
     * 
     */
    @JRubyMethod(name = "round")
    public IRubyObject op_round(ThreadContext context) {
        IRubyObject two = RubyFixnum.two(context.getRuntime());
        if (f_negative_p(context, num)) {
            IRubyObject tnum = f_negate(context, num);
            tnum = f_add(context, f_mul(context, tnum, two), den);
            IRubyObject tden = f_mul(context, den, two);
            return f_negate(context, f_idiv(context, tnum, tden)); 
        } else {
            IRubyObject tnum = f_add(context, f_mul(context, num, two), den);
            IRubyObject tden = f_mul(context, den, two);
            return f_idiv(context, tnum, tden);
        }
    }

    /** nurat_to_f
     * 
     */
    private static long ML = (long)(Math.log(Double.MAX_VALUE) / Math.log(2.0) - 1);
    @JRubyMethod(name = "to_f")
    public IRubyObject to_f(ThreadContext context) {
        Ruby runtime = context.getRuntime();
        if (f_zero_p(context, num)) return runtime.newFloat(0);

        IRubyObject num = this.num;
        IRubyObject den = this.den;

        boolean minus = false;
        if (f_negative_p(context, num)) {
            num = f_negate(context, num);
            minus = true;
        }

        long nl = i_ilog2(context, num);
        long dl = i_ilog2(context, den);

        long ne = 0;
        if (nl > ML) {
            ne = nl - ML;
            num = f_rshift(context, num, RubyFixnum.newFixnum(runtime, ne));
        }

        long de = 0;
        if (dl > ML) {
            de = dl - ML;
            den = f_rshift(context, den, RubyFixnum.newFixnum(runtime, de));
        }

        long e = ne - de;
        
        if (e > 1023 || e < -1022) {
            runtime.getWarnings().warn(IRubyWarnings.ID.FLOAT_OUT_OF_RANGE, "out of Float range", getMetaClass());
            return runtime.newFloat(e > 0 ? Double.MAX_VALUE : 0);
        }

        double f = RubyNumeric.num2dbl(num) / RubyNumeric.num2dbl(den); 

        if (minus) {
            f = -f;
            f = ldexp(f, e);
        }

        if (Double.isInfinite(f) || Double.isNaN(f)) {
            runtime.getWarnings().warn(IRubyWarnings.ID.FLOAT_OUT_OF_RANGE, "out of Float range", getMetaClass());
        }

        return runtime.newFloat(f);
    }

    /** nurat_to_r
     * 
     */
    @JRubyMethod(name = "to_r")
    public IRubyObject to_r(ThreadContext context) {
        return this;
    }
    
    /** nurat_to_r
     * 
     */
    @JRubyMethod(name = "hash")
    public IRubyObject hash(ThreadContext context) {
        return f_xor(context, num, den);
    }

    /** nurat_to_s
     * 
     */
    @JRubyMethod(name = "to_s")
    public IRubyObject to_s(ThreadContext context) {
        return RuntimeHelpers.invoke(context, context.getRuntime().getKernel(), "format", context.getRuntime().newString("%d/%d"), num, den);
    }

    /** nurat_inspect
     * 
     */
    @JRubyMethod(name = "inspect")
    public IRubyObject inspect(ThreadContext context) {
        return RuntimeHelpers.invoke(context, context.getRuntime().getKernel(), "format", context.getRuntime().newString("(%d/%d)"), num, den);
    }

    /** nurat_marshal_dump
     * 
     */
    @JRubyMethod(name = "marshal_dump")
    public IRubyObject marshal_dump(ThreadContext context) {
        return context.getRuntime().newArray(num, den);
    }

    /** nurat_marshal_load
     * 
     */
    @JRubyMethod(name = "marshal_load")
    public IRubyObject marshal_load(ThreadContext context, IRubyObject arg) {
        RubyArray a = arg.convertToArray();
        num = a.size() > 0 ? a.eltInternal(0) : context.getRuntime().getNil();
        den = a.size() > 1 ? a.eltInternal(1) : context.getRuntime().getNil();

        if (f_zero_p(context, den)) throw context.getRuntime().newZeroDivisionError();
        return this;
    }
    
    /** rb_gcd
     * 
     */
    @JRubyMethod(name = "gcd")
    public IRubyObject gcd(ThreadContext context, IRubyObject other) {
        intCheck(other);
        return f_gcd(context, this, other);
    }
        
    /** rb_lcm
     * 
     */
    @JRubyMethod(name = "lcm")
    public IRubyObject lcm(ThreadContext context, IRubyObject other) {
        intCheck(other);
        return Numeric.f_lcm(context, this, other);
    }
    
    /** rb_gcdlcm
     * 
     */
    @JRubyMethod(name = "gcdlcm")
    public IRubyObject gcdlcm(ThreadContext context, IRubyObject other) {
        intCheck(other);
        return context.getRuntime().newArray(f_gcd(context, this, other), Numeric.f_lcm(context, this, other));
    }
    
    
    static RubyArray str_to_r_internal(ThreadContext context, IRubyObject recv) {
        RubyString s = recv.callMethod(context, "strip").convertToString();
        ByteList bytes = s.getByteList();

        Ruby runtime = context.getRuntime();
        if (bytes.realSize == 0) return runtime.newArray(runtime.getNil(), recv);

        IRubyObject m = RubyRegexp.newRegexp(runtime, Numeric.RationalPatterns.rat_pat).callMethod(context, "match", s);
        
        if (!m.isNil()) {
            IRubyObject si = m.callMethod(context, "[]", RubyFixnum.one(runtime));
            IRubyObject nu = m.callMethod(context, "[]", RubyFixnum.two(runtime));
            IRubyObject de = m.callMethod(context, "[]", RubyFixnum.three(runtime));
            IRubyObject re = m.callMethod(context, "post_match");
            
            RubyArray a = nu.callMethod(context, "split", RubyRegexp.newRegexp(runtime, Numeric.RationalPatterns.an_e_pat)).convertToArray();
            IRubyObject ifp = a.eltInternal(0);
            IRubyObject exp = a.size() != 2 ? runtime.getNil() : a.eltInternal(1);
            
            a = ifp.callMethod(context, "split", RubyRegexp.newRegexp(runtime, Numeric.RationalPatterns.a_dot_pat)).convertToArray();
            IRubyObject ip = a.eltInternal(0);
            IRubyObject fp = a.size() != 2 ? runtime.getNil() : a.eltInternal(1);
            
            IRubyObject v = RubyRational.newRationalCanonicalize(context, f_to_i(context, ip));
            
            if (!fp.isNil()) {
                bytes = fp.convertToString().getByteList();
                int count = 0;
                byte[]buf = bytes.bytes;
                int i = bytes.begin;
                int end = i + bytes.realSize;
                while (i < end) if (ASCIIEncoding.INSTANCE.isDigit(buf[i++])) count++;
                IRubyObject l = f_expt(context, RubyFixnum.newFixnum(runtime, 10), RubyFixnum.newFixnum(runtime, count));
                v = f_mul(context, v, l);
                v = f_add(context, v, f_to_i(context, fp));
                v = f_div(context, v, l);
            }
            if (!exp.isNil()) {
                v = f_mul(context, v, f_expt(context, RubyFixnum.newFixnum(runtime, 10), f_to_i(context, exp)));
            }
            if (!si.isNil()) {
                bytes = si.convertToString().getByteList();
                if (bytes.length() > 0 && bytes.get(0) == '-') v = f_negate(context, v);
            }
            if (!de.isNil()) {
                v = f_div(context, v, f_to_i(context, de));
            }
            return runtime.newArray(v, re);
        }
        return runtime.newArray(runtime.getNil(), recv);
    }
    
    private static IRubyObject str_to_r_strict(ThreadContext context, IRubyObject recv) {
        RubyArray a = str_to_r_internal(context, recv);
        if (a.eltInternal(0).isNil() || a.eltInternal(1).convertToString().getByteList().length() > 0) {
            IRubyObject s = recv.callMethod(context, "inspect");
            throw context.getRuntime().newArgumentError("invalid value for Rational: " + s.convertToString());
        }
        return a.eltInternal(0);
    }    
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004-2005 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2005 David Corbin <dcorbin@users.sourceforge.net>
 * Copyright (C) 2006 Nick Sieger <nicksieger@gmail.com>
 * Copyright (C) 2006 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.lang.ref.SoftReference;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Iterator;
import java.util.Map;

import org.joni.Matcher;
import org.joni.NameEntry;
import org.joni.Option;
import org.joni.Regex;
import org.joni.Region;
import org.joni.Syntax;
import org.joni.WarnCallback;
import org.joni.encoding.Encoding;
import org.joni.exception.JOniException;

import static org.jruby.anno.FrameField.*;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.common.IRubyWarnings.ID;
import org.jruby.parser.ReOptions;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.Frame;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.marshal.MarshalStream;
import org.jruby.runtime.marshal.UnmarshalStream;
import org.jruby.util.ByteList;
import org.jruby.util.KCode;
import org.jruby.util.TypeConverter;

/**
 *
 */
@JRubyClass(name="Regexp")
public class RubyRegexp extends RubyObject implements ReOptions, WarnCallback {
    private KCode kcode;
    private Regex pattern;
    private ByteList str;

    private static final int REGEXP_LITERAL_F =     1 << 11;
    private static final int REGEXP_KCODE_DEFAULT = 1 << 12;

    public void setLiteral() {
        flags |= REGEXP_LITERAL_F;
    }
    
    public void clearLiteral() {
        flags &= ~REGEXP_LITERAL_F;
    }
    
    public boolean isLiteral() {
        return (flags & REGEXP_LITERAL_F) != 0;
    }

    public void setKCodeDefault() {
        flags |= REGEXP_KCODE_DEFAULT;
    }
    
    public void clearKCodeDefault() {
        flags &= ~REGEXP_KCODE_DEFAULT;
    }
    
    public boolean isKCodeDefault() {
        return (flags & REGEXP_KCODE_DEFAULT) != 0;
    }

    public KCode getKCode() {
        return kcode;
    }

    private static Map<ByteList, Regex> getPatternCache() {
        Map<ByteList, Regex> cache = patternCache.get();
        if (cache == null) {
            cache = new ConcurrentHashMap<ByteList, Regex>(5);
            patternCache = new SoftReference<Map<ByteList, Regex>>(cache);
        }
        return cache;
    }

    static volatile SoftReference<Map<ByteList, Regex>> patternCache = new SoftReference<Map<ByteList, Regex>>(null);    

    public static RubyClass createRegexpClass(Ruby runtime) {
        RubyClass regexpClass = runtime.defineClass("Regexp", runtime.getObject(), REGEXP_ALLOCATOR);
        runtime.setRegexp(regexpClass);
        regexpClass.index = ClassIndex.REGEXP;
        regexpClass.kindOf = new RubyModule.KindOf() {
            @Override
                public boolean isKindOf(IRubyObject obj, RubyModule type) {
                    return obj instanceof RubyRegexp;
                }
            };
        
        regexpClass.defineConstant("IGNORECASE", runtime.newFixnum(RE_OPTION_IGNORECASE));
        regexpClass.defineConstant("EXTENDED", runtime.newFixnum(RE_OPTION_EXTENDED));
        regexpClass.defineConstant("MULTILINE", runtime.newFixnum(RE_OPTION_MULTILINE));
        
        regexpClass.defineAnnotatedMethods(RubyRegexp.class);

        return regexpClass;
    }
    
    private static ObjectAllocator REGEXP_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            RubyRegexp instance = new RubyRegexp(runtime, klass);
            return instance;
        }
    };

    /** used by allocator
     * 
     */
    private RubyRegexp(Ruby runtime, RubyClass klass) {
        super(runtime, klass);
    }

    /** default constructor
     * 
     */
    private RubyRegexp(Ruby runtime) {
        super(runtime, runtime.getRegexp());
    }

    // used only by the compiler/interpreter (will set the literal flag)
    public static RubyRegexp newRegexp(Ruby runtime, String pattern, int options) {
        return newRegexp(runtime, ByteList.create(pattern), options);
    }

    // used only by the compiler/interpreter (will set the literal flag)
    public static RubyRegexp newRegexp(Ruby runtime, ByteList pattern, int options) {
        RubyRegexp regexp = newRegexp(runtime, pattern, options, false);
        regexp.setLiteral();
        return regexp;
    }
    
    public static RubyRegexp newRegexp(Ruby runtime, ByteList pattern, int options, boolean quote) {
        RubyRegexp regexp = new RubyRegexp(runtime);
        regexp.initialize(pattern, options, quote);
        return regexp;
    }

    // internal usage
    static RubyRegexp newRegexp(Ruby runtime, Regex regex) {
        RubyRegexp regexp = new RubyRegexp(runtime);
        regexp.pattern = regex;
        regexp.str = ByteList.EMPTY_BYTELIST;
        return regexp;
    }
    
    public void warn(String message) {
        getRuntime().getWarnings().warn(ID.MISCELLANEOUS, message);
    }

    @JRubyMethod(name = "kcode")
    public IRubyObject kcode(ThreadContext context) {
        return (!isKCodeDefault() && kcode != null) ? 
            context.getRuntime().newString(kcode.name()) : context.getRuntime().getNil();
    }

    @Override
    public int getNativeTypeIndex() {
        return ClassIndex.REGEXP;
    }
    
    public Regex getPattern() {
        return pattern;
    }

    private void check() {
        if (pattern == null || str == null) throw getRuntime().newTypeError("uninitialized Regexp");
    }

    @JRubyMethod(name = "hash")
    @Override
    public RubyFixnum hash() {
        check();
        int hashval = (int)pattern.getOptions();
        int len = this.str.realSize;
        int p = this.str.begin;
        while (len-->0) {
            hashval = hashval * 33 + str.bytes[p++];
        }
        hashval = hashval + (hashval>>5);
        return getRuntime().newFixnum(hashval);
    }

    @JRubyMethod(name = {"==", "eql?"}, required = 1)
    @Override
    public IRubyObject op_equal(ThreadContext context, IRubyObject other) {
        if(this == other) return context.getRuntime().getTrue();
        if(!(other instanceof RubyRegexp)) return context.getRuntime().getFalse();
        RubyRegexp otherRegex = (RubyRegexp)other;
        
        check();
        otherRegex.check();
        
        return context.getRuntime().newBoolean(str.equal(otherRegex.str) && 
                kcode == otherRegex.kcode && pattern.getOptions() == otherRegex.pattern.getOptions());
    }

    @JRubyMethod(name = "~", reads = {LASTLINE, BACKREF}, writes = BACKREF)
    public IRubyObject op_match2(ThreadContext context) {
        Ruby runtime = context.getRuntime();
        IRubyObject line = context.getCurrentFrame().getLastLine();
        if(!(line instanceof RubyString)) {
            context.getCurrentFrame().setBackRef(runtime.getNil());
            return runtime.getNil();
        }
        int start = search(context, (RubyString)line, 0, false);
        if(start < 0) {
            return runtime.getNil();
        } else {
            return runtime.newFixnum(start);
        }
    }

    /** rb_reg_eqq
     * 
     */
    @JRubyMethod(name = "===", required = 1, writes = BACKREF)
    public IRubyObject eqq(ThreadContext context, IRubyObject str) {
        Ruby runtime = context.getRuntime();        
        if(!(str instanceof RubyString)) str = str.checkStringType();

        if (str.isNil()) {
            context.getCurrentFrame().setBackRef(runtime.getNil());
            return runtime.getFalse();
        }
        int start = search(context, (RubyString)str, 0, false);
        return (start < 0) ? runtime.getFalse() : runtime.getTrue();
    }

    private static final int REGEX_QUOTED = 1;
    private void initialize(ByteList bytes, int options, boolean quote) {
        if (!isTaint() && getRuntime().getSafeLevel() >= 4) throw getRuntime().newSecurityError("Insecure: can't modify regexp");
        checkFrozen();
        if (isLiteral()) throw getRuntime().newSecurityError("can't modify literal regexp");

        setKCode(options);

        Map<ByteList, Regex> cache = getPatternCache();
        Regex pat = cache.get(bytes);

        if (pat != null &&
            pat.getEncoding() == kcode.getEncoding() &&
            pat.getOptions() == (options & 0xf) &&
            ((pat.getUserOptions() & REGEX_QUOTED) != 0) == quote) { // cache hit
            pattern = pat;
        } else {
            if (quote) bytes = quote(bytes, getRuntime().getKCode());
            makeRegexp(bytes, bytes.begin, bytes.realSize, options & 0xf, kcode.getEncoding());
            if (quote) pattern.setUserOptions(REGEX_QUOTED);
            cache.put(bytes, pattern);
        }

        str = bytes;
    }

    private void makeRegexp(ByteList bytes, int start, int len, int flags, Encoding enc) {
        try {
            pattern = new Regex(bytes.bytes, start, start + len, flags, enc, Syntax.DEFAULT, this);
        } catch(Exception e) {
            rb_reg_raise(bytes.bytes, start, len, e.getMessage(), flags);
        }
    }

    private final void rb_reg_raise(byte[] s, int start, int len, String err,int flags) {
        throw getRuntime().newRegexpError(err + ": " + rb_reg_desc(s,start, len,flags));
    }

    private final StringBuilder rb_reg_desc(byte[] s, int start, int len, int flags) {
        StringBuilder sb = new StringBuilder("/");
        rb_reg_expr_str(sb, s, start, len);
        sb.append("/");

        if((flags & ReOptions.RE_OPTION_MULTILINE) != 0) sb.append("m");
        if((flags & ReOptions.RE_OPTION_IGNORECASE) != 0) sb.append("i");
        if((flags & ReOptions.RE_OPTION_EXTENDED) != 0) sb.append("x");

        if (kcode != null && !isKCodeDefault()) {
            sb.append(kcode.name().charAt(0));
        }
        return sb;
    }

    private final void rb_reg_expr_str(StringBuilder sb, byte[] s, int start, int len) {
        int p,pend;
        boolean need_escape = false;
        p = start;
        pend = start+len;
        Encoding enc = kcode.getEncoding();
        while(p<pend) {
            if(s[p] == '/' || (!(' ' == s[p] || (!Character.isWhitespace(s[p]) && 
                                                 !Character.isISOControl(s[p]))) && 
                               enc.length(s[p])==1)) {
                need_escape = true;
                break;
            }
            p += enc.length(s[p]);
        }
        if(!need_escape) {
            sb.append(new ByteList(s,start,len,false).toString());
        } else {
            p = 0;
            while(p < pend) {
                if(s[p] == '\\') {
                    int n = enc.length(s[p+1]) + 1;
                    sb.append(new ByteList(s,p,n,false).toString());
                    p += n;
                    continue;
                } else if(s[p] == '/') {
                    sb.append("\\/");
                } else if(enc.length(s[p])!=1) {
                    sb.append(new ByteList(s,p,enc.length(s[p]),false).toString());
                    p += enc.length(s[p]);
                    continue;
                } else if((' ' == s[p] || (!Character.isWhitespace(s[p]) && 
                                           !Character.isISOControl(s[p])))) {
                    sb.append((char)(s[p]&0xFF));
                } else if(!Character.isWhitespace((char)(s[p]&0xFF))) {
                    sb.append('\\');
                    sb.append(Integer.toString((int)(s[p]&0377),8));
                } else {
                    sb.append((char)(s[p]&0xFF));
                }
                p++;
            }
        }
    }    

    /** rb_reg_init_copy
     */
    @JRubyMethod(name = "initialize_copy", required = 1)
    @Override
    public IRubyObject initialize_copy(IRubyObject re) {
        if(this == re) return this;

        checkFrozen();

        if (getMetaClass().getRealClass() != re.getMetaClass().getRealClass()) {
            throw getRuntime().newTypeError("wrong argument type");
        }

        RubyRegexp regexp = (RubyRegexp)re;
        regexp.check();

        initialize(regexp.str, regexp.getOptions(), false);

        return this;
    }

    /** rb_set_kcode
     */
    private int getKcode() {
        if(kcode == KCode.NONE) {
            return 16;
        } else if(kcode == KCode.EUC) {
            return 32;
        } else if(kcode == KCode.SJIS) {
            return 48;
        } else if(kcode == KCode.UTF8) {
            return 64;
        }
        return 0;
    }
    
    /**
     */
    private void setKCode(int options) {
        clearKCodeDefault();
        switch(options & ~0xf) {
        case 0:
        default:
            setKCodeDefault();
            kcode = getRuntime().getKCode();
            break;
        case 16:
            kcode = KCode.NONE;
            break;
        case 32:
            kcode = KCode.EUC;
            break;
        case 48:
            kcode = KCode.SJIS;
            break;
        case 64:
            kcode = KCode.UTF8;
            break;
        }        
    }

    /** rb_reg_options
     */
    private int getOptions() {
        check();
        int options = (int)(pattern.getOptions() & (RE_OPTION_IGNORECASE|RE_OPTION_MULTILINE|RE_OPTION_EXTENDED));
        if(!isKCodeDefault()) {
            options |= getKcode();
        }
        return options;
    }

    /** rb_reg_initialize_m
     */
    @JRubyMethod(name = "initialize", optional = 3, visibility = Visibility.PRIVATE)
    public IRubyObject initialize_m(IRubyObject[] args) {
        ByteList bytes;
        int regexFlags = 0;

        if(args[0] instanceof RubyRegexp) {
            if(args.length > 1) {
                getRuntime().getWarnings().warn(ID.REGEXP_IGNORED_FLAGS, "flags" + (args.length == 3 ? " and encoding" : "") + " ignored");
            }
            RubyRegexp regexp = (RubyRegexp)args[0];
            regexp.check();

            regexFlags = (int)regexp.pattern.getOptions() & 0xF;
            if (!regexp.isKCodeDefault() && regexp.kcode != null && regexp.kcode != KCode.NIL) {
                if (regexp.kcode == KCode.NONE) {
                    regexFlags |= 16;
                } else if (regexp.kcode == KCode.EUC) {
                    regexFlags |= 32;
                } else if (regexp.kcode == KCode.SJIS) {
                    regexFlags |= 48;
                } else if (regexp.kcode == KCode.UTF8) {
                    regexFlags |= 64;
                }                
            }
            bytes = regexp.str;
        } else {
            if (args.length >= 2) {
                if (args[1] instanceof RubyFixnum) {
                    regexFlags = RubyNumeric.fix2int(args[1]);
                } else if (args[1].isTrue()) {
                    regexFlags = RE_OPTION_IGNORECASE;
                }
            }
            if (args.length == 3 && !args[2].isNil()) {
                ByteList kcodeBytes = args[2].convertToString().getByteList();
                char first = kcodeBytes.length() > 0 ? kcodeBytes.charAt(0) : 0;
                regexFlags &= ~0x70;
                switch (first) {
                case 'n': case 'N':
                    regexFlags |= 16;
                    break;
                case 'e': case 'E':
                    regexFlags |= 32;
                    break;
                case 's': case 'S':
                    regexFlags |= 48;
                    break;
                case 'u': case 'U':
                    regexFlags |= 64;
                    break;
                default:
                    break;
                }
            }
            bytes = args[0].convertToString().getByteList();
        }
        initialize(bytes, regexFlags, false);

        return this;
    }

    @JRubyMethod(name = {"new", "compile"}, required = 1, optional = 2, meta = true)
    public static RubyRegexp newInstance(IRubyObject recv, IRubyObject[] args) {
        RubyClass klass = (RubyClass)recv;
        
        RubyRegexp re = (RubyRegexp) klass.allocate();
        re.callInit(args, Block.NULL_BLOCK);
        
        return re;
    }

    @JRubyMethod(name = "options")
    public IRubyObject options() {
        return getRuntime().newFixnum(getOptions());
    }
    
    /** rb_reg_search
     */
    public int search(ThreadContext context, RubyString str, int pos, boolean reverse) {
        Ruby runtime = context.getRuntime();
        Frame frame = context.getCurrentRubyFrame();

        ByteList value = str.getByteList();
        if (pos > value.realSize || pos < 0) {
            frame.setBackRef(runtime.getNil());
            return -1;
        }

        return performSearch(reverse, pos, value, frame, runtime, context, str);
    }

    private int performSearch(boolean reverse, int pos, ByteList value, Frame frame, Ruby runtime, ThreadContext context, RubyString str) {
        check();

        int realSize = value.realSize;
        int begin = value.begin;
        int range = reverse ? -pos : realSize - pos;

        Matcher matcher = pattern.matcher(value.bytes, begin, begin + realSize);

        int result = matcher.search(begin + pos, begin + pos + range, Option.NONE);

        if (result < 0) {
            frame.setBackRef(runtime.getNil());
            return result;
        }

        updateBackRef(context, str, frame, matcher);

        return result;
    }
    
    final RubyMatchData updateBackRef(ThreadContext context, RubyString str, Frame frame, Matcher matcher) {
        Ruby runtime = context.getRuntime();
        IRubyObject backref = frame.getBackRef();
        final RubyMatchData match;
        if (backref == null || backref.isNil() || ((RubyMatchData)backref).used()) {
            match = new RubyMatchData(runtime);
        } else {
            match = (RubyMatchData)backref;
            match.setTaint(runtime.getSafeLevel() >= 3);
        }
        
        match.regs = matcher.getRegion(); // lazy, null when no groups defined
        match.begin = matcher.getBegin();
        match.end = matcher.getEnd();
        
        match.str = (RubyString)str.strDup(runtime).freeze(context);
        match.pattern = pattern;

        frame.setBackRef(match);
        
        match.infectBy(this);
        match.infectBy(str);
        return match;
    }

    /** rb_reg_match
     * 
     */
    @JRubyMethod(name = "=~", required = 1, reads = BACKREF, writes = BACKREF)
    @Override
    public IRubyObject op_match(ThreadContext context, IRubyObject str) {
        int start;
        if(str.isNil()) {
            context.getCurrentFrame().setBackRef(context.getRuntime().getNil());
            return str;
        }
        
        start = search(context, str.convertToString(), 0, false);

        if (start < 0) return context.getRuntime().getNil();

        return RubyFixnum.newFixnum(context.getRuntime(), start);
    }

    /** rb_reg_match_m
     * 
     */
    @JRubyMethod(name = "match", required = 1, reads = BACKREF)
    public IRubyObject match_m(ThreadContext context, IRubyObject str) {
        if (op_match(context, str).isNil()) return context.getRuntime().getNil();

        IRubyObject result =  context.getCurrentFrame().getBackRef();
        if (result instanceof RubyMatchData) {
            ((RubyMatchData)result).use();
        }
        return result;
    }


    public RubyString regsub(RubyString str, RubyString src, Matcher matcher) {
        Region regs = matcher.getRegion();
        int mbeg = matcher.getBegin();
        int mend = matcher.getEnd();
        
        int p,s,e;
        p = s = 0;
        int no = -1;
        ByteList bs = str.getByteList();
        ByteList srcbs = src.getByteList();
        e = bs.length();
        RubyString val = null;
        Encoding enc = kcode.getEncoding();

        int beg, end;
        while(s < e) {
            int ss = s;
            char c = bs.charAt(s++);
            if(enc.length((byte)c) != 1) {
                s += enc.length((byte)c) - 1;
                continue;
            }
            if (c != '\\' || s == e) continue;
            if (val == null) val = RubyString.newString(getRuntime(), new ByteList(ss - p));

            val.cat(bs.bytes, bs.begin + p, ss - p);
            c = bs.charAt(s++);
            p = s;
            
            switch(c) {
            case '0': case '1': case '2': case '3': case '4':
            case '5': case '6': case '7': case '8': case '9':
                no = c - '0';
                break;
            case '&':
                no = 0;
                break;
            case '`':
                beg = regs == null ? mbeg : regs.beg[0];
                val.cat(srcbs.bytes, srcbs.begin, beg);
                continue;

            case '\'':
                end = regs == null ? mend : regs.end[0];
                val.cat(srcbs.bytes, srcbs.begin + end, src.getByteList().realSize - end);
                continue;

            case '+':
                if (regs == null) {
                    if (mbeg == -1) {
                        no = 0;
                        continue;
                    }
                } else {
                    no = regs.numRegs-1;
                    while(regs.beg[no] == -1 && no > 0) no--;
                    if (no == 0) continue;
                }
                break;
            case '\\':
                val.cat(bs.bytes, s - 1, 1);
                continue;
            default:
                val.cat(bs.bytes, s - 2, 2);
                continue;
            }

            if (regs != null) {
                if (no >= 0) {
                    if (no >= regs.numRegs || regs.beg[no] == -1) continue;
                    val.cat(srcbs.bytes, srcbs.begin + regs.beg[no], regs.end[no] - regs.beg[no]);
                }
            } else {
                if (no != 0 || mbeg == -1) continue;
                val.cat(srcbs.bytes, srcbs.begin + mbeg, mend - mbeg);
            }
        }

        if(p < e) {
            if(val == null) {
                val = RubyString.newString(getRuntime(), bs.makeShared(p, e-p));
            } else {
                val.cat(bs.bytes, bs.begin + p, e - p);
            }
        }
        if (val == null) return str;

        return val;
    }

    final int adjustStartPos(RubyString str, int pos, boolean reverse) {
        check();
        ByteList value = str.getByteList();
        return pattern.adjustStartPosition(value.bytes, value.begin, value.realSize, pos, reverse);
    }

    @JRubyMethod(name = "casefold?")
    public IRubyObject casefold_p(ThreadContext context) {
        check();
        
        return context.getRuntime().newBoolean((pattern.getOptions() & RE_OPTION_IGNORECASE) != 0);
    }

    /** rb_reg_source
     * 
     */
    @JRubyMethod(name = "source")
    public IRubyObject source() {
        Ruby runtime = getRuntime();
        check();
        RubyString str = RubyString.newStringShared(runtime, this.str);
        if(isTaint()) {
            str.taint(runtime.getCurrentContext());
        }
        return str;
    }

    final int length() {
        return str.realSize;
    }

    /** rb_reg_inspect
     *
     */
    @JRubyMethod(name = "inspect")
    @Override
    public IRubyObject inspect() {
        check();
        return getRuntime().newString(ByteList.create(rb_reg_desc(str.bytes, str.begin, str.realSize, pattern.getOptions()).toString()));
    }

    private final static int EMBEDDABLE = RE_OPTION_MULTILINE|RE_OPTION_IGNORECASE|RE_OPTION_EXTENDED;

    @JRubyMethod(name = "to_s")
    @Override
    public IRubyObject to_s() {
        RubyString ss = getRuntime().newString("(?");
        check();

        int options = pattern.getOptions();
        int ptr = str.begin;
        int len = str.realSize;
        byte[] bytes = str.bytes;
        again: do {
            if (len >= 4 && bytes[ptr] == '(' && bytes[ptr + 1] == '?') {
                boolean err = true;
                ptr += 2;
                if ((len -= 2) > 0) {
                    do {
                        if (bytes[ptr] == 'm') {
                            options |= RE_OPTION_MULTILINE;
                        } else if (bytes[ptr] == 'i') {
                            options |= RE_OPTION_IGNORECASE;
                        } else if (bytes[ptr] == 'x') {
                            options |= RE_OPTION_EXTENDED;
                        } else {
                            break;
                        }
                        ptr++;
                    } while(--len > 0);
                }
                if (len > 1 && bytes[ptr] == '-') {
                    ++ptr;
                    --len;
                    do {
                        if (bytes[ptr] == 'm') {
                            options &= ~RE_OPTION_MULTILINE;
                        } else if (bytes[ptr] == 'i') {
                            options &= ~RE_OPTION_IGNORECASE;
                        } else if (bytes[ptr] == 'x') {
                            options &= ~RE_OPTION_EXTENDED;
                        } else {
                            break;
                        }
                        ptr++;
                    } while(--len > 0);
                }

                if (bytes[ptr] == ')') {
                    --len;
                    ++ptr;
                    continue again;
                }

                if (bytes[ptr] == ':' && bytes[ptr + len - 1] == ')') {
                    try {
                        new Regex(bytes, ++ptr, ptr + (len-=2) ,Option.DEFAULT, kcode.getEncoding(), Syntax.DEFAULT);
                        err = false;
                    } catch (JOniException e) {
                        err = true;
                    }
                }

                if (err) {
                    options = (int)pattern.getOptions();
                    ptr = str.begin;
                    len = str.realSize;
                }
            }
            if ((options & RE_OPTION_MULTILINE) !=0 ) ss.cat((byte)'m');
            if ((options & RE_OPTION_IGNORECASE) !=0 ) ss.cat((byte)'i');
            if ((options & RE_OPTION_EXTENDED) !=0 ) ss.cat((byte)'x');

            if ((options & EMBEDDABLE) != EMBEDDABLE) {
                ss.cat((byte)'-');
                if ((options & RE_OPTION_MULTILINE) == 0) ss.cat((byte)'m');
                if ((options & RE_OPTION_IGNORECASE) == 0) ss.cat((byte)'i');
                if ((options & RE_OPTION_EXTENDED) == 0) ss.cat((byte)'x');
            }
            ss.cat((byte)':');
            rb_reg_expr_str(ss, ptr, len);
            ss.cat((byte)')');
            ss.infectBy(this);
            return ss;
        } while(true);
    }

    private final void rb_reg_expr_str(RubyString ss, int s, int len) {
        int p = s;
        int pend = p + len;
        boolean need_escape = false;
        Encoding enc = kcode.getEncoding();
        while (p < pend) {
            if (str.bytes[p] == '/' || (!enc.isPrint(str.bytes[p] & 0xff) && enc.length(str.bytes[p]) == 1)) {
                need_escape = true;
                break;
            }
            p += enc.length(str.bytes[p]);
        }
        if (!need_escape) {
            ss.cat(str.bytes, s, len);
        } else {
            p = s; 
            while (p<pend) {
                if (str.bytes[p] == '\\') {
                    int n = enc.length(str.bytes[p+1]) + 1;
                    ss.cat(str.bytes, p, n);
                    p += n;
                    continue;
                } else if (str.bytes[p] == '/') {
                    ss.cat((byte)'\\');
                    ss.cat(str.bytes, p, 1);
                } else if (enc.length(str.bytes[p]) != 1) {
                    ss.cat(str.bytes, p, enc.length(str.bytes[p]));
                    p += enc.length(str.bytes[p]);
                    continue;
                } else if (enc.isPrint(str.bytes[p] & 0xff)) {
                    ss.cat(str.bytes,p,1);
                } else if (!enc.isSpace(str.bytes[p] & 0xff)) {
                    ss.cat(ByteList.create(Integer.toString(str.bytes[p] & 0377, 8)));
                } else {
                    ss.cat(str.bytes, p, 1);
                }
                p++;
            }
        }
    }

    /** rb_reg_s_quote
     * 
     */
    @JRubyMethod(name = {"quote", "escape"}, required = 1, optional = 1, meta = true)
    public static RubyString quote(IRubyObject recv, IRubyObject[] args) {
        IRubyObject kcode = args.length == 2 ? args[1] : null;
        IRubyObject str = args[0];
        KCode code = recv.getRuntime().getKCode();

        if (kcode != null && !kcode.isNil()) {
            code = KCode.create(recv.getRuntime(), kcode.toString());
        }
        
        RubyString src = str.convertToString();
        RubyString dst = RubyString.newString(recv.getRuntime(), quote(src.getByteList(), code));
        dst.infectBy(src);
        return dst;
    }

    /** rb_reg_quote
     *
     */
    public static ByteList quote(ByteList str, KCode kcode) {
        ByteList bs = str;
        int tix = 0;
        int s = bs.begin;
        char c;
        int send = s+bs.length();
        Encoding enc = kcode.getEncoding();
        meta_found: do {
            for(; s<send; s++) {
                c = (char)(bs.bytes[s]&0xFF);
                if(enc.length((byte)c) != 1) {
                    int n = enc.length((byte)c);
                    while(n-- > 0 && s < send) {
                        s++;
                    }
                    s--;
                    continue;
                }
                switch (c) {
                case '[': case ']': case '{': case '}':
                case '(': case ')': case '|': case '-':
                case '*': case '.': case '\\':
                case '?': case '+': case '^': case '$':
                case ' ': case '#':
                case '\t': case '\f': case '\n': case '\r':
                    break meta_found;
                }
            }
            return bs;
        } while(false);
        ByteList b1 = new ByteList(send*2);
        System.arraycopy(bs.bytes,bs.begin,b1.bytes,b1.begin,s-bs.begin);
        tix += (s-bs.begin);

        for(; s<send; s++) {
            c = (char)(bs.bytes[s]&0xFF);
            if(enc.length((byte)c) != 1) {
                int n = enc.length((byte)c);
                while(n-- > 0 && s < send) {
                    b1.bytes[tix++] = bs.bytes[s++];
                }
                s--;
                continue;
            }

            switch(c) {
            case '[': case ']': case '{': case '}':
            case '(': case ')': case '|': case '-':
            case '*': case '.': case '\\':
            case '?': case '+': case '^': case '$':
            case '#':
                b1.bytes[tix++] = '\\';
                break;
            case ' ':
                b1.bytes[tix++] = '\\';
                b1.bytes[tix++] = ' ';
                continue;
            case '\t':
                b1.bytes[tix++] = '\\';
                b1.bytes[tix++] = 't';
                 continue;
            case '\n':
                b1.bytes[tix++] = '\\';
                b1.bytes[tix++] = 'n';
                continue;
            case '\r':
                b1.bytes[tix++] = '\\';
                b1.bytes[tix++] = 'r';
                continue;
            case '\f':
                b1.bytes[tix++] = '\\';
                b1.bytes[tix++] = 'f';
                continue;
            }
            b1.bytes[tix++] = (byte)c;
        }
        b1.realSize = tix;
        return b1;
    }


    /** rb_reg_nth_match
     *
     */
    public static IRubyObject nth_match(int nth, IRubyObject match) {
        if (match.isNil()) return match;
        RubyMatchData m = (RubyMatchData)match;

        int start, end;
        
        if (m.regs == null) {
            if (nth >= 1) return match.getRuntime().getNil();
            if (nth < 0 && ++nth <= 0) return match.getRuntime().getNil();
            start = m.begin;
            end = m.end;
        } else {
            if (nth >= m.regs.numRegs) return match.getRuntime().getNil();
            if (nth < 0 && (nth+=m.regs.numRegs) <= 0) return match.getRuntime().getNil();
            start = m.regs.beg[nth];
            end = m.regs.end[nth];
        }
        
        if (start == -1) return match.getRuntime().getNil();

        RubyString str = m.str.makeShared(match.getRuntime(), start, end - start);
        str.infectBy(match);
        return str;
    }

    /** rb_reg_last_match
     *
     */
    public static IRubyObject last_match(IRubyObject match) {
        return nth_match(0, match);
    }

    /**
     * Variable arity version for compatibility. Not bound to a Ruby method.
     * @deprecated Use the versions with zero, one, or two args.
     */
    public static IRubyObject last_match_s(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        switch (args.length) {
        case 0:
            return last_match_s(context, recv);
        case 1:
            return last_match_s(context, recv, args[0]);
        default:
            Arity.raiseArgumentError(recv.getRuntime(), args.length, 0, 1);
            return null; // not reached
        }
    }

    /** rb_reg_s_last_match / match_getter
    *
    */
    @JRubyMethod(name = "last_match", meta = true, reads = BACKREF)
    public static IRubyObject last_match_s(ThreadContext context, IRubyObject recv) {
        IRubyObject match = context.getCurrentFrame().getBackRef();
        if (match instanceof RubyMatchData) ((RubyMatchData)match).use();
        return match;
    }

    /** rb_reg_s_last_match
    *
    */
    @JRubyMethod(name = "last_match", meta = true, reads = BACKREF)
    public static IRubyObject last_match_s(ThreadContext context, IRubyObject recv, IRubyObject nth) {
        IRubyObject match = context.getCurrentFrame().getBackRef();
        if (match.isNil()) return match;
        return nth_match(((RubyMatchData)match).backrefNumber(nth), match);
    }

    /** rb_reg_match_pre
     *
     */
    public static IRubyObject match_pre(IRubyObject match) {
        if (match.isNil()) return match;
        RubyMatchData m = (RubyMatchData)match;
        
        int beg = m.regs == null ? m.begin : m.regs.beg[0];
        
        if(beg == -1) match.getRuntime().getNil(); 

        RubyString str = m.str.makeShared(match.getRuntime(), 0, beg);
        str.infectBy(match);
        return str;
    }

    /** rb_reg_match_post
     *
     */
    public static IRubyObject match_post(IRubyObject match) {
        if (match.isNil()) return match;
        RubyMatchData m = (RubyMatchData)match;

        int end;
        if (m.regs == null) {
            if (m.begin == -1) return match.getRuntime().getNil();
            end = m.end;
        } else {
            if (m.regs.beg[0] == -1) return match.getRuntime().getNil();
            end = m.regs.end[0];
        }
        
        RubyString str = m.str.makeShared(match.getRuntime(), end, m.str.getByteList().realSize - end);
        str.infectBy(match);
        return str;
    }

    /** rb_reg_match_last
     *
     */
    public static IRubyObject match_last(IRubyObject match) {
        if (match.isNil()) return match;
        RubyMatchData m = (RubyMatchData)match;

        if (m.regs == null || m.regs.beg[0] == -1) return match.getRuntime().getNil();

        int i;
        for (i=m.regs.numRegs-1; m.regs.beg[i]==-1 && i>0; i--);
        if (i == 0) return match.getRuntime().getNil();
        
        return nth_match(i,match);
    }

    /** rb_reg_s_union
     *
     */
    @JRubyMethod(name = "union", rest = true, meta = true)
    public static IRubyObject union(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
        if (args.length == 0) {
            return newRegexp(recv.getRuntime(), ByteList.create("(?!)"), 0, false);
        } else if (args.length == 1) {
            IRubyObject v = TypeConverter.convertToTypeWithCheck(args[0], recv.getRuntime().getRegexp(), 0, "to_regexp");
            if(!v.isNil()) {
                return v;
            } else {
                // newInstance here
                return newRegexp(recv.getRuntime(), quote(recv,args).getByteList(), 0, false);
            }
        } else {
            KCode kcode = null;
            IRubyObject kcode_re = recv.getRuntime().getNil();
            RubyString source = recv.getRuntime().newString();
            IRubyObject[] _args = new IRubyObject[3];

            for (int i = 0; i < args.length; i++) {
                if (0 < i) {
                    source.cat((byte)'|');
                }
                IRubyObject v = TypeConverter.convertToTypeWithCheck(args[i], recv.getRuntime().getRegexp(), 0, "to_regexp");
                if (!v.isNil()) {
                    if (!((RubyRegexp)v).isKCodeDefault()) {
                        if (kcode == null) {
                            kcode_re = v;
                            kcode = ((RubyRegexp)v).kcode;
                        } else if (((RubyRegexp)v).kcode != kcode) {
                            IRubyObject str1 = kcode_re.inspect();
                            IRubyObject str2 = v.inspect();
                            throw recv.getRuntime().newArgumentError("mixed kcode " + str1 + " and " + str2);
                        }
                    }
                    v = ((RubyRegexp)v).to_s();
                } else {
                    v = quote(recv, new IRubyObject[]{args[i]});
                }
                source.append(v);
            }

            _args[0] = source;
            _args[1] = recv.getRuntime().getNil();
            if (kcode == null) {
                _args[2] = recv.getRuntime().getNil();
            } else if (kcode == KCode.NONE) {
                _args[2] = recv.getRuntime().newString("n");
            } else if (kcode == KCode.EUC) {
                _args[2] = recv.getRuntime().newString("e");
            } else if (kcode == KCode.SJIS) {
                _args[2] = recv.getRuntime().newString("s");
            } else if (kcode == KCode.UTF8) {
                _args[2] = recv.getRuntime().newString("u");
            }
            return recv.callMethod(context, "new", _args);
        }
    }

    /** rb_reg_names
     * 
     */
    @JRubyMethod(name = "names", compat = CompatVersion.RUBY1_9)
    public IRubyObject names() {
        if (pattern.numberOfNames() == 0) return getRuntime().newEmptyArray();

        RubyArray ary = getRuntime().newArray(pattern.numberOfNames());
        for (Iterator<NameEntry> i = pattern.namedBackrefIterator(); i.hasNext();) {
            NameEntry e = i.next();
            ary.append(RubyString.newStringShared(getRuntime(), e.name, e.nameP, e.nameEnd - e.nameP));
        }
        return ary;
    }

    /** rb_reg_named_captures
     * 
     */
    @JRubyMethod(name = "named_captures", compat = CompatVersion.RUBY1_9)
    public IRubyObject named_captures(ThreadContext context) {
        RubyHash hash = RubyHash.newHash(getRuntime());
        if (pattern.numberOfNames() == 0) return hash;

        for (Iterator<NameEntry> i = pattern.namedBackrefIterator(); i.hasNext();) {
            NameEntry e = i.next();
            int[]backrefs = e.getBackRefs();
            RubyArray ary = getRuntime().newArray(backrefs.length);

            for (int backref : backrefs) ary.append(RubyFixnum.newFixnum(getRuntime(), backref));
            hash.fastASet(RubyString.newStringShared(getRuntime(), e.name, e.nameP, e.nameEnd - e.nameP).freeze(context), ary);
        }
        return hash;
    }    

    public static RubyRegexp unmarshalFrom(UnmarshalStream input) throws java.io.IOException {
        RubyRegexp result = newRegexp(input.getRuntime(), input.unmarshalString(), input.unmarshalInt(), false);
        input.registerLinkTarget(result);
        return result;
    }

    public static void marshalTo(RubyRegexp regexp, MarshalStream output) throws java.io.IOException {
        output.registerLinkTarget(regexp);
        output.writeString(new String(regexp.str.bytes,regexp.str.begin,regexp.str.realSize));
        output.writeInt(regexp.pattern.getOptions() & EMBEDDABLE);
    }
}

package org.jruby;

import org.jruby.runtime.builtin.IRubyObject;

/**
 *
 * @author nicksieger
 */
public interface RubyRuntimeAdapter {
    IRubyObject eval(Ruby runtime, String script);
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2007 Ola Bini <ola@ologix.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyModule;
import org.jruby.javasupport.util.RuntimeHelpers;
import org.jruby.runtime.Block;
import org.jruby.runtime.CallType;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;

import org.jruby.util.SignalFacade;
import org.jruby.util.NoFunctionalitySignalFacade;

@JRubyModule(name="Signal")
public class RubySignal {
    private final static SignalFacade SIGNALS = getSignalFacade();

    private final static SignalFacade getSignalFacade() {
        try {
            Class realFacadeClass = Class.forName("org.jruby.util.SunSignalFacade");
            return (SignalFacade)realFacadeClass.newInstance();
        } catch(Throwable e) {
            return new NoFunctionalitySignalFacade();
        }
    }

    // NOTE: The indicies here match exactly the signal values; do not reorder
    public static final String[] NAMES = {
            "EXIT", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "EMT",
            "FPE", "KILL", "BUS", "SEGV", "SYS", "PIPE", "ALRM", "TERM", "URG",
            "STOP", "TSTP", "CONT", "CHLD", "TTIN", "TTOU", "IO", "XCPU",
            "XFSZ", "VTALRM", "PROF", "WINCH", "INFO", "USR1", "USR2"};
    
    public static void createSignal(Ruby runtime) {
        RubyModule mSignal = runtime.defineModule("Signal");
        
        mSignal.defineAnnotatedMethods(RubySignal.class);
    }

    @JRubyMethod(name = "trap", required = 1, optional = 1, frame = true, meta = true)
    public static IRubyObject trap(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
        Ruby runtime = recv.getRuntime();
        runtime.getLoadService().require("jsignal");
        return RuntimeHelpers.invoke(context, runtime.getKernel(), "__jtrap", args, block);
    }
    
    @JRubyMethod(name = "list", meta = true)
    public static IRubyObject list(ThreadContext context, IRubyObject recv) {
        Ruby runtime = recv.getRuntime();
        RubyHash names = RubyHash.newHash(runtime);
        for (int i = 0; i < NAMES.length; i++) {
            names.op_aset(context, runtime.newString(NAMES[i]), runtime.newFixnum(i));
        }
        // IOT is also 6
        names.op_aset(context, runtime.newString("IOT"), runtime.newFixnum(6));
        // CLD is also 20
        names.op_aset(context, runtime.newString("CLD"), runtime.newFixnum(20));
        return names;
    }

    @JRubyMethod(name = "__jtrap_kernel", required = 3,meta = true)
    public static IRubyObject __jtrap_kernel(final IRubyObject recv, IRubyObject arg1, IRubyObject arg2, IRubyObject arg3) {
        return SIGNALS.trap(recv, arg1, arg2, arg3);
    }
}// RubySignal
/*
 **** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2002-2006 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2004 David Corbin <dcorbin@users.sourceforge.net>
 * Copyright (C) 2005 Tim Azzopardi <tim@tigerfive.com>
 * Copyright (C) 2006 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * Copyright (C) 2006 Ola Bini <ola@ologix.com>
 * Copyright (C) 2007 Nick Sieger <nicksieger@gmail.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import static org.jruby.anno.FrameField.BACKREF;
import static org.jruby.anno.FrameField.LASTLINE;

import java.io.UnsupportedEncodingException;
import java.util.Locale;

import org.joni.Matcher;
import org.joni.Option;
import org.joni.Regex;
import org.joni.Region;
import org.joni.encoding.Encoding;
import org.joni.encoding.specific.ASCIIEncoding;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.exceptions.RaiseException;
import org.jruby.java.MiniJava;
import org.jruby.javasupport.util.RuntimeHelpers;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.Frame;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.marshal.UnmarshalStream;
import org.jruby.util.ByteList;
import org.jruby.util.Numeric;
import org.jruby.util.Pack;
import org.jruby.util.Sprintf;
import org.jruby.util.string.JavaCrypt;

/**
 * Implementation of Ruby String class
 * 
 * Concurrency: no synchronization is required among readers, but
 * all users must synchronize externally with writers.
 *
 */
@JRubyClass(name="String", include={"Enumerable", "Comparable"})
public class RubyString extends RubyObject {
    private static final ASCIIEncoding ASCII = ASCIIEncoding.INSTANCE;

    // string doesn't share any resources
    private static final int SHARE_LEVEL_NONE = 0;
    // string has it's own ByteList, but it's pointing to a shared buffer (byte[])
    private static final int SHARE_LEVEL_BUFFER = 1;
    // string doesn't have it's own ByteList (values)
    private static final int SHARE_LEVEL_BYTELIST = 2;

    private volatile int shareLevel = SHARE_LEVEL_NONE;

    private ByteList value;

    private static ObjectAllocator STRING_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return RubyString.newEmptyString(runtime, klass);
        }
    };

    public static RubyClass createStringClass(Ruby runtime) {
        RubyClass stringClass = runtime.defineClass("String", runtime.getObject(), STRING_ALLOCATOR);
        runtime.setString(stringClass);
        stringClass.index = ClassIndex.STRING;
        stringClass.kindOf = new RubyModule.KindOf() {
            @Override
                public boolean isKindOf(IRubyObject obj, RubyModule type) {
                    return obj instanceof RubyString;
                }
            };
            
        stringClass.includeModule(runtime.getComparable());
        stringClass.includeModule(runtime.getEnumerable());
        stringClass.defineAnnotatedMethods(RubyString.class);
        
        return stringClass;
    }

    /** short circuit for String key comparison
     * 
     */
    @Override
    public final boolean eql(IRubyObject other) {
        if (other.getMetaClass() == getRuntime().getString()) return value.equal(((RubyString)other).value);
        return super.eql(other);
    }

    private RubyString(Ruby runtime, RubyClass rubyClass, CharSequence value) {
        super(runtime, rubyClass);
        assert value != null;
        this.value = new ByteList(ByteList.plain(value), false);
    }

    private RubyString(Ruby runtime, RubyClass rubyClass, byte[] value) {
        super(runtime, rubyClass);
        assert value != null;
        this.value = new ByteList(value);
    }

    private RubyString(Ruby runtime, RubyClass rubyClass, ByteList value) {
        super(runtime, rubyClass);
        assert value != null;
        this.value = value;
    }
    
    private RubyString(Ruby runtime, RubyClass rubyClass, ByteList value, boolean objectSpace) {
        super(runtime, rubyClass, objectSpace);
        assert value != null;
        this.value = value;
    }
    

    /** Create a new String which uses the same Ruby runtime and the same
     *  class like this String.
     *
     *  This method should be used to satisfy RCR #38.
     *  @deprecated  
     */
    public RubyString newString(CharSequence s) {
        return new RubyString(getRuntime(), getType(), s);
    }

    /** Create a new String which uses the same Ruby runtime and the same
     *  class like this String.
     *
     *  This method should be used to satisfy RCR #38.
     *  @deprecated
     */
    public RubyString newString(ByteList s) {
        return new RubyString(getRuntime(), getMetaClass(), s);
    }

    // Methods of the String class (rb_str_*):

    /** rb_str_new2
     *
     */
    public static RubyString newString(Ruby runtime, CharSequence str) {
        return new RubyString(runtime, runtime.getString(), str);
    }
    
    public static RubyString newEmptyString(Ruby runtime) {
        return newEmptyString(runtime, runtime.getString());
    }

    public static RubyString newEmptyString(Ruby runtime, RubyClass metaClass) {
        RubyString empty = new RubyString(runtime, metaClass, ByteList.EMPTY_BYTELIST);
        empty.shareLevel = SHARE_LEVEL_BYTELIST;
        return empty;
    }

    public static RubyString newUnicodeString(Ruby runtime, String str) {
        try {
            return new RubyString(runtime, runtime.getString(), new ByteList(str.getBytes("UTF8"), false));
        } catch (UnsupportedEncodingException uee) {
            return new RubyString(runtime, runtime.getString(), str);
        }
    }

    @Deprecated
    public static RubyString newString(Ruby runtime, RubyClass clazz, CharSequence str) {
        return new RubyString(runtime, clazz, str);
    }

    public static RubyString newString(Ruby runtime, byte[] bytes) {
        return new RubyString(runtime, runtime.getString(), bytes);
    }

    public static RubyString newString(Ruby runtime, byte[] bytes, int start, int length) {
        byte[] copy = new byte[length];
        System.arraycopy(bytes, start, copy, 0, length);
        return new RubyString(runtime, runtime.getString(), new ByteList(copy, false));
    }

    public static RubyString newString(Ruby runtime, ByteList bytes) {
        return new RubyString(runtime, runtime.getString(), bytes);
    }

    public static RubyString newStringLight(Ruby runtime, ByteList bytes) {
        return new RubyString(runtime, runtime.getString(), bytes, false);
    }

    public static RubyString newStringShared(Ruby runtime, RubyString orig) {
        orig.shareLevel = SHARE_LEVEL_BYTELIST;
        RubyString str = new RubyString(runtime, runtime.getString(), orig.value);
        str.shareLevel = SHARE_LEVEL_BYTELIST;
        return str;
    }       
    
    public static RubyString newStringShared(Ruby runtime, ByteList bytes) {
        return newStringShared(runtime, runtime.getString(), bytes);
    }    

    public static RubyString newStringShared(Ruby runtime, RubyClass clazz, ByteList bytes) {
        RubyString str = new RubyString(runtime, clazz, bytes);
        str.shareLevel = SHARE_LEVEL_BYTELIST;
        return str;
    }    

    public static RubyString newStringShared(Ruby runtime, byte[] bytes, int start, int length) {
        RubyString str = new RubyString(runtime, runtime.getString(), new ByteList(bytes, start, length, false));
        str.shareLevel = SHARE_LEVEL_BUFFER;
        return str;
    }

    @Override
    public int getNativeTypeIndex() {
        return ClassIndex.STRING;
    }

    @Override
    public Class getJavaClass() {
        return String.class;
    }

    @Override
    public RubyString convertToString() {
        return this;
    }

    @Override
    public String toString() {
        return value.toString();
    }

    /** rb_str_dup
     * 
     */
    @Deprecated
    public final RubyString strDup() {
        return strDup(getRuntime(), getMetaClass());
    }
    
    public final RubyString strDup(Ruby runtime) {
        return strDup(runtime, getMetaClass());
    }
    
    @Deprecated
    final RubyString strDup(RubyClass clazz) {
        return strDup(getRuntime(), getMetaClass());
    }

    final RubyString strDup(Ruby runtime, RubyClass clazz) {
        shareLevel = SHARE_LEVEL_BYTELIST;
        RubyString dup = new RubyString(runtime, clazz, value);
        dup.shareLevel = SHARE_LEVEL_BYTELIST;

        dup.infectBy(this);
        return dup;
    }

    public final RubyString makeShared(Ruby runtime, int index, int len) {
        if (len == 0) {
            RubyString s = newEmptyString(runtime, getMetaClass());
            s.infectBy(this);
            return s;
        }

        if (shareLevel == SHARE_LEVEL_NONE) shareLevel = SHARE_LEVEL_BUFFER;
        RubyString shared = new RubyString(runtime, getMetaClass(), value.makeShared(index, len));
        shared.shareLevel = SHARE_LEVEL_BUFFER;

        shared.infectBy(this);
        return shared;
    }

    final void modifyCheck() {
        if ((flags & FROZEN_F) != 0) throw getRuntime().newFrozenError("string");

        if (!isTaint() && getRuntime().getSafeLevel() >= 4) {
            throw getRuntime().newSecurityError("Insecure: can't modify string");
        }
    }
    
    private final void modifyCheck(byte[] b, int len) {
        if (value.bytes != b || value.realSize != len) throw getRuntime().newRuntimeError("string modified");
    }
    
    private final void frozenCheck() {
        if (isFrozen()) throw getRuntime().newRuntimeError("string frozen");
    }

    /** rb_str_modify
     * 
     */
    public final void modify() {
        modifyCheck();

        if (shareLevel != SHARE_LEVEL_NONE) {
            if (shareLevel == SHARE_LEVEL_BYTELIST) {
                value = value.dup();
            } else {
                value.unshare();
            }
            shareLevel = SHARE_LEVEL_NONE;
        }

        value.invalidate();
    }

    /** rb_str_modify (with length bytes ensured)
     * 
     */    
    public final void modify(int length) {
        modifyCheck();

        if (shareLevel != SHARE_LEVEL_NONE) {
            if (shareLevel == SHARE_LEVEL_BYTELIST) {
                value = value.dup(length);
            } else {
                value.unshare(length);
            }
            shareLevel = SHARE_LEVEL_NONE;
        } else {
            value.ensure(length);
        }

        value.invalidate();
    }
    
    private final void view(ByteList bytes) {
        modifyCheck();

        value = bytes;
        shareLevel = SHARE_LEVEL_NONE;
    }

    private final void view(byte[]bytes) {
        modifyCheck();        

        value.replace(bytes);
        shareLevel = SHARE_LEVEL_NONE;

        value.invalidate();        
    }

    private final void view(int index, int len) {
        modifyCheck();

        if (shareLevel != SHARE_LEVEL_NONE) {
            if (shareLevel == SHARE_LEVEL_BYTELIST) {
                // if len == 0 then shared empty
                value = value.makeShared(index, len);
                shareLevel = SHARE_LEVEL_BUFFER;
            } else {
                value.view(index, len);
            }
        } else {        
            value.view(index, len);
            // FIXME this below is temporary, but its much safer for COW (it prevents not shared Strings with begin != 0)
            // this allows now e.g.: ByteList#set not to be begin aware
            shareLevel = SHARE_LEVEL_BUFFER;
        }

        value.invalidate();
    }

    public static String bytesToString(byte[] bytes, int beg, int len) {
        return new String(ByteList.plain(bytes, beg, len));
    }

    public static String byteListToString(ByteList bytes) {
        return bytesToString(bytes.unsafeBytes(), bytes.begin(), bytes.length());
    }

    public static String bytesToString(byte[] bytes) {
        return bytesToString(bytes, 0, bytes.length);
    }

    public static byte[] stringToBytes(String string) {
        return ByteList.plain(string);
    }

    public static boolean isDigit(int c) {
        return c >= '0' && c <= '9';
    }

    public static boolean isUpper(int c) {
        return c >= 'A' && c <= 'Z';
    }

    public static boolean isLower(int c) {
        return c >= 'a' && c <= 'z';
    }

    public static boolean isLetter(int c) {
        return isUpper(c) || isLower(c);
    }

    public static boolean isAlnum(int c) {
        return isUpper(c) || isLower(c) || isDigit(c);
    }

    public static boolean isPrint(int c) {
        return c >= 0x20 && c <= 0x7E;
    }

    @Override
    public RubyString asString() {
        return this;
    }

    @Override
    public IRubyObject checkStringType() {
        return this;
    }

    @JRubyMethod(name = {"to_s", "to_str"})
    @Override
    public IRubyObject to_s() {
        Ruby runtime = getRuntime();
        if (getMetaClass().getRealClass() != runtime.getString()) {
            return strDup(runtime, runtime.getString());
        }
        return this;
    }

    /* rb_str_cmp_m */
    @JRubyMethod(name = "<=>", required = 1)
    public IRubyObject op_cmp(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyString) {
            return context.getRuntime().newFixnum(op_cmp((RubyString)other));
        }

        // deal with case when "other" is not a string
        if (other.respondsTo("to_str") && other.respondsTo("<=>")) {
            IRubyObject result = other.callMethod(context, MethodIndex.OP_SPACESHIP, "<=>", this);

            if (result instanceof RubyNumeric) {
                return ((RubyNumeric) result).op_uminus(context);
            }
        }

        return context.getRuntime().getNil();
    }
        
    /**
     * 
     */
    @JRubyMethod(name = "==", required = 1)
    @Override
    public IRubyObject op_equal(ThreadContext context, IRubyObject other) {
        Ruby runtime = context.getRuntime();
        
        if (this == other) return runtime.getTrue();
        
        if (!(other instanceof RubyString)) {
            if (!other.respondsTo("to_str")) return runtime.getFalse();

            return other.callMethod(context, MethodIndex.EQUALEQUAL, "==", this).isTrue() ? runtime.getTrue() : runtime.getFalse();
        }
        return value.equal(((RubyString)other).value) ? runtime.getTrue() : runtime.getFalse();
    }

    @JRubyMethod(name = "+", required = 1)
    public IRubyObject op_plus(ThreadContext context, IRubyObject other) {
        RubyString str = other.convertToString();
        
        ByteList result = new ByteList(value.realSize + str.value.realSize);
        result.realSize = value.realSize + str.value.realSize;
        System.arraycopy(value.bytes, value.begin, result.bytes, 0, value.realSize);
        System.arraycopy(str.value.bytes, str.value.begin, result.bytes, value.realSize, str.value.realSize);
      
        RubyString resultStr = newString(context.getRuntime(), result);
        if (isTaint() || str.isTaint()) resultStr.setTaint(true);
        return resultStr;
    }

    @JRubyMethod(name = "*", required = 1)
    public IRubyObject op_mul(ThreadContext context, IRubyObject other) {
        RubyInteger otherInteger = (RubyInteger) other.convertToInteger();
        long len = otherInteger.getLongValue();

        if (len < 0) throw context.getRuntime().newArgumentError("negative argument");

        // we limit to int because ByteBuffer can only allocate int sizes
        if (len > 0 && Integer.MAX_VALUE / len < value.length()) {
            throw context.getRuntime().newArgumentError("argument too big");
        }
        ByteList newBytes = new ByteList(value.length() * (int)len);

        for (int i = 0; i < len; i++) {
            newBytes.append(value);
        }

        RubyString newString = new RubyString(context.getRuntime(), getMetaClass(), newBytes);
        newString.setTaint(isTaint());
        return newString;
    }

    @JRubyMethod(name = "%", required = 1)
    public IRubyObject op_format(ThreadContext context, IRubyObject arg) {
        final RubyString s;

        IRubyObject tmp = arg.checkArrayType();
        if (tmp.isNil()) {
            tmp = arg;
        }

        // FIXME: Should we make this work with platform's locale,
        // or continue hardcoding US?
        s = Sprintf.sprintf(context.getRuntime(), Locale.US, value, tmp);

        s.infectBy(this);
        return s;
    }

    @JRubyMethod(name = "hash")
    @Override
    public RubyFixnum hash() {
        return getRuntime().newFixnum(value.hashCode());
    }

    @Override
    public int hashCode() {
        return value.hashCode();
    }

    @Override
    public boolean equals(Object other) {
        if (this == other) return true;

        if (other instanceof RubyString) {
            RubyString string = (RubyString) other;

            if (string.value.equal(value)) return true;
        }

        return false;
    }

    /** rb_obj_as_string
     *
     */
    public static RubyString objAsString(ThreadContext context, IRubyObject obj) {
        if (obj instanceof RubyString) return (RubyString) obj;

        IRubyObject str = obj.callMethod(context, MethodIndex.TO_S, "to_s");

        if (!(str instanceof RubyString)) return (RubyString) obj.anyToString();

        if (obj.isTaint()) str.setTaint(true);

        return (RubyString) str;
    }

    /** rb_str_cmp
     *
     */
    public int op_cmp(RubyString other) {
        return value.cmp(other.value);
    }

    /** rb_to_id
     *
     */
    @Override
    public String asJavaString() {
        // TODO: This used to intern; but it didn't appear to change anything
        // turning that off, and it's unclear if it was needed. Plus, we intern
        // 
        return toString();
    }

    public IRubyObject doClone(){
        return newString(getRuntime(), value.dup());
    }

    public RubyString cat(byte[] str) {
        modify(value.realSize + str.length);
        System.arraycopy(str, 0, value.bytes, value.begin + value.realSize, str.length);
        value.realSize += str.length;
        return this;
    }

    public RubyString cat(byte[] str, int beg, int len) {
        modify(value.realSize + len);        
        System.arraycopy(str, beg, value.bytes, value.begin + value.realSize, len);
        value.realSize += len;
        return this;
    }

    public RubyString cat(ByteList str) {
        modify(value.realSize + str.realSize);
        System.arraycopy(str.bytes, str.begin, value.bytes, value.begin + value.realSize, str.realSize);
        value.realSize += str.realSize;
        return this;
    }

    public RubyString cat(byte ch) {
        modify(value.realSize + 1);        
        value.bytes[value.begin + value.realSize] = ch;
        value.realSize++;
        return this;
    }
    
    /** rb_str_replace_m
     *
     */
    @JRubyMethod(name = {"replace", "initialize_copy"}, required = 1)
    public RubyString replace(IRubyObject other) {
        if (this == other) return this;

        modifyCheck();

        RubyString otherStr =  stringValue(other);

        otherStr.shareLevel = shareLevel = SHARE_LEVEL_BYTELIST;

        value = otherStr.value;

        infectBy(other);
        return this;
    }

    @JRubyMethod(name = "reverse")
    public RubyString reverse(ThreadContext context) {
        if (value.length() <= 1) return strDup(context.getRuntime());

        ByteList buf = new ByteList(value.length()+2);
        buf.realSize = value.length();
        int src = value.length() - 1;
        int dst = 0;
        
        while (src >= 0) buf.set(dst++, value.get(src--));

        RubyString rev = new RubyString(context.getRuntime(), getMetaClass(), buf);
        rev.infectBy(this);
        return rev;
    }

    @JRubyMethod(name = "reverse!")
    public RubyString reverse_bang() {
        if (value.length() > 1) {
            modify();
            for (int i = 0; i < (value.length() / 2); i++) {
                byte b = (byte) value.get(i);
                
                value.set(i, value.get(value.length() - i - 1));
                value.set(value.length() - i - 1, b);
            }
        }
        
        return this;
    }

    /** rb_str_s_new
     *
     */
    public static RubyString newInstance(IRubyObject recv, IRubyObject[] args, Block block) {
        RubyString newString = newStringShared(recv.getRuntime(), ByteList.EMPTY_BYTELIST);
        newString.setMetaClass((RubyClass) recv);
        newString.callInit(args, block);
        return newString;
    }
    
    /**
     * Variable-arity version for compatibility. Not bound to Ruby.
     * @deprecated Use the versions with zero or one arguments
     */
    public IRubyObject initialize(IRubyObject[] args, Block unusedBlock) {
        switch (args.length) {
        case 0:
            return this;
        case 1:
            return initialize(args[0]);
        default:
            Arity.raiseArgumentError(getRuntime(), args.length, 0, 1);
            return null; // not reached
        }
    }

    @JRubyMethod(frame = true, visibility = Visibility.PRIVATE)
    @Override
    public IRubyObject initialize() {
        return this;
    }

    @JRubyMethod(frame = true, visibility = Visibility.PRIVATE)
    public IRubyObject initialize(IRubyObject arg0) {
        replace(arg0);

        return this;
    }

    @JRubyMethod
    public IRubyObject casecmp(IRubyObject other) {
        int compare = value.caseInsensitiveCmp(stringValue(other).value);
        return RubyFixnum.newFixnum(getRuntime(), compare);
    }

    /** rb_str_match
     *
     */
    @JRubyMethod(name = "=~")
    @Override
    public IRubyObject op_match(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyRegexp) return ((RubyRegexp) other).op_match(context, this);
        if (other instanceof RubyString) {
            throw context.getRuntime().newTypeError("type mismatch: String given");
        }
        return other.callMethod(context, "=~", this);
    }

    /** rb_str_match2
     *
     */
    @JRubyMethod(name = "~", reads = {LASTLINE, BACKREF}, writes = BACKREF)
    public IRubyObject op_match2(ThreadContext context) {
        return RubyRegexp.newRegexp(context.getRuntime(), value, 0, false).op_match2(context);
    }

    /**
     * String#match(pattern)
     *
     * rb_str_match_m
     *
     * @param pattern Regexp or String
     */
    @JRubyMethod
    public IRubyObject match(ThreadContext context, IRubyObject pattern) {
        return getPattern(pattern, false).callMethod(context, "match", this);
    }

    /** rb_str_capitalize
     *
     */
    @JRubyMethod
    public IRubyObject capitalize(ThreadContext context) {
        RubyString str = strDup(context.getRuntime());
        str.capitalize_bang(context);
        return str;
    }

    /** rb_str_capitalize_bang
     *
     */
    @JRubyMethod(name = "capitalize!")
    public IRubyObject capitalize_bang(ThreadContext context) {        
        if (value.realSize == 0) {
            modifyCheck();
            return context.getRuntime().getNil();
        }
        
        modify();
        
        int s = value.begin;
        int send = s + value.realSize;
        byte[]buf = value.bytes;
        
        
        
        boolean modify = false;
        
        int c = buf[s] & 0xff;
        if (ASCII.isLower(c)) {
            buf[s] = (byte)ASCIIEncoding.asciiToUpper(c);
            modify = true;
        }
        
        while (++s < send) {
            c = (char)(buf[s] & 0xff);
            if (ASCII.isUpper(c)) {
                buf[s] = (byte)ASCIIEncoding.asciiToLower(c);
                modify = true;
            }
        }
        
        if (modify) return this;
        return context.getRuntime().getNil();
    }

    @JRubyMethod(name = ">=")
    public IRubyObject op_ge(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyString) {
            return context.getRuntime().newBoolean(op_cmp((RubyString) other) >= 0);
        }

        return RubyComparable.op_ge(context, this, other);
    }

    @JRubyMethod(name = ">")
    public IRubyObject op_gt(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyString) {
            return context.getRuntime().newBoolean(op_cmp((RubyString) other) > 0);
        }

        return RubyComparable.op_gt(context, this, other);
    }

    @JRubyMethod(name = "<=")
    public IRubyObject op_le(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyString) {
            return context.getRuntime().newBoolean(op_cmp((RubyString) other) <= 0);
        }

        return RubyComparable.op_le(context, this, other);
    }

    @JRubyMethod(name = "<")
    public IRubyObject op_lt(ThreadContext context, IRubyObject other) {
        if (other instanceof RubyString) {
            return context.getRuntime().newBoolean(op_cmp((RubyString) other) < 0);
        }

        return RubyComparable.op_lt(context, this, other);
    }

    @JRubyMethod(name = "eql?")
    public IRubyObject str_eql_p(ThreadContext context, IRubyObject other) {
        if (!(other instanceof RubyString)) return context.getRuntime().getFalse();
        RubyString otherString = (RubyString)other;
        return value.equal(otherString.value) ? context.getRuntime().getTrue() : context.getRuntime().getFalse();
    }

    /** rb_str_upcase
     *
     */
    @JRubyMethod
    public RubyString upcase(ThreadContext context) {
        RubyString str = strDup(context.getRuntime());
        str.upcase_bang(context);
        return str;
    }

    /** rb_str_upcase_bang
     *
     */
    @JRubyMethod(name = "upcase!")
    public IRubyObject upcase_bang(ThreadContext context) {
        if (value.realSize == 0) {
            modifyCheck();
            return context.getRuntime().getNil();
        }

        modify();

        int s = value.begin;
        int send = s + value.realSize;
        byte []buf = value.bytes;

        boolean modify = false;
        while (s < send) {
            int c = buf[s] & 0xff;
            if (ASCII.isLower(c)) {
                buf[s] = (byte)ASCIIEncoding.asciiToUpper(c);
                modify = true;
            }
            s++;
        }

        if (modify) return this;
        return context.getRuntime().getNil();        
    }

    /** rb_str_downcase
     *
     */
    @JRubyMethod
    public RubyString downcase(ThreadContext context) {
        RubyString str = strDup(context.getRuntime());
        str.downcase_bang(context);
        return str;
    }

    /** rb_str_downcase_bang
     *
     */
    @JRubyMethod(name = "downcase!")
    public IRubyObject downcase_bang(ThreadContext context) {
        if (value.realSize == 0) {
            modifyCheck();
            return context.getRuntime().getNil();
        }
        
        modify();
        
        int s = value.begin;
        int send = s + value.realSize;
        byte []buf = value.bytes;
        
        boolean modify = false;
        while (s < send) {
            int c = buf[s] & 0xff;
            if (ASCII.isUpper(c)) {
                buf[s] = (byte)ASCIIEncoding.asciiToLower(c);
                modify = true;
            }
            s++;
        }
        
        if (modify) return this;
        return context.getRuntime().getNil();
    }

    /** rb_str_swapcase
     *
     */
    @JRubyMethod
    public RubyString swapcase(ThreadContext context) {
        RubyString str = strDup(context.getRuntime());
        str.swapcase_bang(context);
        return str;
    }

    /** rb_str_swapcase_bang
     *
     */
    @JRubyMethod(name = "swapcase!")
    public IRubyObject swapcase_bang(ThreadContext context) {
        if (value.realSize == 0) {
            modifyCheck();
            return context.getRuntime().getNil();        
        }
        
        modify();
        
        int s = value.begin;
        int send = s + value.realSize;
        byte[]buf = value.bytes;
        
        boolean modify = false;
        while (s < send) {
            int c = buf[s] & 0xff;
            if (ASCII.isUpper(c)) {
                buf[s] = (byte)ASCIIEncoding.asciiToLower(c);
                modify = true;
            } else if (ASCII.isLower(c)) {
                buf[s] = (byte)ASCIIEncoding.asciiToUpper(c);
                modify = true;
            }
            s++;
        }

        if (modify) return this;
        return context.getRuntime().getNil();
    }

    /** rb_str_dump
     *
     */
    @JRubyMethod
    public IRubyObject dump() {
        RubyString s = new RubyString(getRuntime(), getMetaClass(), inspectIntoByteList(true));
        s.infectBy(this);
        return s;
    }

    @JRubyMethod
    public IRubyObject insert(ThreadContext context, IRubyObject indexArg, IRubyObject stringArg) {
        // MRI behavior: first check for ability to convert to String...
        RubyString s = (RubyString)stringArg.convertToString();
        ByteList insert = s.value;

        // ... and then the index
        int index = (int) indexArg.convertToInteger().getLongValue();
        if (index < 0) index += value.length() + 1;

        if (index < 0 || index > value.length()) {
            throw context.getRuntime().newIndexError("index " + index + " out of range");
        }

        modify();

        value.unsafeReplace(index, 0, insert);
        this.infectBy(s);
        return this;
    }

    /** rb_str_inspect
     *
     */
    @JRubyMethod
    @Override
    public IRubyObject inspect() {
        RubyString s = getRuntime().newString(inspectIntoByteList(false));
        s.infectBy(this);
        return s;
    }
    
    private ByteList inspectIntoByteList(boolean ignoreKCode) {
        Ruby runtime = getRuntime();
        Encoding enc = runtime.getKCode().getEncoding();
        final int length = value.length();
        ByteList sb = new ByteList(length + 2 + length / 100);

        sb.append('\"');

        for (int i = 0; i < length; i++) {
            int c = value.get(i) & 0xFF;
            
            if (!ignoreKCode) {
                int seqLength = enc.length((byte)c);
                
                if (seqLength > 1 && (i + seqLength -1 < length)) {
                    // don't escape multi-byte characters, leave them as bytes
                    sb.append(value, i, seqLength);
                    i += seqLength - 1;
                    continue;
                }
            } 
            
            if (isAlnum(c)) {
                sb.append((char)c);
            } else if (c == '\"' || c == '\\') {
                sb.append('\\').append((char)c);
            } else if (c == '#' && isEVStr(i, length)) {
                sb.append('\\').append((char)c);
            } else if (isPrint(c)) {
                sb.append((char)c);
            } else if (c == '\n') {
                sb.append('\\').append('n');
            } else if (c == '\r') {
                sb.append('\\').append('r');
            } else if (c == '\t') {
                sb.append('\\').append('t');
            } else if (c == '\f') {
                sb.append('\\').append('f');
            } else if (c == '\u000B') {
                sb.append('\\').append('v');
            } else if (c == '\u0007') {
                sb.append('\\').append('a');
            } else if (c == '\u0008') {
                sb.append('\\').append('b');
            } else if (c == '\u001B') {
                sb.append('\\').append('e');
            } else {
                sb.append(ByteList.plain(Sprintf.sprintf(runtime,"\\%03o",c)));
            }
        }

        sb.append('\"');
        return sb;
    }
    
    private boolean isEVStr(int i, int length) {
        if (i+1 >= length) return false;
        int c = value.get(i+1) & 0xFF;
        
        return c == '$' || c == '@' || c == '{';
    }

    /** rb_str_length
     *
     */
    @JRubyMethod(name = {"length", "size"})
    public RubyFixnum length() {
        return getRuntime().newFixnum(value.length());
    }

    /** rb_str_empty
     *
     */
    @JRubyMethod(name = "empty?")
    public RubyBoolean empty_p(ThreadContext context) {
        return isEmpty() ? context.getRuntime().getTrue() : context.getRuntime().getFalse();
    }

    public boolean isEmpty() {
        return value.length() == 0;
    }

    /** rb_str_append
     *
     */
    public RubyString append(IRubyObject other) {
        infectBy(other);
        return cat(stringValue(other).value);
    }

    /** rb_str_concat
     *
     */
    @JRubyMethod(name = {"concat", "<<"})
    public RubyString concat(IRubyObject other) {
        if (other instanceof RubyFixnum) {
            long value = ((RubyFixnum) other).getLongValue();
            if (value >= 0 && value < 256) return cat((byte) value);
        }
        return append(other);
    }

    /** rb_str_crypt
     *
     */
    @JRubyMethod(name = "crypt")
    public RubyString crypt(ThreadContext context, IRubyObject other) {
        ByteList salt = stringValue(other).getByteList();
        if (salt.realSize < 2) {
            throw context.getRuntime().newArgumentError("salt too short(need >=2 bytes)");
        }

        salt = salt.makeShared(0, 2);
        RubyString s = RubyString.newStringShared(context.getRuntime(), JavaCrypt.crypt(salt, this.getByteList()));
        s.infectBy(this);
        s.infectBy(other);
        return s;
    }

    /* RubyString aka rb_string_value */
    public static RubyString stringValue(IRubyObject object) {
        return (RubyString) (object instanceof RubyString ? object :
            object.convertToString());
    }

    /**
     * Variable-arity version for compatibility. Not bound to Ruby.
     * @deprecated Use the versions with one or two args.
     */
    public IRubyObject sub(ThreadContext context, IRubyObject[] args, Block block) {
        RubyString str = strDup(context.getRuntime());
        str.sub_bang(context, args, block);
        return str;
    }

    /** rb_str_sub
     *
     */
    @JRubyMethod(name = "sub", frame = true)
    public IRubyObject sub(ThreadContext context, IRubyObject arg0, Block block) {
        RubyString str = strDup(context.getRuntime());
        str.sub_bang(context, arg0, block);
        return str;
    }

    /** rb_str_sub
     *
     */
    @JRubyMethod(name = "sub", frame = true)
    public IRubyObject sub(ThreadContext context, IRubyObject arg0, IRubyObject arg1, Block block) {
        RubyString str = strDup(context.getRuntime());
        str.sub_bang(context, arg0, arg1, block);
        return str;
    }

    /**
     * Variable-arity version for compatibility. Not bound to Ruby.
     * @deprecated Use the versions with one or two arguments.
     */
    public IRubyObject sub_bang(ThreadContext context, IRubyObject[] args, Block block) {
        switch (args.length) {
        case 1:
            return sub_bang(context, args[0], block);
        case 2:
            return sub_bang(context, args[0], args[1], block);
        default:
            Arity.raiseArgumentError(context.getRuntime(), args.length, 1, 2);
            return null; // not reached
        }
    }

    /** rb_str_sub_bang
     *
     */
    @JRubyMethod(name = "sub!", frame = true, reads = BACKREF, writes = BACKREF)
    public IRubyObject sub_bang(ThreadContext context, IRubyObject arg0, Block block) {
        if (block.isGiven()) {
            RubyRegexp rubyRegex = getPattern(arg0, true);
            Regex regex = rubyRegex.getPattern();
            return subBangCommon(regex, context, true, rubyRegex, block, null, false);
        } else {
            throw context.getRuntime().newArgumentError("wrong number of arguments (1 for 2)");
        }
    }

    /** rb_str_sub_bang
     *
     */
    @JRubyMethod(name = "sub!", frame = true, reads = BACKREF, writes = BACKREF)
    public IRubyObject sub_bang(ThreadContext context, IRubyObject arg0, IRubyObject arg1, Block block) {
        RubyString repl = arg1.convertToString();
        RubyRegexp rubyRegex = getPattern(arg0, true);
        Regex regex = rubyRegex.getPattern();
        return subBangCommon(regex, context, false, rubyRegex, block, repl, repl.isTaint());
    }

    private IRubyObject subBangCommon(Regex regex, ThreadContext context, final boolean iter, RubyRegexp rubyRegex, Block block, RubyString repl, boolean tainted) {

        int range = value.begin + value.realSize;
        Matcher matcher = regex.matcher(value.bytes, value.begin, range);

        Frame frame = context.getPreviousFrame();
        if (matcher.search(value.begin, range, Option.NONE) >= 0) {
            if (iter) {
                byte[] bytes = value.bytes;
                int size = value.realSize;
                RubyMatchData match = rubyRegex.updateBackRef(context, this, frame, matcher);
                match.use();
                if (regex.numberOfCaptures() == 0) {
                    repl = objAsString(context, block.yield(context, substr(matcher.getBegin(), matcher.getEnd() - matcher.getBegin())));
                } else {
                    Region region = matcher.getRegion();
                    repl = objAsString(context, block.yield(context, substr(region.beg[0], region.end[0] - region.beg[0])));
                }
                modifyCheck(bytes, size);
                frozenCheck();
                frame.setBackRef(match);
            } else {
                repl = rubyRegex.regsub(repl, this, matcher);
                rubyRegex.updateBackRef(context, this, frame, matcher);
            }

            final int beg;
            final int plen;
            if (regex.numberOfCaptures() == 0) {
                beg = matcher.getBegin();
                plen = matcher.getEnd() - beg;
            } else {
                Region region = matcher.getRegion();
                beg = region.beg[0];
                plen = region.end[0] - beg;
            }

            ByteList replValue = repl.value;
            if (replValue.realSize > plen) {
                modify(value.realSize + replValue.realSize - plen);
            } else {
                modify();
            }
            if (repl.isTaint()) {
                tainted = true;
            }
            if (replValue.realSize != plen) {
                int src = value.begin + beg + plen;
                int dst = value.begin + beg + replValue.realSize;
                int length = value.realSize - beg - plen;
                System.arraycopy(value.bytes, src, value.bytes, dst, length);
            }
            System.arraycopy(replValue.bytes, replValue.begin, value.bytes, value.begin + beg, replValue.realSize);
            value.realSize += replValue.realSize - plen;
            if (tainted) {
                setTaint(true);
            }
            return this;
        } else {
            frame.setBackRef(context.getRuntime().getNil());
            return context.getRuntime().getNil();
        }
    }

    /**
     * Variable-arity version for compatibility. Not bound to Ruby.
     * @deprecated Use the versions with one or two arguments.
     */
    public IRubyObject gsub(ThreadContext context, IRubyObject[] args, Block block) {
        switch (args.length) {
        case 1:
            return gsub(context, args[0], block);
        case 2:
            return gsub(context, args[0], args[1], block);
        default:
            Arity.raiseArgumentError(context.getRuntime(), args.length, 1, 2);
            return null; // not reached
        }
    }
    
    /** rb_str_gsub
     *
     */
    @JRubyMethod(name = "gsub", frame = true, reads = BACKREF, writes = BACKREF)
    public IRubyObject gsub(ThreadContext context, IRubyObject arg0, Block block) {
        return gsub(context, arg0, block, false);
    }
    
    /** rb_str_gsub
     *
     */
    @JRubyMethod(name = "gsub", frame = true, reads = BACKREF, writes = BACKREF)
    public IRubyObject gsub(ThreadContext context, IRubyObject arg0, IRubyObject arg1, Block block) {
        return gsub(context, arg0, arg1, block, false);
    }

    /**
     * Variable-arity version for compatibility. Not bound to Ruby.
     * @deprecated Use the versions with one or two arguments.
     */
    public IRubyObject gsub_bang(ThreadContext context, IRubyObject[] args, Block block) {
        switch (args.length) {
        case 1:
            return gsub_bang(context, args[0], block);
        case 2:
            return gsub_bang(context, args[0], args[1], block);
        default:
            Arity.raiseArgumentError(context.getRuntime(), args.length, 1, 2);
            return null; // not reached
        }
    }

    /** rb_str_gsub_bang
     *
     */
    @JRubyMethod(name = "gsub!", frame = true, reads = BACKREF, writes = BACKREF)
    public IRubyObject gsub_bang(ThreadContext context, IRubyObject arg0, Block block) {
        return gsub(context, arg0, block, true);
    }

    /** rb_str_gsub_bang
     *
     */
    @JRubyMethod(name = "gsub!", frame = true, reads = BACKREF, writes = BACKREF)
    public IRubyObject gsub_bang(ThreadContext context, IRubyObject arg0, IRubyObject arg1, Block block) {
        return gsub(context, arg0, arg1, block, true);
    }

    private final IRubyObject gsub(ThreadContext context, IRubyObject arg0, Block block, final boolean bang) {
        if (block.isGiven()) {
            RubyRegexp rubyRegex = getPattern(arg0, true);
            Regex regex = rubyRegex.getPattern();
            return gsubCommon(regex, context, bang, true, rubyRegex, block, null, false);
        } else {
            throw context.getRuntime().newArgumentError("wrong number of arguments (1 for 2)");
        }
    }

    private final IRubyObject gsub(ThreadContext context, IRubyObject arg0, IRubyObject arg1, Block block, final boolean bang) {
        IRubyObject repl = arg1.convertToString();
        RubyRegexp rubyRegex = getPattern(arg0, true);
        Regex regex = rubyRegex.getPattern();
        return gsubCommon(regex, context, bang, false, rubyRegex, block, repl, repl.isTaint());
    }

    private IRubyObject gsubCommon(Regex regex, ThreadContext context, final boolean bang, final boolean iter, RubyRegexp rubyRegex, Block block, IRubyObject repl, boolean tainted) {

        int begin = value.begin;
        int range = begin + value.realSize;
        Matcher matcher = regex.matcher(value.bytes, begin, range);

        int beg = matcher.search(begin, range, Option.NONE);

        Frame frame = context.getPreviousFrame();

        if (beg < 0) {
            frame.setBackRef(context.getRuntime().getNil());
            return bang ? context.getRuntime().getNil() : strDup(context.getRuntime()); /* bang: true, no match, no substitution */
        }

        int blen = value.realSize + 30; /* len + margin */
        ByteList dest = new ByteList(blen);
        dest.realSize = blen;
        int buf = 0;
        int bp = 0;
        int cp = value.begin;

        int offset = 0;
        RubyString val;

        RubyMatchData match = null;
        while (beg >= 0) {
            final int begz;
            final int endz;
            if (iter) {
                byte[] bytes = value.bytes;
                int size = value.realSize;
                match = rubyRegex.updateBackRef(context, this, frame, matcher);
                match.use();
                if (regex.numberOfCaptures() == 0) {
                    begz = matcher.getBegin();
                    endz = matcher.getEnd();
                    val = objAsString(context, block.yield(context, substr(context.getRuntime(), begz, endz - begz)));
                } else {
                    Region region = matcher.getRegion();
                    begz = region.beg[0];
                    endz = region.end[0];
                    val = objAsString(context, block.yield(context, substr(context.getRuntime(), begz, endz - begz)));
                }
                modifyCheck(bytes, size);
                if (bang) {
                    frozenCheck();
                }
            } else {
                val = rubyRegex.regsub((RubyString) repl, this, matcher);
                if (regex.numberOfCaptures() == 0) {
                    begz = matcher.getBegin();
                    endz = matcher.getEnd();
                } else {
                    Region region = matcher.getRegion();
                    begz = region.beg[0];
                    endz = region.end[0];
                }
            }

            if (val.isTaint()) {
                tainted = true;
            }
            ByteList vbuf = val.value;
            int len = (bp - buf) + (beg - offset) + vbuf.realSize + 3;
            if (blen < len) {
                while (blen < len) {
                    blen <<= 1;
                }
                len = bp - buf;
                dest.realloc(blen);
                dest.realSize = blen;
                bp = buf + len;
            }
            len = beg - offset; /* copy pre-match substr */
            System.arraycopy(value.bytes, cp, dest.bytes, bp, len);
            bp += len;
            System.arraycopy(vbuf.bytes, vbuf.begin, dest.bytes, bp, vbuf.realSize);
            bp += vbuf.realSize;
            offset = endz;

            if (begz == endz) {
                if (value.realSize <= endz) {
                    break;
                }
                len = regex.getEncoding().length(value.bytes[begin + endz]);
                System.arraycopy(value.bytes, begin + endz, dest.bytes, bp, len);
                bp += len;
                offset = endz + len;
            }
            cp = begin + offset;
            if (offset > value.realSize) {
                break;
            }
            beg = matcher.search(cp, range, Option.NONE);
        }

        if (value.realSize > offset) {
            int len = bp - buf;
            if (blen - len < value.realSize - offset) {
                blen = len + value.realSize - offset;
                dest.realloc(blen);
                bp = buf + len;
            }
            System.arraycopy(value.bytes, cp, dest.bytes, bp, value.realSize - offset);
            bp += value.realSize - offset;
        }

        if (match != null) {
            frame.setBackRef(match);
        } else {
            rubyRegex.updateBackRef(context, this, frame, matcher);
        }

        dest.realSize = bp - buf;
        if (bang) {
            view(dest);
            if (tainted) {
                setTaint(true);
            }
            return this;
        } else {
            RubyString destStr = new RubyString(context.getRuntime(), getMetaClass(), dest);
            destStr.infectBy(this);
            if (tainted) {
                destStr.setTaint(true);
            }
            return destStr;
        }
    }

    /**
     * Variable-arity version for compatibility. Not bound to Ruby.
     * @deprecated Use the versions with one or two args.
     */
    public IRubyObject index(ThreadContext context, IRubyObject[] args) {
        switch (args.length) {
        case 1:
            return index(context, args[0]);
        case 2:
            return index(context, args[0], args[1]);
        default:
            Arity.raiseArgumentError(context.getRuntime(), args.length, 1, 2);
            return null; // not reached
        }
    }

    /** rb_str_index_m
     *
     */
    @JRubyMethod(reads = BACKREF, writes = BACKREF)
    public IRubyObject index(ThreadContext context, IRubyObject arg0) {
        return indexCommon(0, arg0, context);
    }

    /** rb_str_index_m
     *
     */
    @JRubyMethod(reads = BACKREF, writes = BACKREF)
    public IRubyObject index(ThreadContext context, IRubyObject arg0, IRubyObject arg1) {
        int pos = RubyNumeric.num2int(arg1);

        if (pos < 0) {
            pos += value.realSize;
            if (pos < 0) {
                if (arg0 instanceof RubyRegexp) {
                    context.getPreviousFrame().setBackRef(context.getRuntime().getNil());
                }
                return context.getRuntime().getNil();
            }
        }

        return indexCommon(pos, arg0, context);
    }

    private IRubyObject indexCommon(int pos, IRubyObject sub, ThreadContext context) throws RaiseException {
        if (sub instanceof RubyRegexp) {
            RubyRegexp regSub = (RubyRegexp) sub;

            pos = regSub.adjustStartPos(this, pos, false);
            pos = regSub.search(context, this, pos, false);
        } else if (sub instanceof RubyFixnum) {
            int c_int = RubyNumeric.fix2int(sub);
            if (c_int < 0x00 || c_int > 0xFF) {
                // out of byte range
                // there will be no match for sure
                return context.getRuntime().getNil();
            }
            byte c = (byte) c_int;
            byte[] bytes = value.bytes;
            int end = value.begin + value.realSize;

            pos += value.begin;
            for (; pos < end; pos++) {
                if (bytes[pos] == c) {
                    return RubyFixnum.newFixnum(context.getRuntime(), pos - value.begin);
                }
            }
            return context.getRuntime().getNil();
        } else if (sub instanceof RubyString) {
            pos = strIndex((RubyString) sub, pos);
        } else {
            IRubyObject tmp = sub.checkStringType();

            if (tmp.isNil()) {
                throw context.getRuntime().newTypeError("type mismatch: " + sub.getMetaClass().getName() + " given");
            }
            pos = strIndex((RubyString) tmp, pos);
        }

        return pos == -1 ? context.getRuntime().getNil() : RubyFixnum.newFixnum(context.getRuntime(), pos);
    }
    
    private int strIndex(RubyString sub, int offset) {
        if (offset < 0) {
            offset += value.realSize;
            if (offset < 0) return -1;
        }
        
        if (value.realSize - offset < sub.value.realSize) return -1;
        if (sub.value.realSize == 0) return offset;
        return value.indexOf(sub.value, offset);
    }

    /**
     * Variable-arity version for compatibility. Not bound to Ruby.
     * @deprecated Use the versions with one or two arguments.
     */
    public IRubyObject rindex(ThreadContext context, IRubyObject[] args) {
        switch (args.length) {
        case 1:
            return rindex(context, args[0]);
        case 2:
            return rindex(context, args[0], args[1]);
        default:
            Arity.raiseArgumentError(context.getRuntime(), args.length, 1, 2);
            return null; // not reached
        }
    }

    /** rb_str_rindex_m
     *
     */
    @JRubyMethod(reads = BACKREF, writes = BACKREF)
    public IRubyObject rindex(ThreadContext context, IRubyObject arg0) {
        return rindexCommon(arg0, value.realSize, context);
    }

    /** rb_str_rindex_m
     *
     */
    @JRubyMethod(reads = BACKREF, writes = BACKREF)
    public IRubyObject rindex(ThreadContext context, IRubyObject arg0, IRubyObject arg1) {
        int pos = RubyNumeric.num2int(arg1);

        if (pos < 0) {
            pos += value.realSize;
            if (pos < 0) {
                if (arg0 instanceof RubyRegexp) { 
                    context.getPreviousFrame().setBackRef(context.getRuntime().getNil());
                }
                return context.getRuntime().getNil();
            }
        }            
        if (pos > value.realSize) pos = value.realSize;

        return rindexCommon(arg0, pos, context);
    }

    private IRubyObject rindexCommon(final IRubyObject sub, int pos, ThreadContext context) throws RaiseException {

        if (sub instanceof RubyRegexp) {
            RubyRegexp regSub = (RubyRegexp) sub;
            if (regSub.length() > 0) {
                pos = regSub.adjustStartPos(this, pos, true);
                pos = regSub.search(context, this, pos, true);
            }
            if (pos >= 0) {
                return RubyFixnum.newFixnum(context.getRuntime(), pos);
            }
        } else if (sub instanceof RubyString) {
            pos = strRindex((RubyString) sub, pos);
            if (pos >= 0) return RubyFixnum.newFixnum(context.getRuntime(), pos);
        } else if (sub instanceof RubyFixnum) {
            int c_int = RubyNumeric.fix2int(sub);
            if (c_int < 0x00 || c_int > 0xFF) {
                // out of byte range
                // there will be no match for sure
                return context.getRuntime().getNil();
            }
            byte c = (byte) c_int;

            byte[] bytes = value.bytes;
            int pbeg = value.begin;
            int p = pbeg + pos;

            if (pos == value.realSize) {
                if (pos == 0) {
                    return context.getRuntime().getNil();
                }
                --p;
            }
            while (pbeg <= p) {
                if (bytes[p] == c) {
                    return RubyFixnum.newFixnum(context.getRuntime(), p - value.begin);
                }
                p--;
            }
            return context.getRuntime().getNil();
        } else {
            IRubyObject tmp = sub.checkStringType();
            if (tmp.isNil()) throw context.getRuntime().newTypeError("type mismatch: " + sub.getMetaClass().getName() + " given");
            pos = strRindex((RubyString) tmp, pos);
            if (pos >= 0) return RubyFixnum.newFixnum(context.getRuntime(), pos);
        }

        return context.getRuntime().getNil();
    }

    private int strRindex(RubyString sub, int pos) {
        int subLength = sub.value.realSize;
        
        /* substring longer than string */
        if (value.realSize < subLength) return -1;
        if (value.realSize - pos < subLength) pos = value.realSize - subLength;

        return value.lastIndexOf(sub.value, pos);
    }
    
    /* rb_str_substr */
    public IRubyObject substr(int beg, int len) {
        return substr(getRuntime(), beg, len);
    }
    
    public IRubyObject substr(Ruby runtime, int beg, int len) {    
        int length = value.length();
        if (len < 0 || beg > length) return getRuntime().getNil();

        if (beg < 0) {
            beg += length;
            if (beg < 0) return getRuntime().getNil();
        }
        
        int end = Math.min(length, beg + len);
        return makeShared(getRuntime(), beg, end - beg);
    }
    
    

    /* rb_str_replace */
    public IRubyObject replace(int beg, int len, RubyString replaceWith) {
        if (beg + len >= value.length()) len = value.length() - beg;

        modify();
        value.unsafeReplace(beg,len,replaceWith.value);

        return infectBy(replaceWith);
    }

    /**
     * Variable-arity version for compatibility. Not bound to Ruby.
     * @deprecated Use the versions with one or two args
     */
    public IRubyObject op_aref(ThreadContext context, IRubyObject[] args) {
        switch (args.length) {
        case 1:
            return op_aref(context, args[0]);
        case 2:
            return op_aref(context, args[0], args[1]);
        default:
            Arity.raiseArgumentError(context.getRuntime(), args.length, 1, 2);
            return null; // not reached
        }
    }

    /** rb_str_aref, rb_str_aref_m
     *
     */
    @JRubyMethod(name = {"[]", "slice"}, reads = BACKREF, writes = BACKREF)
    public IRubyObject op_aref(ThreadContext context, IRubyObject arg1, IRubyObject arg2) {
        if (arg1 instanceof RubyRegexp) {
            if(((RubyRegexp)arg1).search(context, this, 0, false) >= 0) {
                return RubyRegexp.nth_match(RubyNumeric.fix2int(arg2), context.getCurrentFrame().getBackRef());
            }
            return context.getRuntime().getNil();
        }
        return substr(context.getRuntime(), RubyNumeric.fix2int(arg1), RubyNumeric.fix2int(arg2));
    }

    /** rb_str_aref, rb_str_aref_m
     *
     */
    @JRubyMethod(name = {"[]", "slice"}, reads = BACKREF, writes = BACKREF)
    public IRubyObject op_aref(ThreadContext context, IRubyObject arg) {
        if (arg instanceof RubyRegexp) {
            if(((RubyRegexp)arg).search(context, this, 0, false) >= 0) {
                return RubyRegexp.nth_match(0, context.getCurrentFrame().getBackRef());
            }
            return context.getRuntime().getNil();
        } else if (arg instanceof RubyString) {
            return value.indexOf(stringValue(arg).value) != -1 ?
                arg : context.getRuntime().getNil();
        } else if (arg instanceof RubyRange) {
            long[] begLen = ((RubyRange) arg).begLen(value.length(), 0);
            return begLen == null ? context.getRuntime().getNil() :
                substr(context.getRuntime(), (int) begLen[0], (int) begLen[1]);
        }
        int idx = (int) arg.convertToInteger().getLongValue();
        
        if (idx < 0) idx += value.length();
        if (idx < 0 || idx >= value.length()) return context.getRuntime().getNil();

        return context.getRuntime().newFixnum(value.get(idx) & 0xFF);
    }

    /**
     * rb_str_subpat_set
     *
     */
    private void subpatSet(ThreadContext context, RubyRegexp regexp, int nth, IRubyObject repl) {
        RubyMatchData match;
        int start, end, len;        
        if (regexp.search(context, this, 0, false) < 0) throw context.getRuntime().newIndexError("regexp not matched");

        match = (RubyMatchData)context.getCurrentFrame().getBackRef();

        if (match.regs == null) {
            if (nth >= 1) throw context.getRuntime().newIndexError("index " + nth + " out of regexp");
            if (nth < 0) {
                if(-nth >= 1) throw context.getRuntime().newIndexError("index " + nth + " out of regexp");
                nth += 1;
            }
            start = match.begin;
            if(start == -1) throw context.getRuntime().newIndexError("regexp group " + nth + " not matched");
            end = match.end;
        } else {
            if(nth >= match.regs.numRegs) throw context.getRuntime().newIndexError("index " + nth + " out of regexp");
            if(nth < 0) {
                if(-nth >= match.regs.numRegs) throw context.getRuntime().newIndexError("index " + nth + " out of regexp");
                nth += match.regs.numRegs;
            }
            start = match.regs.beg[nth];
            if(start == -1) throw context.getRuntime().newIndexError("regexp group " + nth + " not matched");
            end = match.regs.end[nth];
        }
        
        len = end - start;
        replace(start, len, stringValue(repl));
    }

    /**
     * Variable arity version for compatibility. Not bound to a Ruby method.
     * @deprecated Use the versions with two or three args.
     */
    public IRubyObject op_aset(ThreadContext context, IRubyObject[] args) {
        switch (args.length) {
        case 2:
            return op_aset(context, args[0], args[1]);
        case 3:
            return op_aset(context, args[0], args[1], args[2]);
        default:
            Arity.raiseArgumentError(context.getRuntime(), args.length, 2, 3);
            return null; // not reached
        }
    }

    /** rb_str_aset, rb_str_aset_m
     *
     */
    @JRubyMethod(name = "[]=", reads = BACKREF)
    public IRubyObject op_aset(ThreadContext context, IRubyObject arg0, IRubyObject arg1) {
        if (arg0 instanceof RubyFixnum || arg0.respondsTo("to_int")) { // FIXME: RubyNumeric or RubyInteger instead?
            int idx = RubyNumeric.fix2int(arg0);
            
            if (idx < 0) idx += value.length();

            if (idx < 0 || idx >= value.length()) {
                throw context.getRuntime().newIndexError("string index out of bounds");
            }
            if (arg1 instanceof RubyFixnum) {
                modify();
                value.set(idx, (byte) RubyNumeric.fix2int(arg1));
            } else {
                replace(idx, 1, stringValue(arg1));
            }
            return arg1;
        }
        if (arg0 instanceof RubyRegexp) {
            RubyString repl = stringValue(arg1);
            subpatSet(context, (RubyRegexp) arg0, 0, repl);
            return repl;
        }
        if (arg0 instanceof RubyString) {
            RubyString orig = (RubyString)arg0;
            int beg = value.indexOf(orig.value);
            if (beg < 0) throw context.getRuntime().newIndexError("string not matched");
            replace(beg, orig.value.length(), stringValue(arg1));
            return arg1;
        }
        if (arg0 instanceof RubyRange) {
            long[] begLen = ((RubyRange) arg0).begLen(value.realSize, 2);
            replace((int) begLen[0], (int) begLen[1], stringValue(arg1));
            return arg1;
        }
        throw context.getRuntime().newTypeError("wrong argument type");
    }

    /** rb_str_aset, rb_str_aset_m
     *
     */
    @JRubyMethod(name = "[]=", reads = BACKREF)
    public IRubyObject op_aset(ThreadContext context, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) {
        if (arg0 instanceof RubyRegexp) {
            RubyString repl = stringValue(arg2);
            int nth = RubyNumeric.fix2int(arg1);
            subpatSet(context, (RubyRegexp) arg0, nth, repl);
            return repl;
        }
        RubyString repl = stringValue(arg2);
        int beg = RubyNumeric.fix2int(arg0);
        int len = RubyNumeric.fix2int(arg1);
        if (len < 0) throw context.getRuntime().newIndexError("negative length");
        int strLen = value.length();
        if (beg < 0) beg += strLen;

        if (beg < 0 || (beg > 0 && beg > strLen)) {
            throw context.getRuntime().newIndexError("string index out of bounds");
        }
        if (beg + len > strLen) len = strLen - beg;

        replace(beg, len, repl);
        return repl;
    }

    /**
     * Variable arity version for compatibility. Not bound as a Ruby method.
     * @deprecated Use the versions with one or two args.
     */
    public IRubyObject slice_bang(ThreadContext context, IRubyObject[] args) {
        switch (args.length) {
        case 1:
            return slice_bang(context, args[0]);
        case 2:
            return slice_bang(context, args[0], args[1]);
        default:
            Arity.raiseArgumentError(context.getRuntime(), args.length, 1, 2);
            return null; // not reached
        }
    }

    /** rb_str_slice_bang
     *
     */
    @JRubyMethod(name = "slice!", reads = BACKREF, writes = BACKREF)
    public IRubyObject slice_bang(ThreadContext context, IRubyObject arg0) {
        IRubyObject result = op_aref(context, arg0);
        if (result.isNil()) return result;

        op_aset(context, arg0, RubyString.newEmptyString(context.getRuntime()));
        return result;
    }

    /** rb_str_slice_bang
     *
     */
    @JRubyMethod(name = "slice!", reads = BACKREF, writes = BACKREF)
    public IRubyObject slice_bang(ThreadContext context, IRubyObject arg0, IRubyObject arg1) {
        IRubyObject result = op_aref(context, arg0, arg1);
        if (result.isNil()) return result;

        op_aset(context, arg0, arg1, RubyString.newEmptyString(context.getRuntime()));
        return result;
    }

    @JRubyMethod(name = {"succ", "next"})
    public IRubyObject succ(ThreadContext context) {
        RubyString str = strDup(context.getRuntime());
        str.succ_bang();
        return str;
    }

    @JRubyMethod(name = {"succ!", "next!"})
    public IRubyObject succ_bang() {
        if (value.length() == 0) {
            modifyCheck();
            return this;
        }

        modify();
        
        boolean alnumSeen = false;
        int pos = -1;
        int c = 0;
        int n = 0;
        for (int i = value.length() - 1; i >= 0; i--) {
            c = value.get(i) & 0xFF;
            if (isAlnum(c)) {
                alnumSeen = true;
                if ((isDigit(c) && c < '9') || (isLower(c) && c < 'z') || (isUpper(c) && c < 'Z')) {
                    value.set(i, (byte)(c + 1));
                    pos = -1;
                    break;
                }
                pos = i;
                n = isDigit(c) ? '1' : (isLower(c) ? 'a' : 'A');
                value.set(i, (byte)(isDigit(c) ? '0' : (isLower(c) ? 'a' : 'A')));
            }
        }
        if (!alnumSeen) {
            for (int i = value.length() - 1; i >= 0; i--) {
                c = value.get(i) & 0xFF;
                if (c < 0xff) {
                    value.set(i, (byte)(c + 1));
                    pos = -1;
                    break;
                }
                pos = i;
                n = '\u0001';
                value.set(i, 0);
            }
        }
        if (pos > -1) {
            // This represents left most digit in a set of incremented
            // values?  Therefore leftmost numeric must be '1' and not '0'
            // 999 -> 1000, not 999 -> 0000.  whereas chars should be
            // zzz -> aaaa and non-alnum byte values should be "\377" -> "\001\000"
            value.insert(pos, (byte) n);
        }
        return this;
    }

    /** rb_str_upto_m
     *
     */
    @JRubyMethod(name = "upto", required = 1, frame = true)
    public IRubyObject upto(ThreadContext context, IRubyObject str, Block block) {
        return upto(context, str, false, block);
    }

    /* rb_str_upto */
    public IRubyObject upto(ThreadContext context, IRubyObject str, boolean excl, Block block) {
        RubyString end = str.convertToString();

        int n = value.cmp(end.value);
        if (n > 0 || (excl && n == 0)) return this;

        IRubyObject afterEnd = end.callMethod(context, "succ");
        RubyString current = this;

        while (!current.op_equal(context, afterEnd).isTrue()) {
            block.yield(context, current);            
            if (!excl && current.op_equal(context, end).isTrue()) break;
            current = current.callMethod(context, "succ").convertToString();
            if (excl && current.op_equal(context, end).isTrue()) break;
            if (current.value.realSize > end.value.realSize || current.value.realSize == 0) break;
        }

        return this;
    }

    /** rb_str_include
     *
     */
    @JRubyMethod(name = "include?", required = 1)
    public RubyBoolean include_p(ThreadContext context, IRubyObject obj) {
        if (obj instanceof RubyFixnum) {
            int c = RubyNumeric.fix2int(obj);
            for (int i = 0; i < value.length(); i++) {
                if (value.get(i) == (byte)c) {
                    return context.getRuntime().getTrue();
                }
            }
            return context.getRuntime().getFalse();
        }
        ByteList str = stringValue(obj).value;
        return context.getRuntime().newBoolean(value.indexOf(str) != -1);
    }

    /**
     * Variable-arity version for compatibility. Not bound as a Ruby method.
     * @deprecated Use the versions with zero or one args.
     */
    public IRubyObject to_i(IRubyObject[] args) {
        switch (args.length) {
        case 0:
            return to_i();
        case 1:
            return to_i(args[0]);
        default:
            Arity.raiseArgumentError(getRuntime(), args.length, 0, 1);
            return null; // not reached
        }
    }

    /** rb_str_to_i
     *
     */
    @JRubyMethod(name = "to_i")
    public IRubyObject to_i() {
        return RubyNumeric.str2inum(getRuntime(), this, 10);
    }

    /** rb_str_to_i
     *
     */
    @JRubyMethod(name = "to_i")
    public IRubyObject to_i(IRubyObject arg0) {
        long base = arg0.convertToInteger().getLongValue();
        return RubyNumeric.str2inum(getRuntime(), this, (int) base);
    }

    /** rb_str_oct
     *
     */
    @JRubyMethod(name = "oct")
    public IRubyObject oct(ThreadContext context) {
        if (isEmpty()) return context.getRuntime().newFixnum(0);

        int base = 8;

        int ix = value.begin;

        while(ix < value.begin+value.realSize && ASCII.isSpace(value.bytes[ix] & 0xff)) {
            ix++;
        }

        int pos = (value.bytes[ix] == '-' || value.bytes[ix] == '+') ? ix+1 : ix;
        if((pos+1) < value.begin+value.realSize && value.bytes[pos] == '0') {
            if(value.bytes[pos+1] == 'x' || value.bytes[pos+1] == 'X') {
                base = 16;
            } else if(value.bytes[pos+1] == 'b' || value.bytes[pos+1] == 'B') {
                base = 2;
            } else if(value.bytes[pos+1] == 'd' || value.bytes[pos+1] == 'D') {
                base = 10;
            }
        }
        return RubyNumeric.str2inum(context.getRuntime(), this, base);
    }

    /** rb_str_hex
     *
     */
    @JRubyMethod(name = "hex")
    public IRubyObject hex(ThreadContext context) {
        return RubyNumeric.str2inum(context.getRuntime(), this, 16);
    }

    /** rb_str_to_f
     *
     */
    @JRubyMethod(name = "to_f")
    public IRubyObject to_f() {
        return RubyNumeric.str2fnum(getRuntime(), this);
    }

    /**
     * Variable arity version for compatibility. Not bound to a Ruby method.
     * @deprecated Use the versions with zero, one, or two args.
     */
    public RubyArray split(ThreadContext context, IRubyObject[] args) {
        switch (args.length) {
        case 0:
            return split(context);
        case 1:
            return split(context, args[0]);
        case 2:
            return split(context, args[0], args[1]);
        default:
            Arity.raiseArgumentError(context.getRuntime(), args.length, 0, 2);
            return null; // not reached
        }
    }

    /** rb_str_split_m
     *
     */
    @JRubyMethod(writes = BACKREF)
    public RubyArray split(ThreadContext context) {
        return split(context, context.getRuntime().getNil());
    }

    /** rb_str_split_m
     *
     */
    @JRubyMethod(writes = BACKREF)
    public RubyArray split(ThreadContext context, IRubyObject arg0) {
        return splitCommon(arg0, false, 0, 0, context);
    }

    /** rb_str_split_m
     *
     */
    @JRubyMethod(writes = BACKREF)
    public RubyArray split(ThreadContext context, IRubyObject arg0, IRubyObject arg1) {
        final int lim = RubyNumeric.fix2int(arg1);
        if (lim <= 0) {
            return splitCommon(arg0, false, lim, 1, context);
        } else {
            if (lim == 1) return value.realSize == 0 ? context.getRuntime().newArray() : context.getRuntime().newArray(this);
            return splitCommon(arg0, true, lim, 1, context);
        }
    }

    private RubyArray splitCommon(IRubyObject spat, final boolean limit, final int lim, final int i, ThreadContext context) {
        final RubyArray result;
        if (spat.isNil() && (spat = context.getRuntime().getGlobalVariables().get("$;")).isNil()) {
            result = awkSplit(limit, lim, i);
        } else {
            if (spat instanceof RubyString && ((RubyString) spat).value.realSize == 1) {
                RubyString strSpat = (RubyString) spat;
                if (strSpat.value.bytes[strSpat.value.begin] == (byte) ' ') {
                    result = awkSplit(limit, lim, i);
                } else {
                    result = split(context, spat, limit, lim, i);
                }
            } else {
                result = split(context, spat, limit, lim, i);
            }
        }

        if (!limit && lim == 0) {
            while (result.size() > 0 && ((RubyString) result.eltInternal(result.size() - 1)).value.realSize == 0) {
                result.pop();
            }
        }

        return result;
    }
    
    private RubyArray split(ThreadContext context, IRubyObject pat, boolean limit, int lim, int i) {
        Ruby runtime = context.getRuntime();
        
        final Regex regex = getPattern(pat, true).getPattern();
        int beg, end, start;
        
        int begin = value.begin;
        start = begin;
        beg = 0;
        
        int range = value.begin + value.realSize;
        final Matcher matcher = regex.matcher(value.bytes, value.begin, range);
        
        boolean lastNull = false;
        RubyArray result = runtime.newArray();
        if (regex.numberOfCaptures() == 0) { // shorter path, no captures defined, no region will be returned 
            while ((end = matcher.search(start, range, Option.NONE)) >= 0) {
                if (start == end + begin && matcher.getBegin() == matcher.getEnd()) {
                    if (value.realSize == 0) {
                        result.append(newEmptyString(runtime, getMetaClass()));
                        break;
                    } else if (lastNull) {
                        result.append(substr(runtime, beg, regex.getEncoding().length(value.bytes[begin + beg])));
                        beg = start - begin;
                    } else {
                        if (start == range) {
                            start++;
                        } else {
                            start += regex.getEncoding().length(value.bytes[start]);
                        }
                        lastNull = true;
                        continue;
                    }
                } else {
                    result.append(substr(beg, end - beg));
                    beg = matcher.getEnd();
                    start = begin + matcher.getEnd();
                }
                lastNull = false;
                if (limit && lim <= ++i) break;
            }
        } else {
            while ((end = matcher.search(start, range, Option.NONE)) >= 0) {
                final Region region = matcher.getRegion();
                if (start == end + begin && region.beg[0] == region.end[0]) {
                    if (value.realSize == 0) {                        
                        result.append(newEmptyString(runtime, getMetaClass()));
                        break;
                    } else if (lastNull) {
                        result.append(substr(beg, regex.getEncoding().length(value.bytes[begin + beg])));
                        beg = start - begin;
                    } else {
                        if (start == range) {
                            start++;
                        } else {
                            start += regex.getEncoding().length(value.bytes[start]);
                        }
                        lastNull = true;
                        continue;
                    }                    
                } else {
                    result.append(substr(beg, end - beg));
                    beg = start = region.end[0];
                    start += begin;
                }
                lastNull = false;
                
                for (int idx=1; idx<region.numRegs; idx++) {
                    if (region.beg[idx] == -1) continue;
                    if (region.beg[idx] == region.end[idx]) {
                        result.append(newEmptyString(runtime, getMetaClass()));
                    } else {
                        result.append(substr(region.beg[idx], region.end[idx] - region.beg[idx]));
                    }
                }
                if (limit && lim <= ++i) break;
            }
        }
        
        // only this case affects backrefs 
        context.getCurrentFrame().setBackRef(runtime.getNil());
        
        if (value.realSize > 0 && (limit || value.realSize > beg || lim < 0)) {
            if (value.realSize == beg) {
                result.append(newEmptyString(runtime, getMetaClass()));
            } else {
                result.append(substr(beg, value.realSize - beg));
            }
        }
        
        return result;
    }
    
    private RubyArray awkSplit(boolean limit, int lim, int i) {
        Ruby runtime = getRuntime();
        RubyArray result = runtime.newArray();
        
        byte[]bytes = value.bytes;
        int p = value.begin; 
        int endp = p + value.realSize;
                    
        boolean skip = true;
        
        int end, beg = 0;        
        for (end = beg = 0; p < endp; p++) {
            if (skip) {
                if (ASCII.isSpace(bytes[p] & 0xff)) {
                    beg++;
                } else {
                    end = beg + 1;
                    skip = false;
                    if (limit && lim <= i) break;
                }
            } else {
                if (ASCII.isSpace(bytes[p] & 0xff)) {
                    result.append(makeShared(runtime, beg, end - beg));
                    skip = true;
                    beg = end + 1;
                    if (limit) i++;
                } else {
                    end++;
                }
            }
        }
        
        if (value.realSize > 0 && (limit || value.realSize > beg || lim < 0)) {
            if (value.realSize == beg) {
                result.append(newEmptyString(runtime, getMetaClass()));
            } else {
                result.append(makeShared(runtime, beg, value.realSize - beg));
            }
        }
        return result;
    }

    /** get_pat
     * 
     */
    private final RubyRegexp getPattern(IRubyObject obj, boolean quote) {
        if (obj instanceof RubyRegexp) {
            return (RubyRegexp)obj;
        } else if (!(obj instanceof RubyString)) {
            IRubyObject val = obj.checkStringType();
            if (val.isNil()) throw getRuntime().newTypeError("wrong argument type " + obj.getMetaClass() + " (expected Regexp)");
            obj = val; 
        }

        return RubyRegexp.newRegexp(getRuntime(), ((RubyString)obj).value, 0, quote);
    }

    /** rb_str_scan
     *
     */
    @JRubyMethod(name = "scan", required = 1, frame = true, reads = BACKREF, writes = BACKREF)
    public IRubyObject scan(ThreadContext context, IRubyObject arg, Block block) {
        Ruby runtime = context.getRuntime();
        Frame frame = context.getPreviousFrame();
        
        final RubyRegexp rubyRegex = getPattern(arg, true);
        final Regex regex = rubyRegex.getPattern();
        
        int range = value.begin + value.realSize;
        final Matcher matcher = regex.matcher(value.bytes, value.begin, range);
        matcher.value = 0; // implicit start argument to scanOnce(NG)
        
        IRubyObject result;
        if (!block.isGiven()) {
            RubyArray ary = runtime.newArray();
            
            if (regex.numberOfCaptures() == 0) {
                while ((result = scanOnceNG(rubyRegex, matcher, range)) != null) ary.append(result);
            } else {
                while ((result = scanOnce(rubyRegex, matcher, range)) != null) ary.append(result);
            }

            if (ary.size() > 0) {
                rubyRegex.updateBackRef(context, this, frame, matcher);
            } else {
                frame.setBackRef(runtime.getNil());
            }
            return ary;
        } else {
            byte[]bytes = value.bytes;
            int size = value.realSize;
            RubyMatchData match = null;
            
            if (regex.numberOfCaptures() == 0) {
                while ((result = scanOnceNG(rubyRegex, matcher, range)) != null) {
                    match = rubyRegex.updateBackRef(context, this, frame, matcher);
                    match.use();
                    block.yield(context, result);
                    modifyCheck(bytes, size);
                }
            } else {
                while ((result = scanOnce(rubyRegex, matcher, range)) != null) {
                    match = rubyRegex.updateBackRef(context, this, frame, matcher);
                    match.use();
                    block.yield(context, result);
                    modifyCheck(bytes, size);
                }
            }
            frame.setBackRef(match == null ? runtime.getNil() : match);
            return this;
        }
    }

    /**
     * rb_enc_check
     */
    @SuppressWarnings("unused")
    private Encoding encodingCheck(RubyRegexp pattern) {
        // For 1.9 compatibility, should check encoding compat between string and pattern
        return pattern.getKCode().getEncoding();
    }
    
    // no group version
    private IRubyObject scanOnceNG(RubyRegexp regex, Matcher matcher, int range) {    
        if (matcher.search(matcher.value + value.begin, range, Option.NONE) >= 0) {
            int end = matcher.getEnd();
            if (matcher.getBegin() == end) {
                if (value.realSize > end) {
                    matcher.value = end + regex.getPattern().getEncoding().length(value.bytes[value.begin + end]);
                } else {
                    matcher.value = end + 1;
                }
            } else {
                matcher.value = end;
            }
            return substr(matcher.getBegin(), end - matcher.getBegin()).infectBy(regex);
        }
        return null;
    }
    
    // group version
    private IRubyObject scanOnce(RubyRegexp regex, Matcher matcher, int range) {    
        if (matcher.search(matcher.value + value.begin, range, Option.NONE) >= 0) {
            Region region = matcher.getRegion();
            int end = region.end[0];
            if (region.beg[0] == end) {
                if (value.realSize > end) {
                    matcher.value = end + regex.getPattern().getEncoding().length(value.bytes[value.begin + end]);
                } else {
                    matcher.value = end + 1;
                }
            } else {
                matcher.value = end;
            }
            
            RubyArray result = getRuntime().newArray(region.numRegs);
            for (int i=1; i<region.numRegs; i++) {
                int beg = region.beg[i]; 
                if (beg == -1) {
                    result.append(getRuntime().getNil());
                } else {
                    result.append(substr(beg, region.end[i] - beg).infectBy(regex));
                }
            }
            return result;
        }
        return null;
    }    

    private static final ByteList SPACE_BYTELIST = new ByteList(ByteList.plain(" "));
    
    private final IRubyObject justify(IRubyObject arg0, char jflag) {
        Ruby runtime = getRuntime();
        
        int width = RubyFixnum.num2int(arg0);
        
        int f, flen = 0;
        byte[]fbuf;
        
        IRubyObject pad;

        f = SPACE_BYTELIST.begin;
        flen = SPACE_BYTELIST.realSize;
        fbuf = SPACE_BYTELIST.bytes;
        pad = runtime.getNil();
        
        return justifyCommon(width, jflag, flen, fbuf, f, runtime, pad);
    }
    
    private final IRubyObject justify(IRubyObject arg0, IRubyObject arg1, char jflag) {
        Ruby runtime = getRuntime();
        
        int width = RubyFixnum.num2int(arg0);
        
        int f, flen = 0;
        byte[]fbuf;
        
        IRubyObject pad;

        pad = arg1.convertToString();
        ByteList fList = ((RubyString)pad).value;
        f = fList.begin;
        flen = fList.realSize;

        if (flen == 0) throw runtime.newArgumentError("zero width padding");

        fbuf = fList.bytes;
        
        return justifyCommon(width, jflag, flen, fbuf, f, runtime, pad);
    }

    private IRubyObject justifyCommon(int width, char jflag, int flen, byte[] fbuf, int f, Ruby runtime, IRubyObject pad) {
        if (width < 0 || value.realSize >= width) return strDup(runtime);

        ByteList res = new ByteList(width);
        res.realSize = width;

        int p = res.begin;
        int pend;
        byte[] pbuf = res.bytes;

        if (jflag != 'l') {
            int n = width - value.realSize;
            pend = p + ((jflag == 'r') ? n : n / 2);
            if (flen <= 1) {
                while (p < pend) {
                    pbuf[p++] = fbuf[f];
                }
            } else {
                int q = f;
                while (p + flen <= pend) {
                    System.arraycopy(fbuf, f, pbuf, p, flen);
                    p += flen;
                }
                while (p < pend) {
                    pbuf[p++] = fbuf[q++];
                }
            }
        }

        System.arraycopy(value.bytes, value.begin, pbuf, p, value.realSize);

        if (jflag != 'r') {
            p += value.realSize;
            pend = res.begin + width;
            if (flen <= 1) {
                while (p < pend) {
                    pbuf[p++] = fbuf[f];
                }
            } else {
                while (p + flen <= pend) {
                    System.arraycopy(fbuf, f, pbuf, p, flen);
                    p += flen;
                }
                while (p < pend) {
                    pbuf[p++] = fbuf[f++];
                }
            }
        }

        RubyString resStr = new RubyString(runtime, getMetaClass(), res);
        resStr.infectBy(this);
        if (flen > 0) {
            resStr.infectBy(pad);
        }
        return resStr;
    }

    /**
     * Variable-arity version for compatibility. Not bound to Ruby.
     * @deprecated use the one or two argument versions.
     */
    public IRubyObject ljust(IRubyObject [] args) {
        switch (args.length) {
        case 1:
            return ljust(args[0]);
        case 2:
            return ljust(args[0], args[1]);
        default:
            Arity.raiseArgumentError(getRuntime(), args.length, 1, 2);
            return null; // not reached
        }
    }

    /** rb_str_ljust
     *
     */
    @JRubyMethod
    public IRubyObject ljust(IRubyObject arg0) {
        return justify(arg0, 'l');
    }

    /** rb_str_ljust
     *
     */
    @JRubyMethod
    public IRubyObject ljust(IRubyObject arg0, IRubyObject arg1) {
        return justify(arg0, arg1, 'l');
    }

    /**
     * Variable-arity version for compatibility. Not bound to Ruby.
     * @deprecated use the one or two argument versions.
     */
    public IRubyObject rjust(IRubyObject [] args) {
        switch (args.length) {
        case 1:
            return rjust(args[0]);
        case 2:
            return rjust(args[0], args[1]);
        default:
            Arity.raiseArgumentError(getRuntime(), args.length, 1, 2);
            return null; // not reached
        }
    }

    /** rb_str_rjust
     *
     */
    @JRubyMethod
    public IRubyObject rjust(IRubyObject arg0) {
        return justify(arg0, 'r');
    }

    /** rb_str_rjust
     *
     */
    @JRubyMethod
    public IRubyObject rjust(IRubyObject arg0, IRubyObject arg1) {
        return justify(arg0, arg1, 'r');
    }

    /**
     * Variable-arity version for compatibility. Not bound to Ruby.
     * @deprecated use the one or two argument versions.
     */
    public IRubyObject center(IRubyObject [] args) {
        switch (args.length) {
        case 1:
            return center(args[0]);
        case 2:
            return center(args[0], args[1]);
        default:
            Arity.raiseArgumentError(getRuntime(), args.length, 1, 2);
            return null; // not reached
        }
    }

    /** rb_str_center
     *
     */
    @JRubyMethod
    public IRubyObject center(IRubyObject arg0) {
        return justify(arg0, 'c');
    }

    /** rb_str_center
     *
     */
    @JRubyMethod
    public IRubyObject center(IRubyObject arg0, IRubyObject arg1) {
        return justify(arg0, arg1, 'c');
    }

    @JRubyMethod(name = "chop")
    public IRubyObject chop(ThreadContext context) {
        RubyString str = strDup(context.getRuntime());
        str.chop_bang();
        return str;
    }

    /** rb_str_chop_bang
     * 
     */
    @JRubyMethod(name = "chop!")
    public IRubyObject chop_bang() {
        int end = value.realSize - 1;
        if (end < 0) return getRuntime().getNil(); 

        if ((value.bytes[value.begin + end]) == '\n') {
            if (end > 0 && (value.bytes[value.begin + end - 1]) == '\r') end--;
        }

        view(0, end);
        return this;
    }

    /**
     * Variable-arity version for compatibility. Not bound to Ruby
     * 
     * @param args
     * @return
     * @deprecated Use the zero or one argument versions.
     */
    public RubyString chomp(IRubyObject[] args) {
        switch (args.length) {
        case 0:
            return chomp();
        case 1:
            return chomp(args[0]);
        default:
            Arity.raiseArgumentError(getRuntime(), args.length, 0, 1);
            return null; // not reached
        }
    }

    /** rb_str_chop
     * 
     */
    @JRubyMethod
    public RubyString chomp() {
        RubyString str = strDup(getRuntime());
        str.chomp_bang();
        return str;
    }

    /** rb_str_chop
     * 
     */
    @JRubyMethod
    public RubyString chomp(IRubyObject arg0) {
        RubyString str = strDup(getRuntime());
        str.chomp_bang(arg0);
        return str;
    }

    /**
     * Variable-arity version for compatibility. Not bound to Ruby.
     * @deprecated Use the zero or one argument versions.
     */
    public IRubyObject chomp_bang(IRubyObject[] args) {
        switch (args.length) {
        case 0:
            return chomp_bang();
        case 1:
            return chomp_bang(args[0]);
        default:
            Arity.raiseArgumentError(getRuntime(), args.length, 0, 1);
            return null; // not reached
        }
    }

    /**
     * rb_str_chomp_bang
     *
     * In the common case, removes CR and LF characters in various ways depending on the value of
     *   the optional args[0].
     * If args.length==0 removes one instance of CR, CRLF or LF from the end of the string.
     * If args.length>0 and args[0] is "\n" then same behaviour as args.length==0 .
     * If args.length>0 and args[0] is "" then removes trailing multiple LF or CRLF (but no CRs at
     *   all(!)).
     * @param args See method description.
     */
    @JRubyMethod(name = "chomp!")
    public IRubyObject chomp_bang() {
        IRubyObject rsObj;

        int len = value.length();
        if (len == 0) return getRuntime().getNil();
        byte[]buff = value.bytes;

        rsObj = getRuntime().getGlobalVariables().get("$/");

        if (rsObj == getRuntime().getGlobalVariables().getDefaultSeparator()) {
            int realSize = value.realSize;
            int begin = value.begin;
            if (buff[begin + len - 1] == (byte)'\n') {
                realSize--;
                if (realSize > 0 && buff[begin + realSize - 1] == (byte)'\r') realSize--;
                view(0, realSize);
            } else if (buff[begin + len - 1] == (byte)'\r') {
                realSize--;
                view(0, realSize);
            } else {
                modifyCheck();
                return getRuntime().getNil();
            }
            return this;                
        }
        
        return chompBangCommon(rsObj);
    }

    /**
     * rb_str_chomp_bang
     *
     * In the common case, removes CR and LF characters in various ways depending on the value of
     *   the optional args[0].
     * If args.length==0 removes one instance of CR, CRLF or LF from the end of the string.
     * If args.length>0 and args[0] is "\n" then same behaviour as args.length==0 .
     * If args.length>0 and args[0] is "" then removes trailing multiple LF or CRLF (but no CRs at
     *   all(!)).
     * @param args See method description.
     */
    @JRubyMethod(name = "chomp!")
    public IRubyObject chomp_bang(IRubyObject arg0) {
        return chompBangCommon(arg0);
    }

    private IRubyObject chompBangCommon(IRubyObject rsObj) {

        if (rsObj.isNil()) {
            return getRuntime().getNil();
        }
        RubyString rs = rsObj.convertToString();
        int len = value.realSize;
        int begin = value.begin;
        if (len == 0) {
            return getRuntime().getNil();
        }
        byte[] buff = value.bytes;
        int rslen = rs.value.realSize;

        if (rslen == 0) {
            while (len > 0 && buff[begin + len - 1] == (byte) '\n') {
                len--;
                if (len > 0 && buff[begin + len - 1] == (byte) '\r') {
                    len--;
                }
            }
            if (len < value.realSize) {
                view(0, len);
                return this;
            }
            return getRuntime().getNil();
        }

        if (rslen > len) {
            return getRuntime().getNil();
        }
        byte newline = rs.value.bytes[rslen - 1];

        if (rslen == 1 && newline == (byte) '\n') {
            buff = value.bytes;
            int realSize = value.realSize;
            if (buff[begin + len - 1] == (byte) '\n') {
                realSize--;
                if (realSize > 0 && buff[begin + realSize - 1] == (byte) '\r') {
                    realSize--;
                }
                view(0, realSize);
            } else if (buff[begin + len - 1] == (byte) '\r') {
                realSize--;
                view(0, realSize);
            } else {
                modifyCheck();
                return getRuntime().getNil();
            }
            return this;
        }

        if (buff[begin + len - 1] == newline && rslen <= 1 || value.endsWith(rs.value)) {
            view(0, value.realSize - rslen);
            return this;
        }

        return getRuntime().getNil();
    }

    /** rb_str_lstrip
     * 
     */
    @JRubyMethod
    public IRubyObject lstrip(ThreadContext context) {
        RubyString str = strDup(context.getRuntime());
        str.lstrip_bang();
        return str;
    }

    /** rb_str_lstrip_bang
     */
    @JRubyMethod(name = "lstrip!")
    public IRubyObject lstrip_bang() {
        if (value.realSize == 0) return getRuntime().getNil();
        
        int i=0;
        while (i < value.realSize && ASCII.isSpace(value.bytes[value.begin + i] & 0xff)) i++;
        
        if (i > 0) {
            view(i, value.realSize - i);
            return this;
        }
        
        return getRuntime().getNil();
    }

    /** rb_str_rstrip
     *  
     */
    @JRubyMethod
    public IRubyObject rstrip(ThreadContext context) {
        RubyString str = strDup(context.getRuntime());
        str.rstrip_bang();
        return str;
    }

    /** rb_str_rstrip_bang
     */ 
    @JRubyMethod(name = "rstrip!")
    public IRubyObject rstrip_bang() {
        if (value.realSize == 0) return getRuntime().getNil();
        int i=value.realSize - 1;

        while (i >= 0 && value.bytes[value.begin+i] == 0) i--;
        while (i >= 0 && ASCII.isSpace(value.bytes[value.begin + i] & 0xff)) i--;

        if (i < value.realSize - 1) {
            view(0, i + 1);
            return this;
        }

        return getRuntime().getNil();
    }

    /** rb_str_strip
     *
     */
    @JRubyMethod
    public IRubyObject strip(ThreadContext context) {
        RubyString str = strDup(context.getRuntime());
        str.strip_bang();
        return str;
    }

    /** rb_str_strip_bang
     */
    @JRubyMethod(name = "strip!")
    public IRubyObject strip_bang() {
        IRubyObject l = lstrip_bang();
        IRubyObject r = rstrip_bang();

        if(l.isNil() && r.isNil()) {
            return l;
        }
        return this;
    }

    /** rb_str_count
     *
     */
    @JRubyMethod(name = "count", required = 1, rest = true)
    public IRubyObject count(IRubyObject[] args) {
        if (args.length < 1) throw getRuntime().newArgumentError("wrong number of arguments");
        if (value.realSize == 0) return getRuntime().newFixnum(0);

        boolean[]table = new boolean[TRANS_SIZE];
        boolean init = true;
        for (int i=0; i<args.length; i++) {
            RubyString s = args[i].convertToString();
            s.setup_table(table, init);
            init = false;
        }

        int s = value.begin;
        int send = s + value.realSize;
        byte[]buf = value.bytes;
        int i = 0;

        while (s < send) if (table[buf[s++] & 0xff]) i++;

        return getRuntime().newFixnum(i);
    }

    /** rb_str_delete
     *
     */
    @JRubyMethod(name = "delete", required = 1, rest = true)
    public IRubyObject delete(ThreadContext context, IRubyObject[] args) {
        RubyString str = strDup(context.getRuntime());
        str.delete_bang(args);
        return str;
    }

    /** rb_str_delete_bang
     *
     */
    @JRubyMethod(name = "delete!", required = 1, rest = true)
    public IRubyObject delete_bang(IRubyObject[] args) {
        if (args.length < 1) throw getRuntime().newArgumentError("wrong number of arguments");
        
        boolean[]squeeze = new boolean[TRANS_SIZE];

        boolean init = true;
        for (int i=0; i<args.length; i++) {
            RubyString s = args[i].convertToString();
            s.setup_table(squeeze, init);
            init = false;
        }
        
        modify();
        
        if (value.realSize == 0) return getRuntime().getNil();
        int s = value.begin;
        int t = s;
        int send = s + value.realSize;
        byte[]buf = value.bytes;
        boolean modify = false;
        
        while (s < send) {
            if (squeeze[buf[s] & 0xff]) {
                modify = true;
            } else {
                buf[t++] = buf[s];
            }
            s++;
        }
        value.realSize = t - value.begin;
        
        if (modify) return this;
        return getRuntime().getNil();
    }

    /** rb_str_squeeze
     *
     */
    @JRubyMethod(name = "squeeze", rest = true)
    public IRubyObject squeeze(ThreadContext context, IRubyObject[] args) {
        RubyString str = strDup(context.getRuntime());
        str.squeeze_bang(args);
        return str;
    }

    /** rb_str_squeeze_bang
     *
     */
    @JRubyMethod(name = "squeeze!", rest = true)
    public IRubyObject squeeze_bang(IRubyObject[] args) {
        if (value.realSize == 0) {
            modifyCheck();
            return getRuntime().getNil();
        }

        final boolean squeeze[] = new boolean[TRANS_SIZE];

        if (args.length == 0) {
            for (int i=0; i<TRANS_SIZE; i++) squeeze[i] = true;
        } else {
            boolean init = true;
            for (int i=0; i<args.length; i++) {
                RubyString s = args[i].convertToString();
                s.setup_table(squeeze, init);
                init = false;
            }
        }

        modify();

        int s = value.begin;
        int t = s;
        int send = s + value.realSize;
        byte[]buf = value.bytes;
        int save = -1;

        while (s < send) {
            int c = buf[s++] & 0xff;
            if (c != save || !squeeze[c]) buf[t++] = (byte)(save = c);
        }

        if (t - value.begin != value.realSize) { // modified
            value.realSize = t - value.begin; 
            return this;
        }

        return getRuntime().getNil();
    }

    /** rb_str_tr
     *
     */
    @JRubyMethod
    public IRubyObject tr(ThreadContext context, IRubyObject src, IRubyObject repl) {
        RubyString str = strDup(context.getRuntime());
        str.tr_trans(src, repl, false);
        return str;
    }
    
    /** rb_str_tr_bang
    *
    */
    @JRubyMethod(name = "tr!")
    public IRubyObject tr_bang(IRubyObject src, IRubyObject repl) {
        return tr_trans(src, repl, false);
    }    
    
    private static final class TR {
        int gen, now, max;
        int p, pend;
        byte[]buf;
    }

    private static final int TRANS_SIZE = 256;
    
    /** tr_setup_table
     * 
     */
    private final void setup_table(boolean[]table, boolean init) {
        final boolean[]buf = new boolean[TRANS_SIZE];
        final TR tr = new TR();
        int c;
        
        boolean cflag = false;
        
        tr.p = value.begin;
        tr.pend = value.begin + value.realSize;
        tr.buf = value.bytes;
        tr.gen = tr.now = tr.max = 0;
        
        if (value.realSize > 1 && value.bytes[value.begin] == '^') {
            cflag = true;
            tr.p++;
        }
        
        if (init) for (int i=0; i<TRANS_SIZE; i++) table[i] = true;
        
        for (int i=0; i<TRANS_SIZE; i++) buf[i] = cflag;
        while ((c = trnext(tr)) >= 0) buf[c & 0xff] = !cflag;
        for (int i=0; i<TRANS_SIZE; i++) table[i] = table[i] && buf[i];
    }
    
    /** tr_trans
    *
    */    
    private final IRubyObject tr_trans(IRubyObject src, IRubyObject repl, boolean sflag) {
        if (value.realSize == 0) return getRuntime().getNil();
        
        ByteList replList = repl.convertToString().value;
        
        if (replList.realSize == 0) return delete_bang(new IRubyObject[]{src});

        ByteList srcList = src.convertToString().value;
        
        final TR trsrc = new TR();
        final TR trrepl = new TR();
        
        boolean cflag = false;
        boolean modify = false;
        
        trsrc.p = srcList.begin;
        trsrc.pend = srcList.begin + srcList.realSize;
        trsrc.buf = srcList.bytes;
        if (srcList.realSize >= 2 && srcList.bytes[srcList.begin] == '^') {
            cflag = true;
            trsrc.p++;
        }       
        
        trrepl.p = replList.begin;
        trrepl.pend = replList.begin + replList.realSize;
        trrepl.buf = replList.bytes;
        
        trsrc.gen = trrepl.gen = 0;
        trsrc.now = trrepl.now = 0;
        trsrc.max = trrepl.max = 0;
        
        int c;
        final int[]trans = new int[TRANS_SIZE];
        if (cflag) {
            for (int i=0; i<TRANS_SIZE; i++) trans[i] = 1;
            while ((c = trnext(trsrc)) >= 0) trans[c & 0xff] = -1;
            while ((c = trnext(trrepl)) >= 0); 
            for (int i=0; i<TRANS_SIZE; i++) {
                if (trans[i] >= 0) trans[i] = trrepl.now;
            }
        } else {
            for (int i=0; i<TRANS_SIZE; i++) trans[i] = -1;
            while ((c = trnext(trsrc)) >= 0) {
                int r = trnext(trrepl);
                if (r == -1) r = trrepl.now;
                trans[c & 0xff] = r;
            }
        }
        
        modify();
        
        int s = value.begin;
        int send = s + value.realSize;
        byte sbuf[] = value.bytes;
        
        if (sflag) {
            int t = s;
            int c0, last = -1;
            while (s < send) {
                c0 = sbuf[s++];
                if ((c = trans[c0 & 0xff]) >= 0) {
                    if (last == c) continue;
                    last = c;
                    sbuf[t++] = (byte)(c & 0xff);
                    modify = true;
                } else {
                    last = -1;
                    sbuf[t++] = (byte)c0;
                }
            }
            
            if (value.realSize > (t - value.begin)) {
                value.realSize = t - value.begin;
                modify = true;
            }
        } else {
            while (s < send) {
                if ((c = trans[sbuf[s] & 0xff]) >= 0) {
                    sbuf[s] = (byte)(c & 0xff);
                    modify = true;
                }
                s++;
            }
        }
        
        if (modify) return this;
        return getRuntime().getNil();
    }

    /** trnext
    *
    */    
    private final int trnext(TR t) {
        byte [] buf = t.buf;
        
        for (;;) {
            if (t.gen == 0) {
                if (t.p == t.pend) return -1;
                if (t.p < t.pend -1 && buf[t.p] == '\\') t.p++;
                t.now = buf[t.p++];
                if (t.p < t.pend - 1 && buf[t.p] == '-') {
                    t.p++;
                    if (t.p < t.pend) {
                        if (t.now > ((int)buf[t.p] & 0xFF)) {
                            t.p++;
                            continue;
                        }
                        t.gen = 1;
                        t.max = (int)buf[t.p++] & 0xFF;
                    }
                }
                return t.now & 0xff;
            } else if (++t.now < t.max) {
                return t.now & 0xff;
            } else {
                t.gen = 0;
                return t.max & 0xff;
            }
        }
    }    

    /** rb_str_tr_s
     *
     */
    @JRubyMethod
    public IRubyObject tr_s(ThreadContext context, IRubyObject src, IRubyObject repl) {
        RubyString str = strDup(context.getRuntime());
        str.tr_trans(src, repl, true);
        return str;
    }

    /** rb_str_tr_s_bang
     *
     */
    @JRubyMethod(name = "tr_s!")
    public IRubyObject tr_s_bang(IRubyObject src, IRubyObject repl) {
        return tr_trans(src, repl, true);
    }

    /** rb_str_each_line
     *
     */
    @JRubyMethod(name = {"each_line", "each"}, required = 0, optional = 1, frame = true)
    public IRubyObject each_line(ThreadContext context, IRubyObject[] args, Block block) {
        byte newline;
        int p = value.begin;
        int pend = p + value.realSize;
        int s;
        int ptr = p;
        int len = value.realSize;
        int rslen;
        IRubyObject line;
        

        IRubyObject _rsep;
        if (args.length == 0) {
            _rsep = getRuntime().getGlobalVariables().get("$/");
        } else {
            _rsep = args[0];
        }

        if(_rsep.isNil()) {
            block.yield(context, this);
            return this;
        }
        
        RubyString rsep = stringValue(_rsep);
        ByteList rsepValue = rsep.value;
        byte[] strBytes = value.bytes;

        rslen = rsepValue.realSize;
        
        if(rslen == 0) {
            newline = '\n';
        } else {
            newline = rsepValue.bytes[rsepValue.begin + rslen-1];
        }

        s = p;
        p+=rslen;

        for(; p < pend; p++) {
            if(rslen == 0 && strBytes[p] == '\n') {
                if(strBytes[++p] != '\n') {
                    continue;
                }
                while(p < pend && strBytes[p] == '\n') {
                    p++;
                }
            }
            if(ptr<p && strBytes[p-1] == newline &&
               (rslen <= 1 || 
                ByteList.memcmp(rsepValue.bytes, rsepValue.begin, rslen, strBytes, p-rslen, rslen) == 0)) {
                line = RubyString.newStringShared(getRuntime(), getMetaClass(), this.value.makeShared(s-ptr, p-s));
                line.infectBy(this);
                block.yield(context, line);
                modifyCheck(strBytes,len);
                s = p;
            }
        }

        if(s != pend) {
            if(p > pend) {
                p = pend;
            }
            line = RubyString.newStringShared(getRuntime(), getMetaClass(), this.value.makeShared(s-ptr, p-s));
            line.infectBy(this);
            block.yield(context, line);
        }

        return this;
    }

    /**
     * rb_str_each_byte
     */
    @JRubyMethod(name = "each_byte", frame = true)
    public RubyString each_byte(ThreadContext context, Block block) {
        Ruby runtime = getRuntime();
        // Check the length every iteration, since
        // the block can modify this string.
        for (int i = 0; i < value.length(); i++) {
            block.yield(context, runtime.newFixnum(value.get(i) & 0xFF));
        }
        return this;
    }

    /** rb_str_intern
     *
     */
    public RubySymbol intern() {
        String s = toString();
        if (s.length() == 0) {
            throw getRuntime().newArgumentError("interning empty string");
        }
        if (s.indexOf('\0') >= 0) {
            throw getRuntime().newArgumentError("symbol string may not contain '\\0'");
        }
        return getRuntime().newSymbol(s);
    }

    @JRubyMethod(name = {"to_sym", "intern"})
    public RubySymbol to_sym() {
        return intern();
    }

    @JRubyMethod(name = "sum", optional = 1)
    public RubyInteger sum(IRubyObject[] args) {
        if (args.length > 1) {
            throw getRuntime().newArgumentError("wrong number of arguments (" + args.length + " for 1)");
        }
        
        long bitSize = 16;
        if (args.length == 1) {
            long bitSizeArg = ((RubyInteger) args[0].convertToInteger()).getLongValue();
            if (bitSizeArg > 0) {
                bitSize = bitSizeArg;
            }
        }

        long result = 0;
        for (int i = 0; i < value.length(); i++) {
            result += value.get(i) & 0xFF;
        }
        return getRuntime().newFixnum(bitSize == 0 ? result : result % (long) Math.pow(2, bitSize));
    }
    
    /** string_to_c
     * 
     */
    @JRubyMethod(name = "to_c", reads = BACKREF, writes = BACKREF, compat = CompatVersion.RUBY1_9)
    public IRubyObject to_c(ThreadContext context) {
        Ruby runtime = context.getRuntime();
        Frame frame = context.getCurrentFrame();
        IRubyObject backref = frame.getBackRef();
        if (backref != null && backref instanceof RubyMatchData) ((RubyMatchData)backref).use();

        IRubyObject s = RuntimeHelpers.invoke(
                context, this, "gsub",
                RubyRegexp.newRegexp(runtime, Numeric.ComplexPatterns.underscores_pat),
                runtime.newString(new ByteList(new byte[]{'_'})));

        RubyArray a = RubyComplex.str_to_c_internal(context, s);

        frame.setBackRef(backref);

        if (!a.eltInternal(0).isNil()) {
            return a.eltInternal(0);
        } else {
            return RubyComplex.newComplexCanonicalize(context, RubyFixnum.zero(runtime));
        }
    }

    /** string_to_r
     * 
     */
    @JRubyMethod(name = "to_r", reads = BACKREF, writes = BACKREF, compat = CompatVersion.RUBY1_9)
    public IRubyObject to_r(ThreadContext context) {
        Ruby runtime = context.getRuntime();
        Frame frame = context.getCurrentFrame();
        IRubyObject backref = frame.getBackRef();
        if (backref != null && backref instanceof RubyMatchData) ((RubyMatchData)backref).use();

        IRubyObject s = RuntimeHelpers.invoke(
                context, this, "gsub",
                RubyRegexp.newRegexp(runtime, Numeric.ComplexPatterns.underscores_pat),
                runtime.newString(new ByteList(new byte[]{'_'})));

        RubyArray a = RubyRational.str_to_r_internal(context, s);

        frame.setBackRef(backref);

        if (!a.eltInternal(0).isNil()) {
            return a.eltInternal(0);
        } else {
            return RubyRational.newRationalCanonicalize(context, RubyFixnum.zero(runtime));
        }
    }    

    public static RubyString unmarshalFrom(UnmarshalStream input) throws java.io.IOException {
        RubyString result = newString(input.getRuntime(), input.unmarshalString());
        input.registerLinkTarget(result);
        return result;
    }

    /**
     * @see org.jruby.util.Pack#unpack
     */
    @JRubyMethod
    public RubyArray unpack(IRubyObject obj) {
        return Pack.unpack(getRuntime(), this.value, stringValue(obj).value);
    }

    public void empty() {
        value = ByteList.EMPTY_BYTELIST;
        shareLevel = SHARE_LEVEL_BYTELIST;
    }

    /**
     * Mutator for internal string representation.
     *
     * @param value The new java.lang.String this RubyString should encapsulate
     * @deprecated
     */
    public void setValue(CharSequence value) {
        view(ByteList.plain(value));
    }

    public void setValue(ByteList value) {
        view(value);
    }

    public CharSequence getValue() {
        return toString();
    }

    public byte[] getBytes() {
        return value.bytes();
    }

    public ByteList getByteList() {
        return value;
    }

    /** used by ar-jdbc
     * 
     */
    public String getUnicodeValue() {
        try {
            return new String(value.bytes,value.begin,value.realSize, "UTF8");
        } catch (Exception e) {
            throw new RuntimeException("Something's seriously broken with encodings", e);
        }
    }

    @Override
    public IRubyObject to_java() {
        return MiniJava.javaToRuby(getRuntime(), new String(getBytes()));
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2006 Ola Bini <ola@ologix.com>
 * Copyright (C) 2006 Ryan Bell <ryan.l.bell@gmail.com>
 * Copyright (C) 2007 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2008 Vladimir Sizikov <vsizikov@gmail.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.util.ArrayList;
import java.util.List;

import org.jruby.anno.FrameField;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.Block;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;
import org.jruby.util.TypeConverter;
import org.jruby.util.io.InvalidValueException;
import org.jruby.util.io.ModeFlags;
import org.jruby.util.io.Stream;

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

        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"),
                MethodIndex.getIndex("to_strio"), "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 = gets(context, args);
       
        while (!line.isNil()) {
            block.yield(context, line);
            line = gets(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 = new ByteList(new byte[0], false);
            } else {
                add = isParagraph? NEWLINE : sep;
            }

            ByteList line = internal.getByteList().makeShared((int)pos, ix - (int)pos);
            line.unshare();
            line.append(add);
            line.invalidate();
            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) {
        checkReadable();

        IRubyObject result = internalGets(context, args);
        context.getCurrentFrame().setLastLine(result);

        return result;
    }

    @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++) {
            String line;
            
            if (args[i].isNil()) {
                line = "nil";
            } else {
                IRubyObject tmp = args[i].checkArrayType();
                if (!tmp.isNil()) {
                    RubyArray arr = (RubyArray) tmp;
                    if (getRuntime().isInspecting(arr)) {
                        line = "[...]";
                    } else {
                        inspectPuts(context, arr);
                        continue;
                    }
                } else {
                    line = args[i].toString();
                }
            }

            callMethod(context, "write", getRuntime().newString(line));

            if (!line.endsWith("\n")) {
                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 ugly
            buf.realSize = length;
            buf.replace(0, length, internal.getByteList().bytes, (int) pos, length);
        }
        
        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;
    }
}
package org.jruby;

import org.joni.Matcher;
import org.joni.Option;
import org.joni.Regex;
import org.joni.Region;
import org.joni.encoding.Encoding;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.common.IRubyWarnings.ID;
import org.jruby.exceptions.RaiseException;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;

/**
 * @author kscott
 *
 */
@JRubyClass(name="StringScanner")
public class RubyStringScanner extends RubyObject {

    private RubyString str;
    private int pos = 0;
    private int lastPos = -1;

    private Region regs;
    private int beg = -1;
    private int end = -1;
    // not to be confused with RubyObject's flags
    private int scannerFlags;

    private static final int MATCHED_STR_SCN_F = 1 << 11;     
    
    private static ObjectAllocator STRINGSCANNER_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return new RubyStringScanner(runtime, klass);
        }
    };

    public static RubyClass createScannerClass(final Ruby runtime) {
        RubyClass scannerClass = runtime.defineClass("StringScanner", runtime.getObject(), STRINGSCANNER_ALLOCATOR);
        scannerClass.defineAnnotatedMethods(RubyStringScanner.class);
        ThreadContext context = runtime.getCurrentContext();
        scannerClass.setConstant("Version", runtime.newString("0.7.0").freeze(context));
        scannerClass.setConstant("Id", runtime.newString("$Id: strscan.c 13506 2007-09-24 08:56:24Z nobu $").freeze(context));

        RubyClass standardError = runtime.getStandardError();
        RubyClass error = scannerClass.defineClassUnder(
                "Error", standardError, standardError.getAllocator());

        RubyClass objClass = runtime.getObject();
        if (!objClass.isConstantDefined("ScanError")) {
            objClass.defineConstant("ScanError", error);
        }

        return scannerClass;
    }

    private void clearMatched() {
        scannerFlags &= ~MATCHED_STR_SCN_F;
    }

    private void setMatched() {
        scannerFlags |= MATCHED_STR_SCN_F;
    }

    private boolean isMatched() {
        return (scannerFlags & MATCHED_STR_SCN_F) != 0;
    }
    
    private void check() {
        if (str == null) throw getRuntime().newArgumentError("uninitialized StringScanner object");
    }

    protected RubyStringScanner(Ruby runtime, RubyClass type) {
        super(runtime, type);
    }

    // second argument is allowed, but ignored (MRI)
    @JRubyMethod(name = "initialize", required = 1, optional = 1, frame = true, visibility = Visibility.PRIVATE)
    public IRubyObject initialize(IRubyObject[] args, Block unusedBlock) {
        str = args[0].convertToString();        
        return this;
    }
    
    @JRubyMethod(name = "initialize_copy", frame=true, visibility = Visibility.PRIVATE)
    @Override
    public IRubyObject initialize_copy(IRubyObject other) {
        if (this == other) return this;
        if (!(other instanceof RubyStringScanner)) {
            throw getRuntime().newTypeError("wrong argument type "
                    + other.getMetaClass() + " (expected StringScanner)");
        }

        RubyStringScanner otherScanner = (RubyStringScanner)other;
        str = otherScanner.str;
        pos = otherScanner.pos;
        lastPos = otherScanner.lastPos;
        scannerFlags = otherScanner.scannerFlags;

        regs = otherScanner.regs != null ? otherScanner.regs.clone() : null;
        beg = otherScanner.beg;
        end = otherScanner.end;

        return this;
    }
    
    @JRubyMethod(name = "reset")
    public IRubyObject reset() {
        check();
        pos = 0;
        clearMatched();
        return this;
    }
    
    @JRubyMethod(name = "terminate")
    public IRubyObject terminate() {
        check();
        pos = str.getByteList().realSize;
        clearMatched();
        return this;
    }
    
    @JRubyMethod(name = "clear")
    public IRubyObject clear() {
        check();
        getRuntime().getWarnings().warning(ID.DEPRECATED_METHOD, "StringScanner#clear is obsolete; use #terminate instead", "StringScanner#clear", "#terminate");
        return terminate();
    }
    
    @JRubyMethod(name = "string")
    public RubyString string() {
        return str;
    }

    @JRubyMethod(name = "string=", required = 1)
    public IRubyObject set_string(ThreadContext context, IRubyObject str) {
        this.str = (RubyString) str.convertToString().strDup(context.getRuntime()).freeze(context);
        pos = 0;
        clearMatched();
        return str;
    }

    @JRubyMethod(name = {"concat", "<<"}, required = 1)
    public IRubyObject concat(IRubyObject obj) {
        check();
        str.append(obj); // append will call convertToString()
        return this;
    }
    
    @JRubyMethod(name = {"pos", "pointer"})
    public RubyFixnum pos() {
        check();
        return RubyFixnum.newFixnum(getRuntime(), pos);
    }

    @JRubyMethod(name = {"pos=", "pointer="})
    public IRubyObject set_pos(IRubyObject pos) {
        check();
        int i = RubyNumeric.num2int(pos);
        int size = str.getByteList().realSize;
        if (i < 0) i += size;
        if (i < 0 || i > size) throw getRuntime().newRangeError("index out of range.");
        this.pos = i;
        return RubyFixnum.newFixnum(getRuntime(), i);
    }

    private IRubyObject extractRange(Ruby runtime, int beg, int end) {
        int size = str.getByteList().realSize;
        if (beg > size) return getRuntime().getNil();
        if (end > size) end = size;
        return str.makeShared(runtime, beg, end - beg);
    }
    
    private IRubyObject extractBegLen(Ruby runtime, int beg, int len) {
        assert len >= 0;
        int size = str.getByteList().realSize;
        if (beg > size) return getRuntime().getNil();
        if (beg + len > size) len = size - beg;
        return str.makeShared(runtime, beg, len);
    }
    
    private IRubyObject scan(IRubyObject regex, boolean succptr, boolean getstr, boolean headonly) {
        if (!(regex instanceof RubyRegexp)) throw getRuntime().newTypeError("wrong argument type " + regex.getMetaClass() + " (expected Regexp)");
        check();
        
        Regex pattern = ((RubyRegexp)regex).getPattern();

        clearMatched();
        int rest = str.getByteList().realSize - pos;
        if (rest < 0) return getRuntime().getNil();

        ByteList value = str.getByteList();
        Matcher matcher = pattern.matcher(value.bytes, value.begin + pos, value.begin + value.realSize);

        final int ret;
        if (headonly) {
            ret = matcher.match(value.begin + pos, value.begin + value.realSize, Option.NONE);            
        } else {
            ret = matcher.search(value.begin + pos, value.begin + value.realSize, Option.NONE);
        }

        regs = matcher.getRegion(); 
        if (regs == null) {
            beg = matcher.getBegin();
            end = matcher.getEnd();
        } else {
            beg = regs.beg[0];
            end = regs.end[0];
        }

        if (ret < 0) return getRuntime().getNil();
        setMatched();

        lastPos = pos;
        if (succptr) pos += end;
        return  getstr ? extractBegLen(getRuntime(), lastPos, end) : RubyFixnum.newFixnum(getRuntime(), end);
    }
    
    @JRubyMethod(name = "scan", required = 1)
    public IRubyObject scan(IRubyObject regex) {
        return scan(regex, true, true, true);
    }
    
    @JRubyMethod(name = "match?", required = 1)
    public IRubyObject match_p(IRubyObject regex) {
        return scan(regex, false, false, true);
    }
    
    @JRubyMethod(name = "skip", required = 1)
    public IRubyObject skip(IRubyObject regex) {
        return scan(regex, true, false, true);
    }
    
    @JRubyMethod(name = "check", required = 1)
    public IRubyObject check(IRubyObject regex) {
        return scan(regex, false, true, true);
    }
    
    @JRubyMethod(name = "scan_full", required = 3)
    public IRubyObject scan_full(IRubyObject regex, IRubyObject s, IRubyObject f) {
        return scan(regex, s.isTrue(), f.isTrue(), true);
    }

    @JRubyMethod(name = "scan_until", required = 1)
    public IRubyObject scan_until(IRubyObject regex) {
        return scan(regex, true, true, false);
    }
    
    @JRubyMethod(name = "exist?", required = 1)
    public IRubyObject exist_p(IRubyObject regex) {
        return scan(regex, false, false, false);        
    }
    
    @JRubyMethod(name = "skip_until", required = 1)
    public IRubyObject skip_until(IRubyObject regex) {
        return scan(regex, true, false, false);
    }

    @JRubyMethod(name = "check_until", required = 1)
    public IRubyObject check_until(IRubyObject regex) {
        return scan(regex, false, true, false);        
    }
    
    @JRubyMethod(name = "search_full", required = 3)
    public IRubyObject search_full(IRubyObject regex, IRubyObject s, IRubyObject f) {
        return scan(regex, s.isTrue(), f.isTrue(), false);
    }

    private void adjustRegisters() {
        beg = 0;
        end = pos - lastPos;
        regs = null;
    }
    
    @JRubyMethod(name = "getch")
    public IRubyObject getch(ThreadContext context) {
        check();
        clearMatched();
        
        Ruby runtime = context.getRuntime();
        ByteList value = str.getByteList();
        
        if (pos >= value.realSize) return runtime.getNil();

        Encoding enc = runtime.getKCode().getEncoding();
        
        int len;
        if (enc.isSingleByte()) {
            len = 1;
        } else {
            len = enc.length(value.bytes[value.begin + pos]);
        }
        
        if (pos + len > value.realSize) len = value.realSize - pos;
        lastPos = pos;
        pos += len;
        
        setMatched();
        adjustRegisters();
        
        return extractRange(runtime, lastPos + beg, lastPos + end);
    }
    
    @JRubyMethod(name = "get_byte")
    public IRubyObject get_byte(ThreadContext context) {
        check();
        clearMatched();
        if (pos >= str.getByteList().realSize) return getRuntime().getNil();
        
        lastPos = pos;
        pos++;
        
        setMatched();
        adjustRegisters();
        
        return extractRange(context.getRuntime(), lastPos + beg, lastPos + end);
    }
    
    @JRubyMethod(name = "getbyte")
    public IRubyObject getbyte(ThreadContext context) {
        context.getRuntime().getWarnings().warning(ID.DEPRECATED_METHOD,
                "StringScanner#getbyte is obsolete; use #get_byte instead",
                "StringScanner#getbyte", "#get_byte");
        return get_byte(context);
    }

    @JRubyMethod(name = "peek", required = 1)
    public IRubyObject peek(ThreadContext context, IRubyObject length) {
        check();

        int len = RubyNumeric.num2int(length);
        if (len < 0) {
            throw context.getRuntime().newArgumentError("negative string size (or size too big)");
        }

        ByteList value = str.getByteList();
        if (pos >= value.realSize) return RubyString.newEmptyString(getRuntime()).infectBy(str);
        if (pos + len > value.realSize) len = value.realSize - pos;

        return extractBegLen(context.getRuntime(), pos, len);
    }

    @JRubyMethod(name = "peep", required = 1)
    public IRubyObject peep(ThreadContext context, IRubyObject length) {
        getRuntime().getWarnings().warning(
                ID.DEPRECATED_METHOD, "StringScanner#peep is obsolete; use #peek instead",
                "StringScanner#peep", "#peek");
        return peek(context, length);
    }
    
    @JRubyMethod(name = "unscan")
    public IRubyObject unscan() {
        check();
        Ruby runtime = getRuntime();

        if (!isMatched()) {
            RubyClass errorClass = runtime.fastGetClass("StringScanner").fastGetClass("Error");
            throw new RaiseException(RubyException.newException(
                    runtime, errorClass, "unscan failed: previous match had failed"));
        }
        pos = lastPos;
        clearMatched();
        return this;
    }
    
    @JRubyMethod(name = "beginning_of_line?", alias = "bol?")
    public IRubyObject bol_p() {
        check();
        ByteList value = str.getByteList();
        if (pos > value.realSize) return getRuntime().getNil();
        if (pos == 0) return getRuntime().getTrue();
        return value.bytes[(value.begin + pos) - 1] == (byte)'\n' ? getRuntime().getTrue() : getRuntime().getFalse();        
    }
    
    @JRubyMethod(name = "eos?")
    public RubyBoolean eos_p(ThreadContext context) {
        check();
        return pos >= str.getByteList().realSize ? context.getRuntime().getTrue() : context.getRuntime().getFalse();
    }
    
    @JRubyMethod(name = "empty?")
    public RubyBoolean empty_p(ThreadContext context) {
        getRuntime().getWarnings().warning(ID.DEPRECATED_METHOD, "StringScanner#empty? is obsolete; use #eos? instead", "StringScanner#empty?", "#eos?");
        return eos_p(context);
    }
    
    @JRubyMethod(name = "rest?")
    public RubyBoolean rest_p(ThreadContext context) {
        check();
        return pos >= str.getByteList().realSize ? context.getRuntime().getFalse() : context.getRuntime().getTrue();
    }

    @JRubyMethod(name = "matched?")
    public RubyBoolean matched_p(ThreadContext context) {
        check();
        return isMatched() ? context.getRuntime().getTrue() : context.getRuntime().getFalse();
    }

    @JRubyMethod(name = "matched")
    public IRubyObject matched(ThreadContext context) {
        check();
        if (!isMatched()) return getRuntime().getNil(); 
        return extractRange(context.getRuntime(), lastPos + beg, lastPos + end);
    }
    
    @JRubyMethod(name = "matched_size")
    public IRubyObject matched_size() {
        check();
        if (!isMatched()) return getRuntime().getNil();
        return RubyFixnum.newFixnum(getRuntime(), end - beg);
    }

    @JRubyMethod(name = "matchedsize")
    public IRubyObject matchedsize() {
        getRuntime().getWarnings().warning(ID.DEPRECATED_METHOD, "StringScanner#matchedsize is obsolete; use #matched_size instead", 
                "StringScanner#matchedize", "#matched_size");
        return matched_size();        
    }

    @JRubyMethod(name = "[]", required = 1)
    public IRubyObject op_aref(ThreadContext context, IRubyObject idx) {
        check();
        if (!isMatched()) return context.getRuntime().getNil();
        int i = RubyNumeric.num2int(idx);
        
        int numRegs = regs == null ? 1 : regs.numRegs;
        if (i < 0) i += numRegs;
        if (i < 0 || i >= numRegs) return context.getRuntime().getNil();
        
        if (regs == null) {
            assert i == 0;
            if (beg == -1) return getRuntime().getNil();
            return extractRange(context.getRuntime(), lastPos + beg, lastPos + end);
        } else {
            if (regs.beg[i] == -1) return getRuntime().getNil();
            return extractRange(context.getRuntime(), lastPos + regs.beg[i], lastPos + regs.end[i]);
        }
    }

    @JRubyMethod(name = "pre_match")
    public IRubyObject pre_match(ThreadContext context) {
        check();
        if (!isMatched()) return context.getRuntime().getNil();
        return extractRange(context.getRuntime(), 0, lastPos + beg);
    }
    
    @JRubyMethod(name = "post_match")
    public IRubyObject post_match(ThreadContext context) {
        check();
        if (!isMatched()) return context.getRuntime().getNil();
        return extractRange(context.getRuntime(), lastPos + end, str.getByteList().realSize);
    }
    
    @JRubyMethod(name = "rest")
    public IRubyObject rest(ThreadContext context) {
        check();
        ByteList value = str.getByteList();
        if (pos >= value.realSize) return RubyString.newEmptyString(context.getRuntime()).infectBy(str);
        return extractRange(context.getRuntime(), pos, value.realSize);
    }
    
    @JRubyMethod(name = "rest_size")
    public RubyFixnum rest_size() {
        check();
        ByteList value = str.getByteList();
        if (pos >= value.realSize) return RubyFixnum.zero(getRuntime());
        return RubyFixnum.newFixnum(getRuntime(), value.realSize - pos);
    }

    @JRubyMethod(name = "restsize")
    public RubyFixnum restsize() {
        getRuntime().getWarnings().warning(ID.DEPRECATED_METHOD, "StringScanner#restsize is obsolete; use #rest_size instead", "StringScanner#restsize", "#rest_size");
        return rest_size();
    }
    
    @JRubyMethod(name = "inspect")
    @Override
    public IRubyObject inspect() {
        if (str == null) return inspect("(uninitialized)");
        if (pos >= str.getByteList().realSize) return inspect("fin");
        if (pos == 0) return inspect(pos + "/" + str.getByteList().realSize + " @ " + inspect2());
        return inspect(pos + "/" + str.getByteList().realSize + " " + inspect1() + " @ " + inspect2()); 
    }
    
    private IRubyObject inspect(String msg) {
        IRubyObject result = getRuntime().newString("#<" + getMetaClass() + " " + msg + ">"); 
        if (str != null) result.infectBy(str);
        return result;
    }
    
    private static final int INSPECT_LENGTH = 5;
    
    private IRubyObject inspect1() {
        if (pos == 0) return RubyString.newEmptyString(getRuntime());
        if (pos > INSPECT_LENGTH) {
            return RubyString.newString(getRuntime(), "...".getBytes()).append(str.substr(pos - INSPECT_LENGTH, INSPECT_LENGTH)).inspect();
        } else {
            return str.substr(0, pos).inspect();
        }
    }
    
    private IRubyObject inspect2() {
        if (pos >= str.getByteList().realSize) return RubyString.newEmptyString(getRuntime());
        int len = str.getByteList().realSize - pos;
        if (len > INSPECT_LENGTH) {
            return ((RubyString)str.substr(pos, INSPECT_LENGTH)).cat("...".getBytes()).inspect();
        } else {
            return str.substr(pos, len).inspect();
        }
    }

    @JRubyMethod(name = "must_C_version", meta = true)
    public static IRubyObject mustCversion(IRubyObject recv) {
        return recv;
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2002-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2005 Charles O Nutter <headius@headius.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.util.List;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.Frame;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.marshal.MarshalStream;
import org.jruby.runtime.marshal.UnmarshalStream;
import org.jruby.util.ByteList;
import org.jruby.util.IdUtil;
import org.jruby.common.IRubyWarnings.ID;
import org.jruby.exceptions.RaiseException;
import org.jruby.internal.runtime.methods.CallConfiguration;
import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.runtime.ClassIndex;

/**
 * @author  jpetersen
 */
@JRubyClass(name="Struct")
public class RubyStruct extends RubyObject {
    private IRubyObject[] values;

    /**
     * Constructor for RubyStruct.
     * @param runtime
     * @param rubyClass
     */
    public RubyStruct(Ruby runtime, RubyClass rubyClass) {
        super(runtime, rubyClass);
        
        int size = RubyNumeric.fix2int(getInternalVariable((RubyClass)rubyClass, "__size__"));

        values = new IRubyObject[size];

        for (int i = 0; i < size; i++) {
            values[i] = getRuntime().getNil();
        }
    }

    public static RubyClass createStructClass(Ruby runtime) {
        // TODO: NOT_ALLOCATABLE_ALLOCATOR may be ok here, but it's unclear how Structs
        // work with marshalling. Confirm behavior and ensure we're doing this correctly. JRUBY-415
        RubyClass structClass = runtime.defineClass("Struct", runtime.getObject(), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
        runtime.setStructClass(structClass);
        structClass.index = ClassIndex.STRUCT;
        structClass.includeModule(runtime.getEnumerable());
        structClass.defineAnnotatedMethods(RubyStruct.class);

        return structClass;
    }
    
    @Override
    public int getNativeTypeIndex() {
        return ClassIndex.STRUCT;
    }
    
    private static IRubyObject getInternalVariable(RubyClass type, String internedName) {
        RubyClass structClass = type.getRuntime().getStructClass();
        IRubyObject variable;

        while (type != null && type != structClass) {
            if ((variable = type.fastGetInternalVariable(internedName)) != null) {
                return variable;
            }

            type = type.getSuperClass();
        }

        return type.getRuntime().getNil();
    }

    private RubyClass classOf() {
        return getMetaClass() instanceof MetaClass ? getMetaClass().getSuperClass() : getMetaClass();
    }

    private void modify() {
        testFrozen("Struct is frozen");

        if (!isTaint() && getRuntime().getSafeLevel() >= 4) {
            throw getRuntime().newSecurityError("Insecure: can't modify struct");
        }
    }
    
    @JRubyMethod
    public RubyFixnum hash(ThreadContext context) {
        Ruby runtime = getRuntime();
        int h = getMetaClass().getRealClass().hashCode();

        for (int i = 0; i < values.length; i++) {
            h = (h << 1) | (h < 0 ? 1 : 0);
            h ^= RubyNumeric.num2long(values[i].callMethod(context, MethodIndex.HASH, "hash"));
        }
        
        return runtime.newFixnum(h);
    }

    private IRubyObject setByName(String name, IRubyObject value) {
        RubyArray member = (RubyArray) getInternalVariable(classOf(), "__member__");

        assert !member.isNil() : "uninitialized struct";

        modify();

        for (int i = 0,k=member.getLength(); i < k; i++) {
            if (member.eltInternal(i).asJavaString().equals(name)) {
                return values[i] = value;
            }
        }

        throw notStructMemberError(name);
    }

    private IRubyObject getByName(String name) {
        RubyArray member = (RubyArray) getInternalVariable(classOf(), "__member__");

        assert !member.isNil() : "uninitialized struct";

        for (int i = 0,k=member.getLength(); i < k; i++) {
            if (member.eltInternal(i).asJavaString().equals(name)) {
                return values[i];
            }
        }

        throw notStructMemberError(name);
    }

    // Struct methods

    /** Create new Struct class.
     *
     * MRI: rb_struct_s_def / make_struct
     *
     */
    @JRubyMethod(name = "new", required = 1, rest = true, frame = true, meta = true)
    public static RubyClass newInstance(IRubyObject recv, IRubyObject[] args, Block block) {
        String name = null;
        boolean nilName = false;
        Ruby runtime = recv.getRuntime();

        if (args.length > 0) {
            IRubyObject firstArgAsString = args[0].checkStringType();
            if (!firstArgAsString.isNil()) {
                name = ((RubyString)firstArgAsString).getByteList().toString();
            } else if (args[0].isNil()) {
                nilName = true;
            }
        }

        RubyArray member = runtime.newArray();

        for (int i = (name == null && !nilName) ? 0 : 1; i < args.length; i++) {
            member.append(runtime.newSymbol(args[i].asJavaString()));
        }

        RubyClass newStruct;
        RubyClass superClass = (RubyClass)recv;

        if (name == null || nilName) {
            newStruct = RubyClass.newClass(runtime, superClass); 
            newStruct.setAllocator(STRUCT_INSTANCE_ALLOCATOR);
            newStruct.makeMetaClass(superClass.getMetaClass());
            newStruct.inherit(superClass);
        } else {
            if (!IdUtil.isConstant(name)) {
                throw runtime.newNameError("identifier " + name + " needs to be constant", name);
            }

            IRubyObject type = superClass.getConstantAt(name);
            if (type != null) {
                ThreadContext context = runtime.getCurrentContext();
                Frame frame = context.getCurrentFrame();
                runtime.getWarnings().warn(ID.STRUCT_CONSTANT_REDEFINED, frame.getFile(), frame.getLine(), "redefining constant Struct::" + name, name);
                superClass.remove_const(context, runtime.newString(name));
            }
            newStruct = superClass.defineClassUnder(name, superClass, STRUCT_INSTANCE_ALLOCATOR);
        }

        newStruct.index = ClassIndex.STRUCT;
        
        newStruct.fastSetInternalVariable("__size__", member.length());
        newStruct.fastSetInternalVariable("__member__", member);

        newStruct.getSingletonClass().defineAnnotatedMethods(StructMethods.class);

        // define access methods.
        for (int i = (name == null && !nilName) ? 0 : 1; i < args.length; i++) {
            final String memberName = args[i].asJavaString();
            // if we are storing a name as well, index is one too high for values
            final int index = (name == null && !nilName) ? i : i - 1;
            newStruct.addMethod(memberName, new DynamicMethod(newStruct, Visibility.PUBLIC, CallConfiguration.NO_FRAME_NO_SCOPE) {
                @Override
                public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args, Block block) {
                    Arity.checkArgumentCount(self.getRuntime(), args, 0, 0);
                    return ((RubyStruct)self).get(index);
                }

                @Override
                public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name) {
                    return ((RubyStruct)self).get(index);
                }

                @Override
                public DynamicMethod dup() {
                    return this;
                }
            });
            newStruct.addMethod(memberName + "=", new DynamicMethod(newStruct, Visibility.PUBLIC, CallConfiguration.NO_FRAME_NO_SCOPE) {
                @Override
                public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args, Block block) {
                    Arity.checkArgumentCount(self.getRuntime(), args, 1, 1);
                    return ((RubyStruct)self).set(args[0], index);
                }

                @Override
                public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg) {
                    return ((RubyStruct)self).set(arg, index);
                }

                @Override
                public DynamicMethod dup() {
                    return this;
                }
            });
        }
        
        if (block.isGiven()) {
            // Struct bodies should be public by default, so set block visibility to public. JRUBY-1185.
            block.getBinding().setVisibility(Visibility.PUBLIC);
            block.yield(runtime.getCurrentContext(), null, newStruct, newStruct, false);
        }

        return newStruct;
    }
    
    // For binding purposes on the newly created struct types
    public static class StructMethods {
        @JRubyMethod(name = {"new", "[]"}, rest = true, frame = true)
        public static IRubyObject newStruct(IRubyObject recv, IRubyObject[] args, Block block) {
            return RubyStruct.newStruct(recv, args, block);
        }
        
        @JRubyMethod
        public static IRubyObject members(IRubyObject recv, Block block) {
            return RubyStruct.members(recv, block);
        }
    }

    /** Create new Structure.
     *
     * MRI: struct_alloc
     *
     */
    public static RubyStruct newStruct(IRubyObject recv, IRubyObject[] args, Block block) {
        RubyStruct struct = new RubyStruct(recv.getRuntime(), (RubyClass) recv);

        struct.callInit(args, block);

        return struct;
    }

    @JRubyMethod(rest = true, frame = true, visibility = Visibility.PRIVATE)
    public IRubyObject initialize(IRubyObject[] args, Block unusedBlock) {
        modify();

        int size = RubyNumeric.fix2int(getInternalVariable(getMetaClass(), "__size__"));

        if (args.length > size) {
            throw getRuntime().newArgumentError("struct size differs (" + args.length +" for " + size + ")");
        }

        for (int i = 0; i < args.length; i++) {
            values[i] = args[i];
        }

        return getRuntime().getNil();
    }
    
    public static RubyArray members(IRubyObject recv, Block block) {
        RubyArray member = (RubyArray) getInternalVariable((RubyClass) recv, "__member__");

        assert !member.isNil() : "uninitialized struct";

        RubyArray result = recv.getRuntime().newArray(member.getLength());
        for (int i = 0,k=member.getLength(); i < k; i++) {
            result.append(recv.getRuntime().newString(member.eltInternal(i).asJavaString()));
        }

        return result;
    }

    @JRubyMethod
    public RubyArray members() {
        return members(classOf(), Block.NULL_BLOCK);
    }
    
    @JRubyMethod
    public RubyArray select(ThreadContext context, Block block) {
        RubyArray array = RubyArray.newArray(context.getRuntime());
        
        for (int i = 0; i < values.length; i++) {
            if (block.yield(context, values[i]).isTrue()) {
                array.append(values[i]);
            }
        }
        
        return array;
    }

    public IRubyObject set(IRubyObject value, int index) {
        RubyArray member = (RubyArray) getInternalVariable(classOf(), "__member__");

        assert !member.isNil() : "uninitialized struct";

        modify();

        return values[index] = value;
    }

    private RaiseException notStructMemberError(String name) {
        return getRuntime().newNameError(name + " is not struct member", name);
    }

    public IRubyObject get(int index) {
        RubyArray member = (RubyArray) getInternalVariable(classOf(), "__member__");

        assert !member.isNil() : "uninitialized struct";

        return values[index];
    }

    @Override
    public void copySpecialInstanceVariables(IRubyObject clone) {
        RubyStruct struct = (RubyStruct)clone;
        struct.values = new IRubyObject[values.length];
        System.arraycopy(values, 0, struct.values, 0, values.length);
    }

    @JRubyMethod(name = "==", required = 1)
    public IRubyObject op_equal(ThreadContext context, IRubyObject other) {
        if (this == other) return getRuntime().getTrue();
        if (!(other instanceof RubyStruct)) return getRuntime().getFalse();
        if (getMetaClass().getRealClass() != other.getMetaClass().getRealClass()) return getRuntime().getFalse();
        
        Ruby runtime = getRuntime();
        RubyStruct otherStruct = (RubyStruct)other;
        for (int i = 0; i < values.length; i++) {
            if (!equalInternal(context, values[i], otherStruct.values[i])) return runtime.getFalse();
        }
        return runtime.getTrue();
    }
    
    @JRubyMethod(name = "eql?", required = 1)
    public IRubyObject eql_p(ThreadContext context, IRubyObject other) {
        if (this == other) return getRuntime().getTrue();
        if (!(other instanceof RubyStruct)) return getRuntime().getFalse();
        if (getMetaClass() != other.getMetaClass()) return getRuntime().getFalse();
        
        Ruby runtime = getRuntime();
        RubyStruct otherStruct = (RubyStruct)other;
        for (int i = 0; i < values.length; i++) {
            if (!eqlInternal(context, values[i], otherStruct.values[i])) return runtime.getFalse();
        }
        return runtime.getTrue();        
    }

    /** inspect_struct
    *
    */
    private IRubyObject inspectStruct(final ThreadContext context) {    
        RubyArray member = (RubyArray) getInternalVariable(classOf(), "__member__");

        assert !member.isNil() : "uninitialized struct";

        ByteList buffer = new ByteList("#<struct ".getBytes());
        buffer.append(getMetaClass().getRealClass().getRealClass().getName().getBytes());
        buffer.append(' ');

        for (int i = 0,k=member.getLength(); i < k; i++) {
            if (i > 0) buffer.append(',').append(' ');
            // FIXME: MRI has special case for constants here 
            buffer.append(RubyString.objAsString(context, member.eltInternal(i)).getByteList());
            buffer.append('=');
            buffer.append(inspect(context, values[i]).getByteList());
        }

        buffer.append('>');
        return getRuntime().newString(buffer); // OBJ_INFECT        
    }

    @JRubyMethod(name = {"inspect", "to_s"})
    public IRubyObject inspect(ThreadContext context) {
        if (getRuntime().isInspecting(this)) return getRuntime().newString("#<struct " + getMetaClass().getRealClass().getName() + ":...>");

        try {
            getRuntime().registerInspecting(this);
            return inspectStruct(context);
        } finally {
            getRuntime().unregisterInspecting(this);
        }
    }

    @JRubyMethod(name = {"to_a", "values"})
    public RubyArray to_a() {
        return getRuntime().newArray(values);
    }

    @JRubyMethod(name = {"size", "length"} )
    public RubyFixnum size() {
        return getRuntime().newFixnum(values.length);
    }

    @JRubyMethod(name = "each", backtrace = true)
    public IRubyObject each(ThreadContext context, Block block) {
        for (int i = 0; i < values.length; i++) {
            block.yield(context, values[i]);
        }

        return this;
    }

    @JRubyMethod(frame = true)
    public IRubyObject each_pair(ThreadContext context, Block block) {
        RubyArray member = (RubyArray) getInternalVariable(classOf(), "__member__");

        assert !member.isNil() : "uninitialized struct";

        for (int i = 0; i < values.length; i++) {
            block.yield(context, getRuntime().newArrayNoCopy(new IRubyObject[]{member.eltInternal(i), values[i]}));
        }

        return this;
    }

    @JRubyMethod(name = "[]", required = 1)
    public IRubyObject aref(IRubyObject key) {
        if (key instanceof RubyString || key instanceof RubySymbol) {
            return getByName(key.asJavaString());
        }

        int idx = RubyNumeric.fix2int(key);

        idx = idx < 0 ? values.length + idx : idx;

        if (idx < 0) {
            throw getRuntime().newIndexError("offset " + idx + " too large for struct (size:" + values.length + ")");
        } else if (idx >= values.length) {
            throw getRuntime().newIndexError("offset " + idx + " too large for struct (size:" + values.length + ")");
        }

        return values[idx];
    }

    @JRubyMethod(name = "[]=", required = 2)
    public IRubyObject aset(IRubyObject key, IRubyObject value) {
        if (key instanceof RubyString || key instanceof RubySymbol) {
            return setByName(key.asJavaString(), value);
        }

        int idx = RubyNumeric.fix2int(key);

        idx = idx < 0 ? values.length + idx : idx;

        if (idx < 0) {
            throw getRuntime().newIndexError("offset " + idx + " too large for struct (size:" + values.length + ")");
        } else if (idx >= values.length) {
            throw getRuntime().newIndexError("offset " + idx + " too large for struct (size:" + values.length + ")");
        }

        modify();
        return values[idx] = value;
    }
    
    // FIXME: This is copied code from RubyArray.  Both RE, Struct, and Array should share one impl
    // This is also hacky since I construct ruby objects to access ruby arrays through aref instead
    // of something lower.
    @JRubyMethod(rest = true)
    public IRubyObject values_at(IRubyObject[] args) {
        long olen = values.length;
        RubyArray result = getRuntime().newArray(args.length);

        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof RubyFixnum) {
                result.append(aref(args[i]));
                continue;
            }

            long beglen[];
            if (!(args[i] instanceof RubyRange)) {
            } else if ((beglen = ((RubyRange) args[i]).begLen(olen, 0)) == null) {
                continue;
            } else {
                int beg = (int) beglen[0];
                int len = (int) beglen[1];
                int end = len;
                for (int j = 0; j < end; j++) {
                    result.append(aref(getRuntime().newFixnum(j + beg)));
                }
                continue;
            }
            result.append(aref(getRuntime().newFixnum(RubyNumeric.num2long(args[i]))));
        }

        return result;
    }

    public static void marshalTo(RubyStruct struct, MarshalStream output) throws java.io.IOException {
        output.registerLinkTarget(struct);
        output.dumpDefaultObjectHeader('S', struct.getMetaClass());

        List members = ((RubyArray) getInternalVariable(struct.classOf(), "__member__")).getList();
        output.writeInt(members.size());

        for (int i = 0; i < members.size(); i++) {
            RubySymbol name = (RubySymbol) members.get(i);
            output.dumpObject(name);
            output.dumpObject(struct.values[i]);
        }
    }

    public static RubyStruct unmarshalFrom(UnmarshalStream input) throws java.io.IOException {
        Ruby runtime = input.getRuntime();

        RubySymbol className = (RubySymbol) input.unmarshalObject();
        RubyClass rbClass = pathToClass(runtime, className.asJavaString());
        if (rbClass == null) {
            throw runtime.newNameError("uninitialized constant " + className, className.asJavaString());
        }

        RubyArray mem = members(rbClass, Block.NULL_BLOCK);

        int len = input.unmarshalInt();
        IRubyObject[] values = new IRubyObject[len];
        for(int i = 0; i < len; i++) {
            values[i] = runtime.getNil();
        }
        RubyStruct result = newStruct(rbClass, values, Block.NULL_BLOCK);
        input.registerLinkTarget(result);
        for(int i = 0; i < len; i++) {
            IRubyObject slot = input.unmarshalObject();
            if(!mem.eltInternal(i).toString().equals(slot.toString())) {
                throw runtime.newTypeError("struct " + rbClass.getName() + " not compatible (:" + slot + " for :" + mem.eltInternal(i) + ")");
            }
            result.aset(runtime.newFixnum(i), input.unmarshalObject());
        }
        return result;
    }

    private static RubyClass pathToClass(Ruby runtime, String path) {
        // FIXME: Throw the right ArgumentError's if the class is missing
        // or if it's a module.
        return (RubyClass) runtime.getClassFromPath(path);
    }
    
    private static ObjectAllocator STRUCT_INSTANCE_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            RubyStruct instance = new RubyStruct(runtime, klass);
            
            instance.setMetaClass(klass);
            
            return instance;
        }
    };
    
    @Override
    @JRubyMethod(required = 1)
    public IRubyObject initialize_copy(IRubyObject arg) {
        if (this == arg) return this;
        RubyStruct original = (RubyStruct) arg;
        
        values = new IRubyObject[original.values.length];
        System.arraycopy(original.values, 0, values, 0, original.values.length);

        return this;
    }
    
}
/*
 ***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Joey Gibson <joey@joeygibson.com>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2006 Derek Berner <derek.berner@state.nm.us>
 * Copyright (C) 2006 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * Copyright (C) 2007 William N Dortch <bill.dortch@gmail.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.util.concurrent.locks.ReentrantLock;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.common.IRubyWarnings.ID;
import org.jruby.javasupport.util.RuntimeHelpers;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Block;
import org.jruby.runtime.BlockCallback;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.marshal.UnmarshalStream;
import org.jruby.util.ByteList;

/**
 * Represents a Ruby symbol (e.g. :bar)
 */
@JRubyClass(name="Symbol")
public class RubySymbol extends RubyObject {
    private final String symbol;
    private final int id;
    private final ByteList symbolBytes;
    
    /**
     * 
     * @param runtime
     * @param internedSymbol the String value of the new Symbol. This <em>must</em>
     *                       have been previously interned
     */
    private RubySymbol(Ruby runtime, String internedSymbol) {
        super(runtime, runtime.getSymbol(), false);
        // symbol string *must* be interned

        assert internedSymbol == internedSymbol.intern() : internedSymbol + " is not interned";

        this.symbol = internedSymbol;
        this.symbolBytes = ByteList.create(symbol);

        this.id = runtime.allocSymbolId();
    }
    
    public static RubyClass createSymbolClass(Ruby runtime) {
        RubyClass symbolClass = runtime.defineClass("Symbol", runtime.getObject(), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
        runtime.setSymbol(symbolClass);
        RubyClass symbolMetaClass = symbolClass.getMetaClass();
        symbolClass.index = ClassIndex.SYMBOL;
        symbolClass.kindOf = new RubyModule.KindOf() {
            public boolean isKindOf(IRubyObject obj, RubyModule type) {
                return obj instanceof RubySymbol;
            }
        };

        symbolClass.defineAnnotatedMethods(RubySymbol.class);
        symbolMetaClass.undefineMethod("new");
        
        return symbolClass;
    }
    
    @Override
    public int getNativeTypeIndex() {
        return ClassIndex.SYMBOL;
    }

    /** rb_to_id
     * 
     * @return a String representation of the symbol 
     */
    @Override
    public String asJavaString() {
        return symbol;
    }
    
    /** short circuit for Symbol key comparison
     * 
     */
    @Override
    public final boolean eql(IRubyObject other) {
        return other == this;
    }

    @Override
    public boolean isImmediate() {
    	return true;
    }

    @Override
    public RubyClass getSingletonClass() {
        throw getRuntime().newTypeError("can't define singleton");
    }

    public static RubySymbol getSymbolLong(Ruby runtime, long id) {
        return runtime.getSymbolTable().lookup(id);
    }

    /* Symbol class methods.
     * 
     */

    public static RubySymbol newSymbol(Ruby runtime, String name) {
        return runtime.getSymbolTable().getSymbol(name);
    }

    @JRubyMethod(name = "to_i")
    public RubyFixnum to_i() {
        return getRuntime().newFixnum(id);
    }

    @JRubyMethod(name = "to_int")
    public RubyFixnum to_int() {
        if (getRuntime().getVerbose().isTrue()) {
            getRuntime().getWarnings().warn(ID.SYMBOL_AS_INTEGER, "treating Symbol as an integer");
	}
        return to_i();
    }

    @JRubyMethod(name = "inspect")
    @Override
    public IRubyObject inspect() {
        Ruby runtime = getRuntime();
        return runtime.newString(":" + 
            (isSymbolName(symbol) ? symbol : RubyString.newStringShared(runtime, symbolBytes).dump().toString())); 
    }

    @JRubyMethod(name = "to_s")
    @Override
    public IRubyObject to_s() {
        return RubyString.newStringShared(getRuntime(), symbolBytes);
    }

    @JRubyMethod(name = "id2name")
    public IRubyObject id2name() {
        return to_s();
    }

    @JRubyMethod(name = "===", required = 1)
    @Override
    public IRubyObject op_eqq(ThreadContext context, IRubyObject other) {
        return super.op_equal(context, other);
    }

    @Override
    public RubyFixnum hash() {
        return getRuntime().newFixnum(hashCode());
    }
    
    @Override
    public int hashCode() {
        return id;
    }

    public int getId() {
        return id;
    }
    
    @Override
    public boolean equals(Object other) {
        return other == this;
    }
    
    @JRubyMethod(name = "to_sym")
    public IRubyObject to_sym() {
        return this;
    }

    @Override
    public IRubyObject freeze(ThreadContext context) {
        return this;
    }

    @Override
    public IRubyObject taint(ThreadContext context) {
        return this;
    }

    private static class ToProcCallback implements BlockCallback {
        private RubySymbol symbol;
        public ToProcCallback(RubySymbol symbol) {
            this.symbol = symbol;
        }

        public IRubyObject call(ThreadContext ctx, IRubyObject[] args, Block blk) {
            IRubyObject[] currentArgs = args;
            switch(currentArgs.length) {
            case 0: throw symbol.getRuntime().newArgumentError("no receiver given");
            case 1: {
                if((currentArgs[0] instanceof RubyArray) && ((RubyArray)currentArgs[0]).getLength() != 0) {
                    // This is needed to unpack stuff
                    currentArgs = ((RubyArray)currentArgs[0]).toJavaArrayMaybeUnsafe();
                    IRubyObject[] args2 = new IRubyObject[currentArgs.length-1];
                    System.arraycopy(currentArgs, 1, args2, 0, args2.length);
                    return RuntimeHelpers.invoke(ctx, currentArgs[0], symbol.symbol, args2);
                } else {
                    return RuntimeHelpers.invoke(ctx, currentArgs[0], symbol.symbol);
                }
            }
            default: {
                IRubyObject[] args2 = new IRubyObject[currentArgs.length-1];
                System.arraycopy(currentArgs, 1, args2, 0, args2.length);
                return RuntimeHelpers.invoke(ctx, currentArgs[0], symbol.symbol, args2);
            }
            }
        }
    }
    /*
    @JRubyMethod
    public IRubyObject to_proc() {
        return RubyProc.newProc(getRuntime(),
                                CallBlock.newCallClosure(this, getRuntime().getSymbol(), Arity.noArguments(), new ToProcCallback(this), getRuntime().getCurrentContext()),
                                Block.Type.PROC);
    }
    */
    private static boolean isIdentStart(char c) {
        return ((c >= 'a' && c <= 'z')|| (c >= 'A' && c <= 'Z')
                || c == '_');
    }
    private static boolean isIdentChar(char c) {
        return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z')
                || c == '_');
    }
    
    private static boolean isIdentifier(String s) {
        if (s == null || s.length() <= 0) {
            return false;
        } 
        
        if (!isIdentStart(s.charAt(0))) {
            return false;
        }
        for (int i = 1; i < s.length(); i++) {
            if (!isIdentChar(s.charAt(i))) {
                return false;
            }
        }
        
        return true;
    }
    
    /**
     * is_special_global_name from parse.c.  
     * @param s
     * @return
     */
    private static boolean isSpecialGlobalName(String s) {
        if (s == null || s.length() <= 0) {
            return false;
        }

        int length = s.length();
           
        switch (s.charAt(0)) {        
        case '~': case '*': case '$': case '?': case '!': case '@': case '/': case '\\':        
        case ';': case ',': case '.': case '=': case ':': case '<': case '>': case '\"':        
        case '&': case '`': case '\'': case '+': case '0':
            return length == 1;            
        case '-':
            return (length == 1 || (length == 2 && isIdentChar(s.charAt(1))));
            
        default:
            // we already confirmed above that length > 0
            for (int i = 0; i < length; i++) {
                if (!Character.isDigit(s.charAt(i))) {
                    return false;
                }
            }
        }
        return true;
    }
    
    private static boolean isSymbolName(String s) {
        if (s == null || s.length() < 1) {
            return false;
        }

        int length = s.length();

        char c = s.charAt(0);
        switch (c) {
        case '$':
            if (length > 1 && isSpecialGlobalName(s.substring(1))) {
                return true;
            }
            return isIdentifier(s.substring(1));
        case '@':
            int offset = 1;
            if (length >= 2 && s.charAt(1) == '@') {
                offset++;
            }

            return isIdentifier(s.substring(offset));
        case '<':
            return (length == 1 || (length == 2 && (s.equals("<<") || s.equals("<="))) || 
                    (length == 3 && s.equals("<=>")));
        case '>':
            return (length == 1) || (length == 2 && (s.equals(">>") || s.equals(">=")));
        case '=':
            return ((length == 2 && (s.equals("==") || s.equals("=~"))) || 
                    (length == 3 && s.equals("===")));
        case '*':
            return (length == 1 || (length == 2 && s.equals("**")));
        case '+':
            return (length == 1 || (length == 2 && s.equals("+@")));
        case '-':
            return (length == 1 || (length == 2 && s.equals("-@")));
        case '|': case '^': case '&': case '/': case '%': case '~': case '`':
            return length == 1;
        case '[':
            return s.equals("[]") || s.equals("[]=");
        }
        
        if (!isIdentStart(c)) {
            return false;
        }

        boolean localID = (c >= 'a' && c <= 'z');
        int last = 1;
        
        for (; last < length; last++) {
            char d = s.charAt(last);
            
            if (!isIdentChar(d)) {
                break;
            }
        }
                    
        if (last == length) {
            return true;
        } else if (localID && last == length - 1) {
            char d = s.charAt(last);
            
            return d == '!' || d == '?' || d == '=';
        }
        
        return false;
    }
    
    @JRubyMethod(name = "all_symbols", meta = true)
    public static IRubyObject all_symbols(IRubyObject recv) {
        return recv.getRuntime().getSymbolTable().all_symbols();
    }

    public static RubySymbol unmarshalFrom(UnmarshalStream input) throws java.io.IOException {
        RubySymbol result = newSymbol(input.getRuntime(), RubyString.byteListToString(input.unmarshalString()));
        input.registerLinkTarget(result);
        return result;
    }

    public static class SymbolTable {
        static final int DEFAULT_INITIAL_CAPACITY = 2048; // *must* be power of 2!
        static final int MAXIMUM_CAPACITY = 1 << 30;
        static final float DEFAULT_LOAD_FACTOR = 0.75f;
        
        private final ReentrantLock tableLock = new ReentrantLock();
        private volatile SymbolEntry[] symbolTable;
        private int size;
        private int threshold;
        private final float loadFactor;
        private final Ruby runtime;
        
        public SymbolTable(Ruby runtime) {
            this.runtime = runtime;
            this.loadFactor = DEFAULT_LOAD_FACTOR;
            this.threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
            this.symbolTable = new SymbolEntry[DEFAULT_INITIAL_CAPACITY];
        }
        
        // note all fields are final -- rehash creates new entries when necessary.
        // as documented in java.util.concurrent.ConcurrentHashMap.java, that will
        // statistically affect only a small percentage (< 20%) of entries for a given rehash.
        static class SymbolEntry {
            final int hash;
            final String name;
            final RubySymbol symbol;
            final SymbolEntry next;
            
            SymbolEntry(int hash, String name, RubySymbol symbol, SymbolEntry next) {
                this.hash = hash;
                this.name = name;
                this.symbol = symbol;
                this.next = next;
            }
        }

        public RubySymbol getSymbol(String name) {
            int hash = name.hashCode();
            SymbolEntry[] table;
            for (SymbolEntry e = (table = symbolTable)[hash & (table.length - 1)]; e != null; e = e.next) {
                if (hash == e.hash && name.equals(e.name)) {
                    return e.symbol;
                }
            }
            ReentrantLock lock;
            (lock = tableLock).lock();
            try {
                int potentialNewSize;
                if ((potentialNewSize = size + 1) > threshold) {
                    table = rehash(); 
                } else {
                    table = symbolTable;
                }
                int index;
                // try lookup again under lock
                for (SymbolEntry e = table[index = hash & (table.length - 1)]; e != null; e = e.next) {
                    if (hash == e.hash && name.equals(e.name)) {
                        return e.symbol;
                    }
                }
                String internedName;
                RubySymbol symbol = new RubySymbol(runtime, internedName = name.intern());
                table[index] = new SymbolEntry(hash, internedName, symbol, table[index]);
                size = potentialNewSize;
                // write-volatile
                symbolTable = table;
                return symbol;
            } finally {
                lock.unlock();
            }
        }
        
        public RubySymbol fastGetSymbol(String internedName) {
            assert internedName == internedName.intern() : internedName + " is not interned";
            SymbolEntry[] table;
            for (SymbolEntry e = (table = symbolTable)[internedName.hashCode() & (table.length - 1)]; e != null; e = e.next) {
                if (internedName == e.name) {
                    return e.symbol;
                }
            }
            ReentrantLock lock;
            (lock = tableLock).lock();
            try {
                int potentialNewSize;
                if ((potentialNewSize = size + 1) > threshold) {
                    table = rehash();
                } else {
                    table = symbolTable;
                }
                int index;
                int hash;
                // try lookup again under lock
                for (SymbolEntry e = table[index = (hash = internedName.hashCode()) & (table.length - 1)]; e != null; e = e.next) {
                    if (internedName == e.name) {
                        return e.symbol;
                    }
                }
                RubySymbol symbol = new RubySymbol(runtime, internedName);
                table[index] = new SymbolEntry(hash, internedName, symbol, table[index]);
                size = potentialNewSize;
                // write-volatile
                symbolTable = table;
                return symbol;
            } finally {
                lock.unlock();
            }
        }
        
        // backwards-compatibility, but threadsafe now
        public RubySymbol lookup(String name) {
            int hash = name.hashCode();
            SymbolEntry[] table;
            for (SymbolEntry e = (table = symbolTable)[hash & (table.length - 1)]; e != null; e = e.next) {
                if (hash == e.hash && name.equals(e.name)) {
                    return e.symbol;
                }
            }
            return null;
        }
        
        public RubySymbol lookup(long id) {
            SymbolEntry[] table = symbolTable;
            for (int i = table.length; --i >= 0; ) {
                for (SymbolEntry e = table[i]; e != null; e = e.next) {
                    if (id == e.symbol.id) {
                        return e.symbol;
                    }
                }
            }
            return null;
        }
        
        public RubyArray all_symbols() {
            SymbolEntry[] table = this.symbolTable;
            RubyArray array = runtime.newArray(this.size);
            for (int i = table.length; --i >= 0; ) {
                for (SymbolEntry e = table[i]; e != null; e = e.next) {
                    array.append(e.symbol);
                }
            }
            return array;
        }
        
        // not so backwards-compatible here, but no one should have been
        // calling this anyway.
        @Deprecated
        public void store(RubySymbol symbol) {
            throw new UnsupportedOperationException();
        }
        
        private SymbolEntry[] rehash() {
            SymbolEntry[] oldTable = symbolTable;
            int oldCapacity;
            if ((oldCapacity = oldTable.length) >= MAXIMUM_CAPACITY) {
                return oldTable;
            }
            
            int newCapacity = oldCapacity << 1;
            SymbolEntry[] newTable = new SymbolEntry[newCapacity];
            threshold = (int)(newCapacity * loadFactor);
            int sizeMask = newCapacity - 1;
            SymbolEntry e;
            for (int i = oldCapacity; --i >= 0; ) {
                // We need to guarantee that any existing reads of old Map can
                //  proceed. So we cannot yet null out each bin.
                e = oldTable[i];

                if (e != null) {
                    SymbolEntry next = e.next;
                    int idx = e.hash & sizeMask;

                    //  Single node on list
                    if (next == null)
                        newTable[idx] = e;

                    else {
                        // Reuse trailing consecutive sequence at same slot
                        SymbolEntry lastRun = e;
                        int lastIdx = idx;
                        for (SymbolEntry last = next;
                             last != null;
                             last = last.next) {
                            int k = last.hash & sizeMask;
                            if (k != lastIdx) {
                                lastIdx = k;
                                lastRun = last;
                            }
                        }
                        newTable[lastIdx] = lastRun;

                        // Clone all remaining nodes
                        for (SymbolEntry p = e; p != lastRun; p = p.next) {
                            int k = p.hash & sizeMask;
                            SymbolEntry n = newTable[k];
                            newTable[k] = new SymbolEntry(p.hash, p.name, p.symbol, n);
                        }
                    }
                }
            }
            symbolTable = newTable;
            return newTable;
        }
        
    }
}
package org.jruby;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.HashMap;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ObjectMarshal;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.builtin.Variable;
import org.jruby.runtime.component.VariableEntry;
import org.jruby.runtime.marshal.MarshalStream;
import org.jruby.runtime.marshal.UnmarshalStream;

@JRubyClass(name="SystemCallError", parent="StandardError")
public class RubySystemCallError extends RubyException {
    private IRubyObject errno = getRuntime().getNil();

    private final static Map<String, String> defaultMessages = new HashMap<String, String>();
    static {
        defaultMessages.put("Errno::EPERM", "Operation not permitted");
        defaultMessages.put("Errno::ENOENT", "No such file or directory");
        defaultMessages.put("Errno::ESRCH", "No such process");
        defaultMessages.put("Errno::EINTR", "Interrupted system call");
        defaultMessages.put("Errno::EIO", "Input/output error");
        defaultMessages.put("Errno::ENXIO", "Device not configured");
        defaultMessages.put("Errno::E2BIG", "Argument list too long");
        defaultMessages.put("Errno::ENOEXEC", "Exec format error");
        defaultMessages.put("Errno::EBADF", "Bad file descriptor");
        defaultMessages.put("Errno::ECHILD", "No child processes");
        defaultMessages.put("Errno::EDEADLK", "Resource deadlock avoided");
        defaultMessages.put("Errno::ENOMEM", "Cannot allocate memory");
        defaultMessages.put("Errno::EACCES", "Permission denied");
        defaultMessages.put("Errno::EFAULT", "Bad address");
        defaultMessages.put("Errno::ENOTBLK", "Block device required");
        defaultMessages.put("Errno::EBUSY", "Resource busy");
        defaultMessages.put("Errno::EEXIST", "File exists");
        defaultMessages.put("Errno::EXDEV", "Cross-device link");
        defaultMessages.put("Errno::ENODEV", "Operation not supported by device");
        defaultMessages.put("Errno::ENOTDIR", "Not a directory");
        defaultMessages.put("Errno::EISDIR", "Is a directory");
        defaultMessages.put("Errno::EINVAL", "Invalid argument");
        defaultMessages.put("Errno::ENFILE", "Too many open files in system");
        defaultMessages.put("Errno::EMFILE", "Too many open files");
        defaultMessages.put("Errno::ENOTTY", "Inappropriate ioctl for device");
        defaultMessages.put("Errno::ETXTBSY", "Text file busy");
        defaultMessages.put("Errno::EFBIG", "File too large");
        defaultMessages.put("Errno::ENOSPC", "No space left on device");
        defaultMessages.put("Errno::ESPIPE", "Illegal seek");
        defaultMessages.put("Errno::EROFS", "Read-only file system");
        defaultMessages.put("Errno::EMLINK", "Too many links");
        defaultMessages.put("Errno::EPIPE", "Broken pipe");
        defaultMessages.put("Errno::EDOM", "Numerical argument out of domain");
        defaultMessages.put("Errno::ERANGE", "Result too large");
        defaultMessages.put("Errno::EAGAIN", "Resource temporarily unavailable");
        defaultMessages.put("Errno::EWOULDBLOCK", "Resource temporarily unavailable");
        defaultMessages.put("Errno::EINPROGRESS", "Operation now in progress");
        defaultMessages.put("Errno::EALREADY", "Operation already in progress");
        defaultMessages.put("Errno::ENOTSOCK", "Socket operation on non-socket");
        defaultMessages.put("Errno::EDESTADDRREQ", "Destination address required");
        defaultMessages.put("Errno::EMSGSIZE", "Message too long");
        defaultMessages.put("Errno::EPROTOTYPE", "Protocol wrong type for socket");
        defaultMessages.put("Errno::ENOPROTOOPT", "Protocol not available");
        defaultMessages.put("Errno::EPROTONOSUPPORT", "Protocol not supported");
        defaultMessages.put("Errno::ESOCKTNOSUPPORT", "Socket type not supported");
        defaultMessages.put("Errno::EPFNOSUPPORT", "Protocol family not supported");
        defaultMessages.put("Errno::EAFNOSUPPORT", "Address family not supported by protocol family");
        defaultMessages.put("Errno::EADDRINUSE", "Address already in use");
        defaultMessages.put("Errno::EADDRNOTAVAIL", "Can't assign requested address");
        defaultMessages.put("Errno::ENETDOWN", "Network is down");
        defaultMessages.put("Errno::ENETUNREACH", "Network is unreachable");
        defaultMessages.put("Errno::ENETRESET", "Network dropped connection on reset");
        defaultMessages.put("Errno::ECONNABORTED", "Software caused connection abort");
        defaultMessages.put("Errno::ECONNRESET", "Connection reset by peer");
        defaultMessages.put("Errno::ENOBUFS", "No buffer space available");
        defaultMessages.put("Errno::EISCONN", "Socket is already connected");
        defaultMessages.put("Errno::ENOTCONN", "Socket is not connected");
        defaultMessages.put("Errno::ESHUTDOWN", "Can't send after socket shutdown");
        defaultMessages.put("Errno::ETOOMANYREFS", "Too many references: can't splice");
        defaultMessages.put("Errno::ETIMEDOUT", "Operation timed out");
        defaultMessages.put("Errno::ECONNREFUSED", "Connection refused");
        defaultMessages.put("Errno::ELOOP", "Too many levels of symbolic links");
        defaultMessages.put("Errno::ENAMETOOLONG", "File name too long");
        defaultMessages.put("Errno::EHOSTDOWN", "Host is down");
        defaultMessages.put("Errno::EHOSTUNREACH", "No route to host");
        defaultMessages.put("Errno::ENOTEMPTY", "Directory not empty");
        defaultMessages.put("Errno::EUSERS", "Too many users");
        defaultMessages.put("Errno::EDQUOT", "Disc quota exceeded");
        defaultMessages.put("Errno::ESTALE", "Stale NFS file handle");
        defaultMessages.put("Errno::EREMOTE", "Too many levels of remote in path");
        defaultMessages.put("Errno::ENOLCK", "No locks available");
        defaultMessages.put("Errno::ENOSYS", "Function not implemented");
        defaultMessages.put("Errno::EOVERFLOW", "Value too large to be stored in data type");
        defaultMessages.put("Errno::EIDRM", "Identifier removed");
        defaultMessages.put("Errno::ENOMSG", "No message of desired type");
        defaultMessages.put("Errno::EILSEQ", "Illegal byte sequence");
        defaultMessages.put("Errno::EBADMSG", "Bad message");
        defaultMessages.put("Errno::EMULTIHOP", "EMULTIHOP (Reserved)");
        defaultMessages.put("Errno::ENODATA", "No message available on STREAM");
        defaultMessages.put("Errno::ENOLINK", "ENOLINK (Reserved)");
        defaultMessages.put("Errno::ENOSR", "No STREAM resources");
        defaultMessages.put("Errno::ENOSTR", "Not a STREAM");
        defaultMessages.put("Errno::EPROTO", "Protocol error");
        defaultMessages.put("Errno::ETIME", "STREAM ioctl timeout");
        defaultMessages.put("Errno::EOPNOTSUPP", "Operation not supported");
        defaultMessages.put("Errno::EOPNOTSUPP_DARWIN", "Operation not supported");
    }

    protected RubySystemCallError(Ruby runtime, RubyClass rubyClass) {
        super(runtime, rubyClass, null);
    }

    public RubySystemCallError(Ruby runtime, RubyClass rubyClass, String message, int errno) {
        super(runtime, rubyClass, message);
        this.errno = runtime.newFixnum(errno);
    }

    private static ObjectAllocator SYSTEM_CALL_ERROR_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            RubyException instance = new RubySystemCallError(runtime, klass);
            
            instance.setMetaClass(klass);
            
            return instance;
        }
    };
    
    private static final ObjectMarshal SYSTEM_CALL_ERROR_MARSHAL = new ObjectMarshal() {
        public void marshalTo(Ruby runtime, Object obj, RubyClass type,
                              MarshalStream marshalStream) throws IOException {
            RubySystemCallError exc = (RubySystemCallError) obj;
            marshalStream.registerLinkTarget(exc);
            
            List<Variable<IRubyObject>> attrs = exc.getVariableList();
            attrs.add(new VariableEntry<IRubyObject>(
                    "mesg", exc.message == null ? runtime.getNil() : exc.message));
            attrs.add(new VariableEntry<IRubyObject>("errno", exc.errno));
            attrs.add(new VariableEntry<IRubyObject>("bt", exc.getBacktrace()));
            marshalStream.dumpVariables(attrs);
        }

        public Object unmarshalFrom(Ruby runtime, RubyClass type,
            UnmarshalStream unmarshalStream) throws IOException {
            RubySystemCallError exc = (RubySystemCallError) type.allocate();
            
            unmarshalStream.registerLinkTarget(exc);
            unmarshalStream.defaultVariablesUnmarshal(exc);
            
            exc.message = exc.removeInternalVariable("mesg");
            exc.errno = exc.removeInternalVariable("errno");
            exc.set_backtrace(exc.removeInternalVariable("bt"));
            
            return exc;
        }
    };

    public static RubyClass createSystemCallErrorClass(Ruby runtime, RubyClass standardError) {
        RubyClass exceptionClass = runtime.defineClass("SystemCallError", standardError, SYSTEM_CALL_ERROR_ALLOCATOR);

        exceptionClass.setMarshal(SYSTEM_CALL_ERROR_MARSHAL);
        
        runtime.callbackFactory(RubyClass.class);
        exceptionClass.defineAnnotatedMethods(RubySystemCallError.class);

        return exceptionClass;
    }
    
    @JRubyMethod(optional = 2, required=0, frame = true, visibility = Visibility.PRIVATE)
    public IRubyObject initialize(IRubyObject[] args, Block block) {
        RubyClass sCallErorrClass = getRuntime().getSystemCallError();
        RubyClass klass = getMetaClass().getRealClass();

        IRubyObject msg = getRuntime().getNil();
        IRubyObject err = getRuntime().getNil();
        
        boolean isErrnoClass = !klass.equals(sCallErorrClass);
        
        if (!isErrnoClass) {
            // one optional, one required args
            Arity.checkArgumentCount(getRuntime(), args, 1, 2);
            msg = args[0];
            if (args.length == 2) {
                err = args[1];
            }
            if (args.length == 1 && (msg instanceof RubyFixnum)) {
                err = msg;
                msg = getRuntime().getNil();
            }
        } else {
            // one optional and no required args
            Arity.checkArgumentCount(getRuntime(), args, 0, 1);
            if (args.length == 1) {
                msg = args[0];
            }
            // try to get errno out of the class 
            err = klass.fastGetConstant("Errno");
        }
        
        if (!err.isNil()) {
            errno = err.convertToInteger();
        }

        String val = defaultMessages.get(klass.getName());
        if (val == null) {
            val = "Unknown error";
        }

        // MRI behavior: we don't print errno for actual Errno errors
        if (!errno.isNil() && !isErrnoClass) {
            val += " " + errno.toString();
        }

        if (!msg.isNil()) {
            val += " - " + msg.convertToString();
        }

        message = getRuntime().newString(val);        
        return this;
    }

    @JRubyMethod
    public IRubyObject errno() {
        return errno;
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/

package org.jruby;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;

@JRubyClass(name="SystemExit", parent="Exception")
public class RubySystemExit extends RubyException {
    IRubyObject status;

    private static ObjectAllocator SYSTEMEXIT_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            return new RubySystemExit(runtime, klass);
        }
    };    
    
    public static RubyClass createSystemExitClass(Ruby runtime, RubyClass exceptionClass) {
        RubyClass systemExitClass = runtime.defineClass("SystemExit", exceptionClass, SYSTEMEXIT_ALLOCATOR);

        systemExitClass.defineAnnotatedMethods(RubySystemExit.class);
        
        return systemExitClass;
    }  
    
    public static RubySystemExit newInstance(Ruby runtime, int status) {
        RubyClass exc = runtime.getSystemExit();
        IRubyObject[] exArgs = new IRubyObject[] {
                runtime.newFixnum(status),
                runtime.newString("exit") };
        return (RubySystemExit) exc.newInstance(runtime.getCurrentContext(), exArgs, Block.NULL_BLOCK);
    }

    protected RubySystemExit(Ruby runtime, RubyClass exceptionClass) {
        super(runtime, exceptionClass);
        status = runtime.getNil();
    }

    @JRubyMethod(name = "initialize", optional = 2, frame = true, visibility = Visibility.PRIVATE)
    public IRubyObject initialize(IRubyObject[]args, Block block) {
        status = RubyFixnum.zero(getRuntime());
        if (args.length > 0 && args[0] instanceof RubyFixnum) {
            status = args[0];
            IRubyObject[]tmpArgs = new IRubyObject[args.length - 1];
            System.arraycopy(args, 1, tmpArgs, 0, tmpArgs.length);
            args = tmpArgs;
        }
        super.initialize(args, block);
        return this;
    }

    @JRubyMethod(name = "status")
    public IRubyObject status() {
        return status;
    }

    @JRubyMethod(name = "success?")
    public IRubyObject success_p() {
        if (status.isNil()) return getRuntime().getTrue();
        if (status.equals(RubyFixnum.zero(getRuntime()))) return getRuntime().getTrue();
        return getRuntime().getFalse();
    }

}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2002 Jason Voegele <jason@jvoegele.com>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2002-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004-2005 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.io.IOException;
import java.nio.channels.Channel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.HashMap;
import java.util.Map;

import java.util.Set;
import org.jruby.common.IRubyWarnings.ID;
import org.jruby.exceptions.RaiseException;
import org.jruby.exceptions.ThreadKill;
import org.jruby.internal.runtime.FutureThread;
import org.jruby.internal.runtime.NativeThread;
import org.jruby.internal.runtime.RubyRunnable;
import org.jruby.internal.runtime.ThreadLike;
import org.jruby.internal.runtime.ThreadService;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.ReentrantLock;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.runtime.ObjectMarshal;
import org.jruby.runtime.Visibility;

/**
 * Implementation of Ruby's <code>Thread</code> class.  Each Ruby thread is
 * mapped to an underlying Java Virtual Machine thread.
 * <p>
 * Thread encapsulates the behavior of a thread of execution, including the main
 * thread of the Ruby script.  In the descriptions that follow, the parameter
 * <code>aSymbol</code> refers to a symbol, which is either a quoted string or a
 * <code>Symbol</code> (such as <code>:name</code>).
 * 
 * Note: For CVS history, see ThreadClass.java.
 */
@JRubyClass(name="Thread")
public class RubyThread extends RubyObject {
    private ThreadLike threadImpl;
    private RubyFixnum priority;
    private transient Map<IRubyObject, IRubyObject> threadLocalVariables;
    private boolean abortOnException;
    private IRubyObject finalResult;
    private RaiseException exitingException;
    private IRubyObject receivedException;
    private RubyThreadGroup threadGroup;

    private final ThreadService threadService;
    private volatile boolean isStopped = false;
    private volatile boolean isDead = false;
    public Object stopLock = new Object();
    
    private volatile boolean killed = false;
    public Object killLock = new Object();
    
    public final ReentrantLock lock = new ReentrantLock();
    
    private static final boolean DEBUG = false;

    protected RubyThread(Ruby runtime, RubyClass type) {
        super(runtime, type);
        this.threadService = runtime.getThreadService();
        finalResult = runtime.getNil();
    }
    
    /**
     * Dispose of the current thread by removing it from its parent ThreadGroup.
     */
    public void dispose() {
        threadGroup.remove(this);
    }
   
    public static RubyClass createThreadClass(Ruby runtime) {
        // FIXME: In order for Thread to play well with the standard 'new' behavior,
        // it must provide an allocator that can create empty object instances which
        // initialize then fills with appropriate data.
        RubyClass threadClass = runtime.defineClass("Thread", runtime.getObject(), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
        runtime.setThread(threadClass);

        threadClass.defineAnnotatedMethods(RubyThread.class);

        RubyThread rubyThread = new RubyThread(runtime, threadClass);
        // TODO: need to isolate the "current" thread from class creation
        rubyThread.threadImpl = new NativeThread(rubyThread, Thread.currentThread());
        runtime.getThreadService().setMainThread(Thread.currentThread(), rubyThread);
        
        // set to default thread group
        runtime.getDefaultThreadGroup().addDirectly(rubyThread);
        
        threadClass.setMarshal(ObjectMarshal.NOT_MARSHALABLE_MARSHAL);
        
        return threadClass;
    }

    /**
     * <code>Thread.new</code>
     * <p>
     * Thread.new( <i>[ arg ]*</i> ) {| args | block } -> aThread
     * <p>
     * Creates a new thread to execute the instructions given in block, and
     * begins running it. Any arguments passed to Thread.new are passed into the
     * block.
     * <pre>
     * x = Thread.new { sleep .1; print "x"; print "y"; print "z" }
     * a = Thread.new { print "a"; print "b"; sleep .2; print "c" }
     * x.join # Let the threads finish before
     * a.join # main thread exits...
     * </pre>
     * <i>produces:</i> abxyzc
     */
    @JRubyMethod(name = {"new", "fork"}, rest = true, frame = true, meta = true)
    public static IRubyObject newInstance(IRubyObject recv, IRubyObject[] args, Block block) {
        return startThread(recv, args, true, block);
    }

    /**
     * Basically the same as Thread.new . However, if class Thread is
     * subclassed, then calling start in that subclass will not invoke the
     * subclass's initialize method.
     */
    @JRubyMethod(name = "start", rest = true, frame = true, meta = true)
    public static RubyThread start(IRubyObject recv, IRubyObject[] args, Block block) {
        return startThread(recv, args, false, block);
    }
    
    public static RubyThread adopt(IRubyObject recv, Thread t) {
        return adoptThread(recv, t, Block.NULL_BLOCK);
    }

    private static RubyThread adoptThread(final IRubyObject recv, Thread t, Block block) {
        final Ruby runtime = recv.getRuntime();
        final RubyThread rubyThread = new RubyThread(runtime, (RubyClass) recv);
        
        rubyThread.threadImpl = new NativeThread(rubyThread, t);
        ThreadContext context = runtime.getThreadService().registerNewThread(rubyThread);
        
        context.preAdoptThread();
        
        // set to default thread group
        runtime.getDefaultThreadGroup().addDirectly(rubyThread);
        
        return rubyThread;
    }
    
    @JRubyMethod(name = "initialize", rest = true, frame = true, visibility = Visibility.PRIVATE)
    public IRubyObject initialize(IRubyObject[] args, Block block) {
        Ruby runtime = getRuntime();
        if (!block.isGiven()) throw runtime.newThreadError("must be called with a block");

        RubyRunnable runnable = new RubyRunnable(this, args, block);
        if (RubyInstanceConfig.POOLING_ENABLED) {
            threadImpl = new FutureThread(this, runnable);
        } else {
            Thread thread = new Thread(runnable);
            thread.setDaemon(true);
            threadImpl = new NativeThread(this, thread);
        }
        
        // set to default thread group
        runtime.getDefaultThreadGroup().addDirectly(this);
        
        threadImpl.start();
        
        // We yield here to hopefully permit the target thread to schedule
        // MRI immediately schedules it, so this is close but not exact
        Thread.yield();
        
        return this;
    }
    
    private static RubyThread startThread(final IRubyObject recv, final IRubyObject[] args, boolean callInit, Block block) {
        RubyThread rubyThread = new RubyThread(recv.getRuntime(), (RubyClass) recv);
        
        if (callInit) {
            rubyThread.callInit(args, block);
        } else {
            // for Thread::start, which does not call the subclass's initialize
            rubyThread.initialize(args, block);
        }
        
        return rubyThread;
    }
    
    private void ensureNotCurrent() {
        if (this == getRuntime().getCurrentContext().getThread()) {
            throw new RuntimeException("internal thread method called from another thread");
        }
    }
    
    public synchronized void cleanTerminate(IRubyObject result) {
        finalResult = result;
        isStopped = true;
        isDead = true;
    }

    public void pollThreadEvents() {
        pollThreadEvents(getRuntime().getCurrentContext());
    }
    
    public void pollThreadEvents(ThreadContext context) {
        // check for criticalization *before* locking ourselves
        threadService.waitForCritical();

        if (killed) throwThreadKill();
        if (receivedException != null) receivedAnException(context);
    }
    
    private void throwThreadKill() {
        throw new ThreadKill();
    }

    /**
     * Returns the status of the global ``abort on exception'' condition. The
     * default is false. When set to true, will cause all threads to abort (the
     * process will exit(0)) if an exception is raised in any thread. See also
     * Thread.abort_on_exception= .
     */
    @JRubyMethod(name = "abort_on_exception", meta = true)
    public static RubyBoolean abort_on_exception_x(IRubyObject recv) {
    	Ruby runtime = recv.getRuntime();
        return runtime.isGlobalAbortOnExceptionEnabled() ? runtime.getTrue() : runtime.getFalse();
    }

    @JRubyMethod(name = "abort_on_exception=", required = 1, meta = true)
    public static IRubyObject abort_on_exception_set_x(IRubyObject recv, IRubyObject value) {
        recv.getRuntime().setGlobalAbortOnExceptionEnabled(value.isTrue());
        return value;
    }

    @JRubyMethod(name = "current", meta = true)
    public static RubyThread current(IRubyObject recv) {
        return recv.getRuntime().getCurrentContext().getThread();
    }

    @JRubyMethod(name = "main", meta = true)
    public static RubyThread main(IRubyObject recv) {
        return recv.getRuntime().getThreadService().getMainThread();
    }

    @JRubyMethod(name = "pass", meta = true)
    public static IRubyObject pass(IRubyObject recv) {
        Ruby runtime = recv.getRuntime();
        ThreadService ts = runtime.getThreadService();
        boolean critical = ts.getCritical();
        
        ts.setCritical(false);
        
        Thread.yield();
        
        ts.setCritical(critical);
        
        return recv.getRuntime().getNil();
    }

    @JRubyMethod(name = "list", meta = true)
    public static RubyArray list(IRubyObject recv) {
    	RubyThread[] activeThreads = recv.getRuntime().getThreadService().getActiveRubyThreads();
        
        return recv.getRuntime().newArrayNoCopy(activeThreads);
    }
    
    private IRubyObject getSymbolKey(IRubyObject originalKey) {
        if (originalKey instanceof RubySymbol) {
            return originalKey;
        } else if (originalKey instanceof RubyString) {
            return getRuntime().newSymbol(originalKey.asJavaString());
        } else if (originalKey instanceof RubyFixnum) {
            getRuntime().getWarnings().warn(ID.FIXNUMS_NOT_SYMBOLS, "Do not use Fixnums as Symbols");
            throw getRuntime().newArgumentError(originalKey + " is not a symbol");
        } else {
            throw getRuntime().newTypeError(originalKey + " is not a symbol");
        }
    }
    
    private synchronized Map<IRubyObject, IRubyObject> getThreadLocals() {
        if (threadLocalVariables == null) {
            threadLocalVariables = new HashMap<IRubyObject, IRubyObject>();
        }
        return threadLocalVariables;
    }

    @JRubyMethod(name = "[]", required = 1)
    public IRubyObject op_aref(IRubyObject key) {
        IRubyObject value;
        if ((value = getThreadLocals().get(getSymbolKey(key))) != null) {
            return value;
        }
        return getRuntime().getNil();
    }

    @JRubyMethod(name = "[]=", required = 2)
    public IRubyObject op_aset(IRubyObject key, IRubyObject value) {
        key = getSymbolKey(key);
        
        getThreadLocals().put(key, value);
        return value;
    }

    @JRubyMethod(name = "abort_on_exception")
    public RubyBoolean abort_on_exception() {
        return abortOnException ? getRuntime().getTrue() : getRuntime().getFalse();
    }

    @JRubyMethod(name = "abort_on_exception=", required = 1)
    public IRubyObject abort_on_exception_set(IRubyObject val) {
        abortOnException = val.isTrue();
        return val;
    }

    @JRubyMethod(name = "alive?")
    public RubyBoolean alive_p() {
        return !isDead && threadImpl.isAlive() ? getRuntime().getTrue() : getRuntime().getFalse();
    }

    @JRubyMethod(name = "join", optional = 1, backtrace = true)
    public IRubyObject join(IRubyObject[] args) {
        long timeoutMillis = Long.MAX_VALUE;
        if (args.length > 0) {
            if (args.length > 1) {
                throw getRuntime().newArgumentError(args.length,1);
            }
            // MRI behavior: value given in seconds; converted to Float; less
            // than or equal to zero returns immediately; returns nil
            timeoutMillis = (long)(1000.0D * args[0].convertToFloat().getValue());
            if (timeoutMillis <= 0) {
	        // TODO: not sure that we should skip calling join() altogether.
		// Thread.join() has some implications for Java Memory Model, etc.
	        if (threadImpl.isAlive()) {
		   return getRuntime().getNil();
		} else {   
                   return this;
		}
            }
        }
        if (isCurrent()) {
            throw getRuntime().newThreadError("thread tried to join itself");
        }
        try {
            if (threadService.getCritical()) {
                // If the target thread is sleeping or stopped, wake it
                synchronized (stopLock) {
                    stopLock.notify();
                }
                
                // interrupt the target thread in case it's blocking or waiting
                // WARNING: We no longer interrupt the target thread, since this usually means
                // interrupting IO and with NIO that means the channel is no longer usable.
                // We either need a new way to handle waking a target thread that's waiting
                // on IO, or we need to accept that we can't wake such threads and must wait
                // for them to complete their operation.
                //threadImpl.interrupt();
            }

            RubyThread currentThread = getRuntime().getCurrentContext().getThread();
            final long timeToWait = Math.min(timeoutMillis, 200);

            // We need this loop in order to be able to "unblock" the
            // join call without actually calling interrupt.
            long start = System.currentTimeMillis();
            while(true) {
                currentThread.pollThreadEvents();
                threadImpl.join(timeToWait);
                if (!threadImpl.isAlive()) {
                    break;
                }
                if (System.currentTimeMillis() - start > timeoutMillis) {
                    break;
                }
            }
        } catch (InterruptedException ie) {
            ie.printStackTrace();
            assert false : ie;
        } catch (ExecutionException ie) {
            ie.printStackTrace();
            assert false : ie;
        }

        if (exitingException != null) {
            throw exitingException;
        }

        if (threadImpl.isAlive()) {
            return getRuntime().getNil();
        } else {
            return this;
	}
    }

    @JRubyMethod(name = "value")
    public IRubyObject value() {
        join(new IRubyObject[0]);
        synchronized (this) {
            return finalResult;
        }
    }

    @JRubyMethod(name = "group")
    public IRubyObject group() {
        if (threadGroup == null) {
        	return getRuntime().getNil();
        }
        
        return threadGroup;
    }
    
    void setThreadGroup(RubyThreadGroup rubyThreadGroup) {
    	threadGroup = rubyThreadGroup;
    }
    
    @JRubyMethod(name = "inspect")
    @Override
    public IRubyObject inspect() {
        // FIXME: There's some code duplication here with RubyObject#inspect
        StringBuilder part = new StringBuilder();
        String cname = getMetaClass().getRealClass().getName();
        part.append("#<").append(cname).append(":0x");
        part.append(Integer.toHexString(System.identityHashCode(this)));
        
        if (threadImpl.isAlive()) {
            if (isStopped) {
                part.append(getRuntime().newString(" sleep"));
            } else if (killed) {
                part.append(getRuntime().newString(" aborting"));
            } else {
                part.append(getRuntime().newString(" run"));
            }
        } else {
            part.append(" dead");
        }
        
        part.append(">");
        return getRuntime().newString(part.toString());
    }

    @JRubyMethod(name = "key?", required = 1)
    public RubyBoolean key_p(IRubyObject key) {
        key = getSymbolKey(key);
        
        return getRuntime().newBoolean(getThreadLocals().containsKey(key));
    }

    @JRubyMethod(name = "keys")
    public RubyArray keys() {
        IRubyObject[] keys = new IRubyObject[getThreadLocals().size()];
        
        return RubyArray.newArrayNoCopy(getRuntime(), getThreadLocals().keySet().toArray(keys));
    }
    
    @JRubyMethod(name = "critical=", required = 1, meta = true)
    public static IRubyObject critical_set(IRubyObject receiver, IRubyObject value) {
    	receiver.getRuntime().getThreadService().setCritical(value.isTrue());
    	
    	return value;
    }

    @JRubyMethod(name = "critical", meta = true)
    public static IRubyObject critical(IRubyObject receiver) {
    	return receiver.getRuntime().newBoolean(receiver.getRuntime().getThreadService().getCritical());
    }
    
    @JRubyMethod(name = "stop", meta = true)
    public static IRubyObject stop(IRubyObject receiver) {
        RubyThread rubyThread = receiver.getRuntime().getThreadService().getCurrentContext().getThread();
        Object stopLock = rubyThread.stopLock;
        
        synchronized (stopLock) {
            rubyThread.pollThreadEvents();
            try {
                rubyThread.isStopped = true;
                // attempt to decriticalize all if we're the critical thread
                receiver.getRuntime().getThreadService().setCritical(false);
                
                stopLock.wait();
            } catch (InterruptedException ie) {
                rubyThread.pollThreadEvents();
            }
            rubyThread.isStopped = false;
        }
        
        return receiver.getRuntime().getNil();
    }
    
    @JRubyMethod(name = "kill", required = 1, frame = true, meta = true)
    public static IRubyObject kill(IRubyObject receiver, IRubyObject rubyThread, Block block) {
        if (!(rubyThread instanceof RubyThread)) throw receiver.getRuntime().newTypeError(rubyThread, receiver.getRuntime().getThread());
        return ((RubyThread)rubyThread).kill();
    }
    
    @JRubyMethod(name = "exit", frame = true, meta = true)
    public static IRubyObject s_exit(IRubyObject receiver, Block block) {
        RubyThread rubyThread = receiver.getRuntime().getThreadService().getCurrentContext().getThread();
        
        rubyThread.killed = true;
        // attempt to decriticalize all if we're the critical thread
        receiver.getRuntime().getThreadService().setCritical(false);
        
        throw new ThreadKill();
    }

    @JRubyMethod(name = "stop?")
    public RubyBoolean stop_p() {
    	// not valid for "dead" state
    	return getRuntime().newBoolean(isStopped);
    }
    
    @JRubyMethod(name = "wakeup")
    public RubyThread wakeup() {
    	synchronized (stopLock) {
    		stopLock.notifyAll();
    	}
    	
    	return this;
    }
    
    @JRubyMethod(name = "priority")
    public RubyFixnum priority() {
        return priority;
    }

    @JRubyMethod(name = "priority=", required = 1)
    public IRubyObject priority_set(IRubyObject priority) {
        // FIXME: This should probably do some translation from Ruby priority levels to Java priority levels (until we have green threads)
        int iPriority = RubyNumeric.fix2int(priority);
        
        if (iPriority < Thread.MIN_PRIORITY) {
            iPriority = Thread.MIN_PRIORITY;
        } else if (iPriority > Thread.MAX_PRIORITY) {
            iPriority = Thread.MAX_PRIORITY;
        }
        
        this.priority = RubyFixnum.newFixnum(getRuntime(), iPriority);
        
        if (threadImpl.isAlive()) {
            threadImpl.setPriority(iPriority);
        }
        return this.priority;
    }

    @JRubyMethod(name = "raise", optional = 2, frame = true)
    public IRubyObject raise(IRubyObject[] args, Block block) {
        ensureNotCurrent();
        Ruby runtime = getRuntime();
        
        if (DEBUG) System.out.println("thread " + Thread.currentThread() + " before raising");
        RubyThread currentThread = getRuntime().getCurrentContext().getThread();
        try {
            while (!(currentThread.lock.tryLock() && this.lock.tryLock())) {
                if (currentThread.lock.isHeldByCurrentThread()) currentThread.lock.unlock();
            }

            currentThread.pollThreadEvents();
            if (DEBUG) System.out.println("thread " + Thread.currentThread() + " raising");
            receivedException = prepareRaiseException(runtime, args, block);
            
            // If the target thread is sleeping or stopped, wake it
            synchronized (stopLock) {
                stopLock.notify();
            }

            // interrupt the target thread in case it's blocking or waiting
            // WARNING: We no longer interrupt the target thread, since this usually means
            // interrupting IO and with NIO that means the channel is no longer usable.
            // We either need a new way to handle waking a target thread that's waiting
            // on IO, or we need to accept that we can't wake such threads and must wait
            // for them to complete their operation.
            //threadImpl.interrupt();
            
            // new interrupt, to hopefully wake it out of any blocking IO
            this.interrupt();
        } finally {
            if (currentThread.lock.isHeldByCurrentThread()) currentThread.lock.unlock();
            if (this.lock.isHeldByCurrentThread()) this.lock.unlock();
        }

        return this;
    }

    private IRubyObject prepareRaiseException(Ruby runtime, IRubyObject[] args, Block block) {
        if(args.length == 0) {
            IRubyObject lastException = runtime.getGlobalVariables().get("$!");
            if(lastException.isNil()) {
                return new RaiseException(runtime, runtime.getRuntimeError(), "", false).getException();
            } 
            return lastException;
        }

        IRubyObject exception;
        ThreadContext context = getRuntime().getCurrentContext();
        
        if(args.length == 1) {
            if(args[0] instanceof RubyString) {
                return runtime.getRuntimeError().newInstance(context, args, block);
            }
            
            if(!args[0].respondsTo("exception")) {
                return runtime.newTypeError("exception class/object expected").getException();
            }
            exception = args[0].callMethod(context, "exception");
        } else {
            if (!args[0].respondsTo("exception")) {
                return runtime.newTypeError("exception class/object expected").getException();
            }
            
            exception = args[0].callMethod(context, "exception", args[1]);
        }
        
        if (!runtime.getException().isInstance(exception)) {
            return runtime.newTypeError("exception object expected").getException();
        }
        
        if (args.length == 3) {
            ((RubyException) exception).set_backtrace(args[2]);
        }
        
        return exception;
    }
    
    @JRubyMethod(name = "run")
    public IRubyObject run() {
        // if stopped, unstop
        synchronized (stopLock) {
            if (isStopped) {
                isStopped = false;
                stopLock.notifyAll();
            }
        }
    	
    	return this;
    }
    
    public void sleep(long millis) throws InterruptedException {
        assert this == getRuntime().getCurrentContext().getThread();
        synchronized (stopLock) {
            pollThreadEvents();
            try {
                isStopped = true;
                stopLock.wait(millis);
            } finally {
                isStopped = false;
                pollThreadEvents();
            }
        }
    }

    @JRubyMethod(name = "status")
    public IRubyObject status() {
        if (threadImpl.isAlive()) {
            if (isStopped || currentSelector != null && currentSelector.isOpen()) {
            	return getRuntime().newString("sleep");
            } else if (killed) {
                return getRuntime().newString("aborting");
            }
        	
            return getRuntime().newString("run");
        } else if (exitingException != null) {
            return getRuntime().getNil();
        } else {
            return getRuntime().getFalse();
        }
    }

    @JRubyMethod(name = {"kill", "exit", "terminate"})
    public IRubyObject kill() {
    	// need to reexamine this
        RubyThread currentThread = getRuntime().getCurrentContext().getThread();
        
        // If the killee thread is the same as the killer thread, just die
        if (currentThread == this) throwThreadKill();
        
        try {
            if (DEBUG) System.out.println("thread " + Thread.currentThread() + " trying to kill");
            while (!(currentThread.lock.tryLock() && this.lock.tryLock())) {
                if (currentThread.lock.isHeldByCurrentThread()) currentThread.lock.unlock();
            }

            currentThread.pollThreadEvents();

            if (DEBUG) System.out.println("thread " + Thread.currentThread() + " succeeded with kill");
            killed = true;
            
            // If the target thread is sleeping or stopped, wake it
            synchronized (stopLock) {
                stopLock.notify();
            }

            // interrupt the target thread in case it's blocking or waiting
            // WARNING: We no longer interrupt the target thread, since this usually means
            // interrupting IO and with NIO that means the channel is no longer usable.
            // We either need a new way to handle waking a target thread that's waiting
            // on IO, or we need to accept that we can't wake such threads and must wait
            // for them to complete their operation.
            //threadImpl.interrupt();
            
            // new interrupt, to hopefully wake it out of any blocking IO
            this.interrupt();
        } finally {
            if (currentThread.lock.isHeldByCurrentThread()) currentThread.lock.unlock();
            if (this.lock.isHeldByCurrentThread()) this.lock.unlock();
        }
        
        try {
            threadImpl.join();
        } catch (InterruptedException ie) {
            // we were interrupted, check thread events again
            currentThread.pollThreadEvents();
        } catch (ExecutionException ie) {
            // we were interrupted, check thread events again
            currentThread.pollThreadEvents();
        }
        
        return this;
    }
    
    @JRubyMethod(name = {"kill!", "exit!", "terminate!"})
    public IRubyObject kill_bang() {
        throw getRuntime().newNotImplementedError("Thread#kill!, exit!, and terminate! are not safe and not supported");
    }
    
    @JRubyMethod(name = "safe_level")
    public IRubyObject safe_level() {
        throw getRuntime().newNotImplementedError("Thread-specific SAFE levels are not supported");
    }

    private boolean isCurrent() {
        return threadImpl.isCurrent();
    }

    public void exceptionRaised(RaiseException exception) {
        assert isCurrent();

        RubyException rubyException = exception.getException();
        Ruby runtime = rubyException.getRuntime();
        if (runtime.getSystemExit().isInstance(rubyException)) {
            threadService.getMainThread().raise(new IRubyObject[] {rubyException}, Block.NULL_BLOCK);
        } else if (abortOnException(runtime)) {
            runtime.printError(rubyException);
            RubyException systemExit = RubySystemExit.newInstance(runtime, 1);
            systemExit.message = rubyException.message;
            systemExit.set_backtrace(rubyException.backtrace());
            threadService.getMainThread().raise(new IRubyObject[] {systemExit}, Block.NULL_BLOCK);
            return;
        } else if (runtime.getDebug().isTrue()) {
            runtime.printError(exception.getException());
        }
        exitingException = exception;
    }

    private boolean abortOnException(Ruby runtime) {
        return (runtime.isGlobalAbortOnExceptionEnabled() || abortOnException);
    }

    public static RubyThread mainThread(IRubyObject receiver) {
        return receiver.getRuntime().getThreadService().getMainThread();
    }
    
    private Selector currentSelector;
    
    @Deprecated
    public boolean selectForAccept(RubyIO io) {
        return select(io, SelectionKey.OP_ACCEPT);
    }
    
    public boolean select(RubyIO io, int ops) {
        Channel channel = io.getChannel();
        
        if (channel instanceof SelectableChannel) {
            SelectableChannel selectable = (SelectableChannel)channel;
            
            synchronized (selectable.blockingLock()) {
                boolean oldBlocking = selectable.isBlocking();

                try {
                    selectable.configureBlocking(false);
                    
                    io.addBlockingThread(this);
                    currentSelector = selectable.provider().openSelector();

                    SelectionKey key = selectable.register(currentSelector, ops);

                    int result = currentSelector.select();

                    // check for thread events, in case we've been woken up to die
                    pollThreadEvents();

                    if (result == 1) {
                        Set<SelectionKey> keySet = currentSelector.selectedKeys();

                        if (keySet.iterator().next() == key) {
                            return true;
                        }
                    }

                    return false;
                } catch (IOException ioe) {
                    throw io.getRuntime().newRuntimeError("Error with selector: " + ioe);
                } finally {
                    if (currentSelector != null) {
                        try {
                            currentSelector.close();
                        } catch (IOException ioe) {
                            throw io.getRuntime().newRuntimeError("Could not close selector");
                        }
                    }
                    currentSelector = null;
                    io.removeBlockingThread(this);
                    try {
                        selectable.configureBlocking(oldBlocking);
                    } catch (IOException ioe) {
                        // ignore; I don't like doing it, but it seems like we
                        // really just need to make all channels non-blocking by
                        // default and use select when implementing blocking ops,
                        // so if this remains set non-blocking, perhaps it's not
                        // such a big deal...
                    }
                }
            }
        } else {
            // can't select, just have to do a blocking call
            return true;
        }
    }
    
    public void interrupt() {
        if (currentSelector != null) {
            currentSelector.wakeup();
        }
    }
    
    public void beforeBlockingCall() {
        isStopped = true;
    }
    
    public void afterBlockingCall() {
        isStopped = false;
    }

    private void receivedAnException(ThreadContext context) {
        // clear this so we don't keep re-throwing
        IRubyObject raiseException = receivedException;
        receivedException = null;
        RubyModule kernelModule = getRuntime().getKernel();
        if (DEBUG) {
            System.out.println("thread " + Thread.currentThread() + " before propagating exception: " + killed);
        }
        kernelModule.callMethod(context, "raise", raiseException);
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2004 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.util.HashMap;
import java.util.Map;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;

import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.builtin.IRubyObject;

/**
 * Implementation of Ruby's <code>ThreadGroup</code> class. This is currently
 * just a stub.
 * <p>
 *
 * @author Charles O Nutter (headius@headius.com)
 */
@JRubyClass(name="ThreadGroup")
public class RubyThreadGroup extends RubyObject {
    private Map<Integer, IRubyObject> rubyThreadList = new HashMap<Integer, IRubyObject>();
    private boolean enclosed = false;

    // ENEBO: Can these be fast?
    public static RubyClass createThreadGroupClass(Ruby runtime) {
        RubyClass threadGroupClass = runtime.defineClass("ThreadGroup", runtime.getObject(), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
        runtime.setThreadGroup(threadGroupClass);
        
        threadGroupClass.defineAnnotatedMethods(RubyThreadGroup.class);
        
        // create the default thread group
        RubyThreadGroup defaultThreadGroup = new RubyThreadGroup(runtime, threadGroupClass);
        runtime.setDefaultThreadGroup(defaultThreadGroup);
        threadGroupClass.defineConstant("Default", defaultThreadGroup);

        return threadGroupClass;
    }
    
    @JRubyMethod(name = "new", frame = true, meta = true)
    public static IRubyObject newInstance(IRubyObject recv, Block block) {
        return new RubyThreadGroup(recv.getRuntime(), (RubyClass)recv);
    }

    @JRubyMethod(name = "add", required = 1, frame = true)
    public synchronized IRubyObject add(IRubyObject rubyThread, Block block) {
        if (!(rubyThread instanceof RubyThread)) throw getRuntime().newTypeError(rubyThread, getRuntime().getThread());
        
        // synchronize on the RubyThread for threadgroup updates
        if (isFrozen()) {
            throw getRuntime().newTypeError("can't add to frozen ThreadGroup");
        }

        RubyThread thread = (RubyThread)rubyThread;

        // we only add live threads
        if (thread.alive_p().isTrue()) {
            addDirectly(thread);
        }
        
        return this;
    }
    
    void addDirectly(RubyThread rubyThread) {
        synchronized (rubyThread) {
            IRubyObject oldGroup = rubyThread.group();
            if (oldGroup != getRuntime().getNil()) {
                RubyThreadGroup threadGroup = (RubyThreadGroup) oldGroup;
                threadGroup.rubyThreadList.remove(System.identityHashCode(rubyThread));
            }

            rubyThread.setThreadGroup(this);
            rubyThreadList.put(System.identityHashCode(rubyThread), rubyThread);
        }
    }
    
    public synchronized void remove(RubyThread rubyThread) {
        rubyThread.setThreadGroup(null);
        rubyThreadList.remove(System.identityHashCode(rubyThread));
    }
    
    @JRubyMethod(name = "enclose", frame = true)
    public IRubyObject enclose(Block block) {
        enclosed = true;

        return this;
    }
    
    @JRubyMethod(name = "enclosed?", frame = true)
    public IRubyObject enclosed_p(Block block) {
        return new RubyBoolean(getRuntime(), enclosed);
    }

    @JRubyMethod(name = "list", frame = true)
    public synchronized IRubyObject list(Block block) {
        return getRuntime().newArrayNoCopy((IRubyObject[]) rubyThreadList.values().toArray(new IRubyObject[rubyThreadList.size()]));
    }

    private RubyThreadGroup(Ruby runtime, RubyClass type) {
        super(runtime, type);
    }

}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Chad Fowler <chadfowler@chadfowler.com>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004 Joey Gibson <joey@joeygibson.com>
 * Copyright (C) 2004 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2006 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2006 Ola Bini <ola.bini@ki.se>
 * Copyright (C) 2006 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.lang.ref.SoftReference;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
import org.jruby.runtime.Block;
import org.jruby.runtime.ClassIndex;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;
import org.jruby.util.RubyDateFormat;

/** 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 final static DateTimeFormatter ONE_DAY_CTIME_FORMATTER = DateTimeFormat.forPattern("EEE MMM  d HH:mm:ss yyyy").withLocale(Locale.ENGLISH);
    private final static DateTimeFormatter TWO_DAY_CTIME_FORMATTER = DateTimeFormat.forPattern("EEE MMM dd HH:mm:ss yyyy").withLocale(Locale.ENGLISH);

    private final static DateTimeFormatter TO_S_FORMATTER = DateTimeFormat.forPattern("EEE MMM dd HH:mm:ss Z yyyy").withLocale(Locale.ENGLISH);
    private final static DateTimeFormatter TO_S_UTC_FORMATTER = DateTimeFormat.forPattern("EEE MMM dd HH:mm:ss 'UTC' yyyy").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 ByteList TZ_STRING = ByteList.create("TZ");
     
    public static DateTimeZone getLocalTimeZone(Ruby runtime) {
        RubyString tzVar = runtime.newString(TZ_STRING);
        RubyHash h = ((RubyHash)runtime.getObject().fastGetConstant("ENV"));
        IRubyObject tz = h.op_aref(runtime.getCurrentContext(), tzVar);
        if (tz == null || ! (tz instanceof RubyString)) {
            return DateTimeZone.getDefault();
        } else {
            String zone = tz.toString();
            DateTimeZone cachedZone = runtime.getLocalTimezoneCache().get(zone);

            if (cachedZone != null) return cachedZone;

            String originalZone = zone;

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

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

            DateTimeZone dtz = DateTimeZone.forTimeZone(TimeZone.getTimeZone(zone));
            runtime.getLocalTimezoneCache().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;
    }

    // We assume that these two time instances
    // occurred at the same time.
    private static final long BASE_TIME_MILLIS = System.currentTimeMillis();
    private static final long BASE_TIME_NANOS = System.nanoTime();

    private static ObjectAllocator TIME_ALLOCATOR = new ObjectAllocator() {
        public IRubyObject allocate(Ruby runtime, RubyClass klass) {
            long usecsPassed = (System.nanoTime() - BASE_TIME_NANOS) / 1000L;
            long millisTime = BASE_TIME_MILLIS + usecsPassed / 1000L;
            long usecs = usecsPassed % 1000L;

            DateTimeZone dtz = getLocalTimeZone(runtime);
            DateTime dt = new DateTime(millisTime, dtz);
            RubyTime rt =  new RubyTime(runtime, klass, dt);
            rt.setUSec(usecs);

            return rt;
        }
    };

    public static RubyClass createTimeClass(Ruby runtime) {
        RubyClass timeClass = runtime.defineClass("Time", runtime.getObject(), TIME_ALLOCATOR);
        timeClass.index = ClassIndex.TIME;
        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);
        rubyDateFormat.applyPattern(format.toString());
        rubyDateFormat.setDateTime(dt);
        String result = rubyDateFormat.format(null);
        return getRuntime().newString(result);
    }
    
    @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)
    public IRubyObject op_plus(IRubyObject other) {
        long time = getTimeInMillis();

        if (other instanceof RubyTime) {
            throw getRuntime().newTypeError("time + time ?");
        }
        long adjustment = (long) (RubyNumeric.num2dbl(other) * 1000000);
        int micro = (int) (adjustment % 1000);
        adjustment = adjustment / 1000;

        time += adjustment;

        RubyTime newTime = new RubyTime(getRuntime(), getMetaClass());
        newTime.dt = new DateTime(time).withZone(dt.getZone());
        newTime.setUSec(micro);

        return newTime;
    }
    
    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)
    public IRubyObject op_minus(IRubyObject other) {
        if (other instanceof RubyTime) return opMinus((RubyTime) other);
        
        long time = getTimeInMillis();
        long adjustment = (long) (RubyNumeric.num2dbl(other) * 1000000);
        int micro = (int) (adjustment % 1000);
        adjustment = adjustment / 1000;

        time -= adjustment;

        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(callMethod(context, MethodIndex.OP_SPACESHIP, "<=>", 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"})
    @Override
    public IRubyObject to_s() {
        DateTimeFormatter simpleDateFormat;
        if (dt.getZone() == DateTimeZone.UTC) {
            simpleDateFormat = TO_S_UTC_FORMATTER;
        } else {
            simpleDateFormat = TO_S_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 = {"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 = {"gmt_offset", "gmtoff", "utc_offset"})
    public RubyInteger gmt_offset() {
        int offset = dt.getZone().getOffsetFromLocal(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() {
        String zone = dt.getZone().getShortName(dt.getMillis());
        if(zone.equals("+00:00")) {
            zone = "GMT";
        }
        return getRuntime().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, frame = true)
    public RubyString dump(IRubyObject[] args, Block unusedBlock) {
        RubyString str = (RubyString) mdump(new IRubyObject[] { this });
        str.syncVariables(this.getVariableList());
        return str;
    }    

    public RubyObject mdump(final IRubyObject[] args) {
        RubyTime obj = (RubyTime)args[0];
        DateTime dateTime = obj.dt.withZone(DateTimeZone.UTC);
        byte dumpValue[] = new byte[8];
        int pe = 
            0x1                                 << 31 |
            (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,false));
    }

    @JRubyMethod(name = "initialize", frame = true, visibility = 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 = "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, 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) {
                double dbl = ((RubyFloat) arg).getDoubleValue();
                long micro = (long) ((dbl - seconds) * 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, 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, 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 = {"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", required = 1, frame = true, meta = true)
    public static RubyTime load(IRubyObject recv, IRubyObject from, Block block) {
        return s_mload(recv, (RubyTime)(((RubyClass)recv).allocate()), from);
    }

    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));
        }
        if ((p & (1<<31)) == 0) {
            dt = dt.withMillis(p * 1000L + s);
        } else {
            p &= ~(1<<31);
            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);
            dt = dt.withZone(getLocalTimeZone(runtime));
            time.setUSec((s & 0xFFFFF) % 1000);
        }
        time.setDateTime(dt);
        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;

        if (args.length == 10) {
            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 (0 <= year && year < 39) {
            year += 2000;
        } else if (69 <= year && year < 139) {
            year += 1900;
        }

        DateTimeZone dtz;
        if (gmt) {
            dtz = DateTimeZone.UTC;
        } else {
            dtz = getLocalTimeZone(runtime);
        }

        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, dtz);

            dt = dt.plusMonths(month - 1)
                    .plusDays(int_args[0] - 1)
                    .plusHours(int_args[1])
                    .plusMinutes(int_args[2])
                    .plusSeconds(int_args[3]);
        } 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;
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2004 Charles O Nutter <headius@headius.com>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;

/**
 * 
 * Note: This was renamed from UnboundMethod.java
 * 
 * @author jpetersen
 */
@JRubyClass(name="UnboundMethod", parent="Method")
public class RubyUnboundMethod extends RubyMethod {
    protected RubyUnboundMethod(Ruby runtime) {
        super(runtime, runtime.getUnboundMethod());
    }

    public static RubyUnboundMethod newUnboundMethod(
        RubyModule implementationModule,
        String methodName,
        RubyModule originModule,
        String originName,
        DynamicMethod method) {
        RubyUnboundMethod newMethod = new RubyUnboundMethod(implementationModule.getRuntime());

        newMethod.implementationModule = implementationModule;
        newMethod.methodName = methodName;
        newMethod.originModule = originModule;
        newMethod.originName = originName;
        newMethod.method = method;

        return newMethod;
    }

    public static RubyClass defineUnboundMethodClass(Ruby runtime) {
        // TODO: NOT_ALLOCATABLE_ALLOCATOR is probably ok here. Confirm. JRUBY-415
        RubyClass newClass = 
        	runtime.defineClass("UnboundMethod", runtime.getMethod(), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
        runtime.setUnboundMethod(newClass);

        newClass.defineAnnotatedMethods(RubyUnboundMethod.class);

        return newClass;
    }

    /**
     * @see org.jruby.RubyMethod#call(IRubyObject[])
     */
    @JRubyMethod(name = {"call", "[]"}, rest = true, frame = true)
    @Override
    public IRubyObject call(ThreadContext context, IRubyObject[] args, Block block) {
        throw context.getRuntime().newTypeError("you cannot call unbound method; bind first");
    }

    /**
     * @see org.jruby.RubyMethod#unbind()
     */
    @JRubyMethod(name = "unbind", frame = true)
    @Override
    public RubyUnboundMethod unbind(Block block) {
        return this;
    }

    @JRubyMethod(name = "bind", required = 1, frame = true)
    public RubyMethod bind(ThreadContext context, IRubyObject aReceiver, Block block) {
        RubyClass receiverClass = aReceiver.getMetaClass();
        
        if (!originModule.isInstance(aReceiver)) {
            if (originModule instanceof MetaClass) {
                throw context.getRuntime().newTypeError("singleton method called for a different object");
            } else if (receiverClass instanceof MetaClass && receiverClass.getMethods().containsKey(originName)) {
                throw context.getRuntime().newTypeError("method `" + originName + "' overridden");
            } else if (
                !(originModule.isModule() ? originModule.isInstance(aReceiver) : aReceiver.getType() == originModule)) {
                // FIX replace type() == ... with isInstanceOf(...)
                throw context.getRuntime().newTypeError("bind argument must be an instance of " + originModule.getName());
            }
        }
        return RubyMethod.newMethod(implementationModule, methodName, receiverClass, originName, method, aReceiver);
    }
    
    @JRubyMethod(name = "clone")
    @Override
    public RubyMethod rbClone() {
        return newUnboundMethod(implementationModule, methodName, originModule, originName, method);
    }

    @JRubyMethod(name = "to_proc", frame = true)
    @Override
    public IRubyObject to_proc(ThreadContext context, Block unusedBlock) {
        return super.to_proc(context, unusedBlock);
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2007 Ola Bini <ola.bini@gmail.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.io.IOException;

import java.util.Iterator;
import java.util.List;
import java.util.Map;

import java.util.regex.Pattern;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyModule;

import org.jruby.runtime.Block;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;

import org.jruby.javasupport.JavaEmbedUtils;

import org.jruby.javasupport.JavaUtil;
import org.jruby.javasupport.util.RuntimeHelpers;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.Visibility;

import org.jruby.yaml.JRubyRepresenter;
import org.jruby.yaml.JRubyConstructor;
import org.jruby.yaml.JRubySerializer;
import org.jruby.util.IOInputStream;
import org.jruby.util.IOOutputStream;

import org.jvyamlb.Representer;
import org.jvyamlb.Constructor;
import org.jvyamlb.ParserImpl;
import org.jvyamlb.PositioningParserImpl;
import org.jvyamlb.Scanner;
import org.jvyamlb.ScannerImpl;
import org.jvyamlb.Composer;
import org.jvyamlb.ComposerImpl;
import org.jvyamlb.PositioningScannerImpl;
import org.jvyamlb.PositioningComposerImpl;
import org.jvyamlb.Serializer;
import org.jvyamlb.ResolverImpl;
import org.jvyamlb.EmitterImpl;
import org.jvyamlb.exceptions.YAMLException;
import org.jvyamlb.YAMLConfig;
import org.jvyamlb.YAML;
import org.jvyamlb.PositioningScanner;
import org.jvyamlb.Positionable;
import org.jvyamlb.Position;

/**
 * @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
 */
@JRubyModule(name="YAML")
public class RubyYAML {
    public static RubyModule createYAMLModule(Ruby runtime) {
        RubyModule result = runtime.defineModule("YAML");

        runtime.getKernel().callMethod(runtime.getCurrentContext(),"require", runtime.newString("stringio"));

        result.defineAnnotatedMethods(RubyYAML.class);

        RubyClass obj = runtime.getObject();
        RubyClass clazz = runtime.getClassClass();
        RubyClass hash = runtime.getHash();
        RubyClass array = runtime.getArray();
        RubyClass struct = runtime.getStructClass();
        RubyClass exception = runtime.getException();
        RubyClass string = runtime.getString();
        RubyClass symbol = runtime.getSymbol();
        RubyClass range = runtime.getRange();
        RubyClass regexp = runtime.getRegexp();
        RubyClass time = runtime.getTime();
        RubyClass date = runtime.fastGetClass("Date"); 
        RubyClass fixnum = runtime.getFixnum(); 
        RubyClass bignum = runtime.getBignum(); 
        RubyClass flt = runtime.getFloat(); 
        RubyClass trueClass = runtime.getTrueClass(); 
        RubyClass falseClass = runtime.getFalseClass(); 
        RubyClass nilClass = runtime.getNilClass(); 

        clazz.defineAnnotatedMethods(YAMLClassMethods.class);
        
        obj.defineAnnotatedMethods(YAMLObjectMethods.class);
        
        hash.defineAnnotatedMethods(YAMLHashMethods.class);

        array.defineAnnotatedMethods(YAMLArrayMethods.class);

        struct.defineAnnotatedMethods(YAMLStructMethods.class);

        exception.defineAnnotatedMethods(YAMLExceptionMethods.class);

        string.defineAnnotatedMethods(YAMLStringMethods.class);

        symbol.defineAnnotatedMethods(YAMLSymbolMethods.class);

        range.defineAnnotatedMethods(YAMLRangeMethods.class);

        regexp.defineAnnotatedMethods(YAMLRegexpMethods.class);

        time.defineAnnotatedMethods(YAMLTimeMethods.class);

        date.defineAnnotatedMethods(YAMLDateMethods.class);

        bignum.defineAnnotatedMethods(YAMLNumericMethods.class);

        fixnum.defineAnnotatedMethods(YAMLNumericMethods.class);

        flt.defineAnnotatedMethods(YAMLNumericMethods.class);

        trueClass.defineAnnotatedMethods(YAMLTrueMethods.class);

        falseClass.defineAnnotatedMethods(YAMLFalseMethods.class);

        nilClass.defineAnnotatedMethods(YAMLNilMethods.class);

        runtime.setObjectToYamlMethod(runtime.getObject().searchMethod("to_yaml"));

        return result;
    }

    @JRubyMethod(name = "dump", required = 1, optional = 1, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject dump(IRubyObject self, IRubyObject[] args) {
        IRubyObject obj = args[0];
        Ruby runtime = self.getRuntime();
        IRubyObject val = runtime.newArray(obj);
        if(args.length>1) {
            return RuntimeHelpers.invoke(runtime.getCurrentContext(), self,"dump_all", val, args[1]);
        } else {
            return self.callMethod(runtime.getCurrentContext(),"dump_all", val);
        }
    }

    @JRubyMethod(name = "dump_all", required = 1, optional = 1, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject dump_all(IRubyObject self, IRubyObject[] args) {
        ThreadContext context = self.getRuntime().getCurrentContext();
        RubyArray objs = (RubyArray)args[0];
        IRubyObject io = null;
        IRubyObject io2 = null;
        if(args.length == 2 && args[1] != null && !args[1].isNil()) {
            io = args[1];
        }
        YAMLConfig cfg = YAML.config().version("1.0");
        IOOutputStream iox = null;
        if(null == io) {
            io2 = self.getRuntime().fastGetClass("StringIO").callMethod(context, "new");
            iox = new IOOutputStream(io2);
        } else {
            iox = new IOOutputStream(io);
        }
        Serializer ser = new JRubySerializer(new EmitterImpl(iox,cfg),new ResolverImpl(),cfg);
        try {
            ser.open();
            Representer r = new JRubyRepresenter(ser, cfg);
            for(Iterator iter = objs.getList().iterator();iter.hasNext();) {
                r.represent(iter.next());
            }
            ser.close();
        } catch(IOException e) {
            throw self.getRuntime().newIOErrorFromException(e);
        }
        if(null == io) {
            io2.callMethod(context, "rewind");
            return io2.callMethod(context, "read");
        } else {
            return io;
        }
    }

    @JRubyMethod(name = "_parse_internal", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject parse_internal(IRubyObject self, IRubyObject arg) {
        boolean debug = self.getRuntime().getDebug().isTrue();
        IRubyObject io = check_yaml_port(arg);
        Scanner scn = null;
        try {
            if(io instanceof RubyString) {
                scn = debug ? new PositioningScannerImpl(((RubyString)io).getByteList()) : new ScannerImpl(((RubyString)io).getByteList());
            } else {
                scn = debug ? new PositioningScannerImpl(new IOInputStream(io)) : new ScannerImpl(new IOInputStream(io));
            }
            Composer ctor = 
                debug ?
                new PositioningComposerImpl(new PositioningParserImpl((PositioningScanner)scn,YAML.config().version("1.0")),new ResolverImpl()) :
                new ComposerImpl(new ParserImpl(scn,YAML.config().version("1.0")),new ResolverImpl())
                ;
            if(ctor.checkNode()) {
                return JavaEmbedUtils.javaToRuby(self.getRuntime(),ctor.getNode());
            }
            return self.getRuntime().getNil();
        } catch(YAMLException e) {
            if(self.getRuntime().getDebug().isTrue()) {
                Position.Range range = ((Positionable)e).getRange();
                throw self.getRuntime().newArgumentError("syntax error on " + range.start + ":" + range.end + ": " + e.getMessage());
            } else {
                throw self.getRuntime().newArgumentError("syntax error:" + e.getMessage());
            }
        }
    }

    @JRubyMethod(name = "load", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject load(IRubyObject self, IRubyObject arg) {
        boolean debug = self.getRuntime().getDebug().isTrue();
        IRubyObject io = check_yaml_port(arg);
        Scanner scn = null;
        try {
            if(io instanceof RubyString) {
                scn = debug ? new PositioningScannerImpl(((RubyString)io).getByteList()) : new ScannerImpl(((RubyString)io).getByteList());
            } else {
                scn = debug ? new PositioningScannerImpl(new IOInputStream(io)) : new ScannerImpl(new IOInputStream(io));
            }
            Constructor ctor = 
                debug ?
                new JRubyConstructor(self, new PositioningComposerImpl(new PositioningParserImpl((PositioningScanner)scn,YAML.config().version("1.0")),new ResolverImpl())) :
                new JRubyConstructor(self, new ComposerImpl(new ParserImpl(scn,YAML.config().version("1.0")),new ResolverImpl()))
                ;
            if(ctor.checkData()) {
                return JavaEmbedUtils.javaToRuby(self.getRuntime(),ctor.getData());
            }
            return self.getRuntime().getNil();
        } catch(YAMLException e) {
            if(self.getRuntime().getDebug().isTrue()) {
                Position.Range range = ((Positionable)e).getRange();
                throw self.getRuntime().newArgumentError("syntax error on " + range.start + ":" + range.end + ": " + e.getMessage());
            } else {
                throw self.getRuntime().newArgumentError("syntax error:" + e.getMessage());
            }
        }
    }

    @JRubyMethod(name = "load_file", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject load_file(IRubyObject self, IRubyObject arg) {
        Ruby runtime = self.getRuntime();
        ThreadContext context = runtime.getCurrentContext();
        IRubyObject io = RuntimeHelpers.invoke(context, runtime.getFile(),"open", arg, runtime.newString("r"));
        IRubyObject val = self.callMethod(context,"load", io);
        io.callMethod(context, "close");
        return val;
    }

    @JRubyMethod(name = "each_document", required = 1, frame = true, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject each_document(IRubyObject self, IRubyObject arg, Block block) {
        boolean debug = self.getRuntime().getDebug().isTrue();
        ThreadContext context = self.getRuntime().getCurrentContext();
        IRubyObject io = arg;
        Scanner scn = null;
        try {
            if(io instanceof RubyString) {
                scn = debug ? new PositioningScannerImpl(((RubyString)io).getByteList()) : new ScannerImpl(((RubyString)io).getByteList());
            } else {
                scn = debug ? new PositioningScannerImpl(new IOInputStream(io)) : new ScannerImpl(new IOInputStream(io));
            }
            Constructor ctor = 
                debug ?
                new JRubyConstructor(self, new PositioningComposerImpl(new PositioningParserImpl((PositioningScanner)scn,YAML.config().version("1.0")),new ResolverImpl())) :
                new JRubyConstructor(self, new ComposerImpl(new ParserImpl(scn,YAML.config().version("1.0")),new ResolverImpl()))
                ;
            while(ctor.checkData()) {
                block.yield(context, JavaEmbedUtils.javaToRuby(self.getRuntime(),ctor.getData()));
            }
            return self.getRuntime().getNil();
        } catch(YAMLException e) {
            if(self.getRuntime().getDebug().isTrue()) {
                Position.Range range = ((Positionable)e).getRange();
                throw self.getRuntime().newArgumentError("syntax error on " + range.start + ":" + range.end + ": " + e.getMessage());
            } else {
                throw self.getRuntime().newArgumentError("syntax error:" + e.getMessage());
            }
        }
    }

    @JRubyMethod(name = "load_documents", required = 1, frame = true, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject load_documents(IRubyObject self, IRubyObject arg, Block block) {
        boolean debug = self.getRuntime().getDebug().isTrue();
        ThreadContext context = self.getRuntime().getCurrentContext();
        IRubyObject io = check_yaml_port(arg);
        Scanner scn = null;
        try {
            if(io instanceof RubyString) {
                scn = debug ? new PositioningScannerImpl(((RubyString)io).getByteList()) : new ScannerImpl(((RubyString)io).getByteList());
            } else {
                scn = debug ? new PositioningScannerImpl(new IOInputStream(io)) : new ScannerImpl(new IOInputStream(io));
            }
            Constructor ctor = 
                debug ?
                new JRubyConstructor(self, new PositioningComposerImpl(new PositioningParserImpl((PositioningScanner)scn,YAML.config().version("1.0")),new ResolverImpl())) :
                new JRubyConstructor(self, new ComposerImpl(new ParserImpl(scn,YAML.config().version("1.0")),new ResolverImpl()))
                ;
            while(ctor.checkData()) {
                block.yield(context, JavaEmbedUtils.javaToRuby(self.getRuntime(),ctor.getData()));
            }
            return self.getRuntime().getNil();
        } catch(YAMLException e) {
            if(self.getRuntime().getDebug().isTrue()) {
                Position.Range range = ((Positionable)e).getRange();
                throw self.getRuntime().newArgumentError("syntax error on " + range.start + ":" + range.end + ": " + e.getMessage());
            } else {
                throw self.getRuntime().newArgumentError("syntax error:" + e.getMessage());
            }
        }
    }

    @JRubyMethod(name = "load_stream", required = 1, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject load_stream(IRubyObject self, IRubyObject arg) {
        boolean debug = self.getRuntime().getDebug().isTrue();
        ThreadContext context = self.getRuntime().getCurrentContext();
        IRubyObject d = self.getRuntime().getNil();
        IRubyObject io = arg;
        Scanner scn = null;
        try {
            if(io instanceof RubyString) {
                scn = debug ? new PositioningScannerImpl(((RubyString)io).getByteList()) : new ScannerImpl(((RubyString)io).getByteList());
            } else {
                scn = debug ? new PositioningScannerImpl(new IOInputStream(io)) : new ScannerImpl(new IOInputStream(io));
            }
            Constructor ctor = 
                debug ?
                new JRubyConstructor(self, new PositioningComposerImpl(new PositioningParserImpl((PositioningScanner)scn,YAML.config().version("1.0")),new ResolverImpl())) :
                new JRubyConstructor(self, new ComposerImpl(new ParserImpl(scn,YAML.config().version("1.0")),new ResolverImpl()))
                ;
            while(ctor.checkData()) {
                if(d.isNil()) {
                    d = self.getRuntime().fastGetModule("YAML").fastGetClass("Stream").callMethod(context,"new", d);
                }
                d.callMethod(context,"add", JavaEmbedUtils.javaToRuby(self.getRuntime(),ctor.getData()));
            }
            return d;
        } catch(YAMLException e) {
            if(self.getRuntime().getDebug().isTrue()) {
                Position.Range range = ((Positionable)e).getRange();
                throw self.getRuntime().newArgumentError("syntax error on " + range.start + ":" + range.end + ": " + e.getMessage());
            } else {
                throw self.getRuntime().newArgumentError("syntax error:" + e.getMessage());
            }
        }
    }

    @JRubyMethod(name = "dump_stream", rest = true, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject dump_stream(IRubyObject self, IRubyObject[] args) {
        ThreadContext context = self.getRuntime().getCurrentContext();
        IRubyObject stream = self.getRuntime().fastGetModule("YAML").fastGetClass("Stream").callMethod(context, "new");
        for(int i=0,j=args.length;i<j;i++) {
            stream.callMethod(context,"add", args[i]);
        }
        return stream.callMethod(context, "emit");
    }

    @JRubyMethod(name = "quick_emit_node", required = 1, rest = true, frame = true, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject quick_emit_node(IRubyObject self, IRubyObject[] args, Block block) {
        return block.yield(self.getRuntime().getCurrentContext(), args[0]);
    }

//    @JRubyMethod(name = "quick_emit_node", rest = true, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject quick_emit(IRubyObject self, IRubyObject[] args) {
        return self.getRuntime().getNil();
    }
    
    // prepares IO port type for load (ported from ext/syck/rubyext.c)
    private static IRubyObject check_yaml_port(IRubyObject port) {
        if (port instanceof RubyString) {
            // OK
        }
        else if (port.respondsTo("read")) {
            if (port.respondsTo("binmode")) {
                ThreadContext context = port.getRuntime().getCurrentContext();
                port.callMethod(context, "binmode");
            }
        }
        else {
            throw port.getRuntime().newTypeError("instance of IO needed");
        }
        return port;
    }

    @JRubyClass(name="Hash")
    public static class YAMLHashMethods {
        @JRubyMethod(name = "to_yaml_node", required = 1)
        public static IRubyObject hash_to_yaml_node(IRubyObject self, IRubyObject arg) {
            Ruby runtime = self.getRuntime();
            ThreadContext context = runtime.getCurrentContext();
            return RuntimeHelpers.invoke(context, arg, "map", self.callMethod(context, "taguri"), self, self.callMethod(context, "to_yaml_style"));
        }
    }   

    @JRubyClass(name="Object")
    public static class YAMLObjectMethods {
        @JRubyMethod(name = "to_yaml_properties")
        public static IRubyObject obj_to_yaml_properties(IRubyObject self) {
            ThreadContext context = self.getRuntime().getCurrentContext();
            return self.callMethod(context, "instance_variables").callMethod(context, "sort");
        }
        @JRubyMethod(name = "to_yaml_style")
        public static IRubyObject obj_to_yaml_style(IRubyObject self) {
            return self.getRuntime().getNil();
        }
        @JRubyMethod(name = "to_yaml_node", required = 1)
        public static IRubyObject obj_to_yaml_node(IRubyObject self, IRubyObject arg) {
            ThreadContext context = self.getRuntime().getCurrentContext();
            Map mep = (Map)(new RubyHash(self.getRuntime()));
            RubyArray props = (RubyArray)self.callMethod(context, "to_yaml_properties");
            for(Iterator iter = props.getList().iterator(); iter.hasNext();) {
                String m = iter.next().toString();
                mep.put(self.getRuntime().newString(m.substring(1)), self.callMethod(context,"instance_variable_get", self.getRuntime().newString(m)));
            }
            return RuntimeHelpers.invoke(context, arg, "map", self.callMethod(context, "taguri"), (IRubyObject)mep, self.callMethod(context, "to_yaml_style"));
        }
        @JRubyMethod(name = "to_yaml", rest = true)
        public static IRubyObject obj_to_yaml(IRubyObject self, IRubyObject[] args) {
            ThreadContext context = self.getRuntime().getCurrentContext();
            return self.getRuntime().fastGetModule("YAML").callMethod(context,"dump", self);
        }
        @JRubyMethod(name = "taguri")
        public static IRubyObject obj_taguri(IRubyObject self) {
            return self.getRuntime().newString("!ruby/object:" + self.getType().getName());
        }
    }
    
    @JRubyClass(name="Class")
    public static class YAMLClassMethods {
        @JRubyMethod(name = "to_yaml", rest = true)
        public static IRubyObject class_to_yaml(IRubyObject self, IRubyObject[] args) {
            throw self.getRuntime().newTypeError("can't dump anonymous class " + self.getType().getName());
        }
    }
    
    @JRubyClass(name="Array")
    public static class YAMLArrayMethods {
        @JRubyMethod(name = "to_yaml_node", required = 1)
        public static IRubyObject array_to_yaml_node(IRubyObject self, IRubyObject arg) {
            ThreadContext context = self.getRuntime().getCurrentContext();
            return RuntimeHelpers.invoke(context, arg, "seq", self.callMethod(context, "taguri"), self, self.callMethod(context, "to_yaml_style"));
        }
    }

    @JRubyClass(name="Struct")
    public static class YAMLStructMethods {
        @JRubyMethod(name = "to_yaml_node", required = 1)
        public static IRubyObject struct_to_yaml_node(IRubyObject self, IRubyObject arg) {
            ThreadContext context = self.getRuntime().getCurrentContext();
            Map mep = (Map)(new RubyHash(self.getRuntime()));
            for(Iterator iter = ((RubyArray)self.callMethod(context, "members")).getList().iterator();iter.hasNext();) {
                IRubyObject key = self.getRuntime().newString(iter.next().toString());
                mep.put(key,self.callMethod(context,MethodIndex.AREF, "[]", key));
            }            
            for(Iterator iter = ((RubyArray)self.callMethod(context, "to_yaml_properties")).getList().iterator(); iter.hasNext();) {
                String m = iter.next().toString();
                mep.put(self.getRuntime().newString(m.substring(1)), self.callMethod(context,"instance_variable_get", self.getRuntime().newString(m)));
            }
            return RuntimeHelpers.invoke(context, arg, "map", self.callMethod(context, "taguri"), (IRubyObject)mep, self.callMethod(context, "to_yaml_style"));
        }
        @JRubyMethod(name = "taguri")
        public static IRubyObject struct_taguri(IRubyObject self) {
            return self.getRuntime().newString("!ruby/struct:" + self.getType().getName());
        }
    }
    
    @JRubyClass(name="Exception")
    public static class YAMLExceptionMethods {
        @JRubyMethod(name = "to_yaml_node", required = 1)
        public static IRubyObject exception_to_yaml_node(IRubyObject self, IRubyObject arg) {
            ThreadContext context = self.getRuntime().getCurrentContext();
            Map mep = (Map)(new RubyHash(self.getRuntime()));
            mep.put(self.getRuntime().newString("message"),self.callMethod(context, "message"));
            for(Iterator iter = ((RubyArray)self.callMethod(context, "to_yaml_properties")).getList().iterator(); iter.hasNext();) {
                String m = iter.next().toString();
                mep.put(self.getRuntime().newString(m.substring(1)), self.callMethod(context,"instance_variable_get", self.getRuntime().newString(m)));
            }
            return RuntimeHelpers.invoke(context, arg,"map", self.callMethod(context, "taguri"), (IRubyObject)mep, self.callMethod(context, "to_yaml_style"));
        }
        @JRubyMethod(name = "taguri")
        public static IRubyObject exception_taguri(IRubyObject self) {
            return self.getRuntime().newString("!ruby/exception:" + self.getType().getName());
        }
    }
    
    private static final Pattern AFTER_NEWLINE = Pattern.compile("\n.+", Pattern.DOTALL);
    @JRubyClass(name="String")
    public static class YAMLStringMethods {
        @JRubyMethod(name = "is_complex_yaml?")
        public static IRubyObject string_is_complex(IRubyObject self) {
            ThreadContext context = self.getRuntime().getCurrentContext();
            return (self.callMethod(context, "to_yaml_style").isTrue() ||
                    ((List)self.callMethod(context, "to_yaml_properties")).isEmpty() ||
                    AFTER_NEWLINE.matcher(self.toString()).find()) ? self.getRuntime().getTrue() : self.getRuntime().getFalse();
        }
        @JRubyMethod(name = "is_binary_data?")
        public static IRubyObject string_is_binary(IRubyObject self) {
            ThreadContext context = self.getRuntime().getCurrentContext();
            if(self.callMethod(context, MethodIndex.EMPTY_P, "empty?").isTrue()) {
                return self.getRuntime().getNil();
            }
            return self.toString().indexOf('\0') != -1 ? self.getRuntime().getTrue() : self.getRuntime().getFalse();
        }
        private static JRubyRepresenter into(IRubyObject arg) {
            IRubyObject jobj = arg.getInstanceVariables().fastGetInstanceVariable("@java_object");
            if(jobj != null) {
                return (JRubyRepresenter)(((org.jruby.javasupport.JavaObject)jobj).getValue());
            }
            return null;
        }
        @JRubyMethod(name = "to_yaml_node", required = 1)
        public static IRubyObject string_to_yaml_node(IRubyObject self, IRubyObject arg) {
            ThreadContext context = self.getRuntime().getCurrentContext();
            Ruby rt = self.getRuntime();
            if(self.callMethod(context, "is_binary_data?").isTrue()) {
                return RuntimeHelpers.invoke(context, arg, "scalar", rt.newString("tag:yaml.org,2002:binary"), rt.newArray(self).callMethod(context, "pack", rt.newString("m")), rt.newString("|"));
            }
            if(((List)self.callMethod(context, "to_yaml_properties")).isEmpty()) {
                JRubyRepresenter rep = into(arg);
                if(rep != null) {
                    try {
                        return JavaUtil.convertJavaToRuby(rt,rep.scalar(self.callMethod(context, "taguri").toString(),self.convertToString().getByteList(),self.toString().startsWith(":") ? "\"" : self.callMethod(context, "to_yaml_style").toString()));
                    } catch(IOException e) {
                        throw rt.newIOErrorFromException(e);
                    }
                } else {
                    return RuntimeHelpers.invoke(context, arg, "scalar", self.callMethod(context, "taguri"), self, self.toString().startsWith(":") ? rt.newString("\"") : self.callMethod(context, "to_yaml_style"));
                }
            }

            Map mep = (Map)(new RubyHash(self.getRuntime()));
            mep.put(self.getRuntime().newString("str"),rt.newString(self.toString()));
            for(Iterator iter = ((RubyArray)self.callMethod(context, "to_yaml_properties")).getList().iterator(); iter.hasNext();) {
                String m = iter.next().toString();
                mep.put(self.getRuntime().newString(m), self.callMethod(context,"instance_variable_get", self.getRuntime().newString(m)));
            }
            return RuntimeHelpers.invoke(context, arg, "map", self.callMethod(context, "taguri"), (IRubyObject)mep, self.callMethod(context, "to_yaml_style"));
        }
    }
    
    @JRubyClass(name="Symbol")
    public static class YAMLSymbolMethods {
        @JRubyMethod(name = "to_yaml_node", required = 1)
        public static IRubyObject symbol_to_yaml_node(IRubyObject self, IRubyObject arg) {
            ThreadContext context = self.getRuntime().getCurrentContext();
            return RuntimeHelpers.invoke(context, arg, "scalar", self.callMethod(context, "taguri"), self.callMethod(context, "inspect"), self.callMethod(context, "to_yaml_style"));
        }
        @JRubyMethod(name = "taguri")
        public static IRubyObject symbol_taguri(IRubyObject self) {
            return self.getRuntime().newString("tag:yaml.org,2002:str");
        }
    }
    
    @JRubyClass(name="Numeric")
    public static class YAMLNumericMethods {
        @JRubyMethod(name = "to_yaml_node", required = 1)
        public static IRubyObject numeric_to_yaml_node(IRubyObject self, IRubyObject arg) {
            ThreadContext context = self.getRuntime().getCurrentContext();
            String val = self.toString();
            if("Infinity".equals(val)) {
                val = ".Inf";
            } else if("-Infinity".equals(val)) {
                val = "-.Inf";
            } else if("NaN".equals(val)) {
                val = ".NaN";
            }
            return RuntimeHelpers.invoke(context, arg,"scalar", self.callMethod(context, "taguri"), self.getRuntime().newString(val), self.callMethod(context, "to_yaml_style"));
        }
    }

    @JRubyClass(name="Range")
    public static class YAMLRangeMethods {
        @JRubyMethod(name = "to_yaml_node", required = 1)
        public static IRubyObject range_to_yaml_node(IRubyObject self, IRubyObject arg) {
            ThreadContext context = self.getRuntime().getCurrentContext();
            Map mep = (Map)(new RubyHash(self.getRuntime()));
            mep.put(self.getRuntime().newString("begin"),self.callMethod(context, "begin"));
            mep.put(self.getRuntime().newString("end"),self.callMethod(context, "end"));
            mep.put(self.getRuntime().newString("excl"),self.callMethod(context, "exclude_end?"));
            for(Iterator iter = ((RubyArray)self.callMethod(context, "to_yaml_properties")).getList().iterator(); iter.hasNext();) {
                String m = iter.next().toString();
                mep.put(self.getRuntime().newString(m.substring(1)), self.callMethod(context,"instance_variable_get", self.getRuntime().newString(m)));
            }
            return RuntimeHelpers.invoke(context, arg, "map", self.callMethod(context, "taguri"), (IRubyObject)mep, self.callMethod(context, "to_yaml_style"));
        }
    }
    
    @JRubyClass(name="Regexp")
    public static class YAMLRegexpMethods {
        @JRubyMethod(name = "to_yaml_node", required = 1)
        public static IRubyObject regexp_to_yaml_node(IRubyObject self, IRubyObject arg) {
            ThreadContext context = self.getRuntime().getCurrentContext();
            return RuntimeHelpers.invoke(context, arg, "scalar", self.callMethod(context, "taguri"), self.callMethod(context, "inspect"), self.callMethod(context, "to_yaml_style"));
        }
    }
    
    @JRubyClass(name="Time")
    public static class YAMLTimeMethods {
        @JRubyMethod(name = "to_yaml_node", required = 1)
        public static IRubyObject time_to_yaml_node(IRubyObject self, IRubyObject arg) {
            ThreadContext context = self.getRuntime().getCurrentContext();
            IRubyObject tz = self.getRuntime().newString("Z");
            IRubyObject difference_sign = self.getRuntime().newString("-");
            self = self.dup();
            if(!self.callMethod(context, "utc?").isTrue()) {
                IRubyObject utc_same_instant = self.callMethod(context, "utc");
                IRubyObject utc_same_writing = RuntimeHelpers.invoke(context, self.getRuntime().getTime(), "utc", new IRubyObject[]{
                        self.callMethod(context, "year"),self.callMethod(context, "month"),self.callMethod(context, "day"),self.callMethod(context, "hour"),
                        self.callMethod(context, "min"),self.callMethod(context, "sec"),self.callMethod(context, "usec")});
                IRubyObject difference_to_utc = utc_same_writing.callMethod(context,MethodIndex.OP_MINUS, "-", utc_same_instant);
                IRubyObject absolute_difference;
                if(difference_to_utc.callMethod(context,MethodIndex.OP_LT, "<", RubyFixnum.zero(self.getRuntime())).isTrue()) {
                    difference_sign = self.getRuntime().newString("-");
                    absolute_difference = RubyFixnum.zero(self.getRuntime()).callMethod(context,MethodIndex.OP_MINUS, "-", difference_to_utc);
                } else {
                    difference_sign = self.getRuntime().newString("+");
                    absolute_difference = difference_to_utc;
                }
                IRubyObject difference_minutes = absolute_difference.callMethod(context,"/", self.getRuntime().newFixnum(60)).callMethod(context, "round");
                tz = self.getRuntime().newString("%s%02d:%02d").callMethod(context,"%", self.getRuntime().newArrayNoCopy(new IRubyObject[]{difference_sign,difference_minutes.callMethod(context,"/", self.getRuntime().newFixnum(60)),difference_minutes.callMethod(context,"%", self.getRuntime().newFixnum(60))}));
            }
            IRubyObject standard = self.callMethod(context,"strftime", self.getRuntime().newString("%Y-%m-%d %H:%M:%S"));
            if(self.callMethod(context, "usec").callMethod(context, "nonzero?").isTrue()) {
                standard = standard.callMethod(context, MethodIndex.OP_PLUS, "+", self.getRuntime().newString(".%06d").callMethod(context,"%", self.getRuntime().newArray(self.callMethod(context, "usec"))));
            }
            standard = standard.callMethod(context,MethodIndex.OP_PLUS, "+", self.getRuntime().newString(" %s").callMethod(context,"%", self.getRuntime().newArray(tz)));
            return RuntimeHelpers.invoke(context, arg, "scalar", self.callMethod(context, "taguri"), standard, self.callMethod(context, "to_yaml_style"));
        }
    }
    
    @JRubyClass(name="Date")
    public static class YAMLDateMethods {
        @JRubyMethod(name = "to_yaml_node", required = 1)
        public static IRubyObject date_to_yaml_node(IRubyObject self, IRubyObject arg) {
            ThreadContext context = self.getRuntime().getCurrentContext();
            return RuntimeHelpers.invoke(context, arg, "scalar", self.callMethod(context, "taguri"), self.callMethod(context, MethodIndex.TO_S, "to_s"), self.callMethod(context, "to_yaml_style"));
        }
    }
    
    @JRubyClass(name="TrueClass")
    public static class YAMLTrueMethods {
        @JRubyMethod(name = "to_yaml_node", required = 1)
        public static IRubyObject true_to_yaml_node(IRubyObject self, IRubyObject arg) {
            ThreadContext context = self.getRuntime().getCurrentContext();
            return RuntimeHelpers.invoke(context, arg, "scalar", self.callMethod(context, "taguri"), self.callMethod(context, MethodIndex.TO_S, "to_s"), self.callMethod(context, "to_yaml_style"));
        }
        @JRubyMethod(name = "taguri")
        public static IRubyObject true_taguri(IRubyObject self) {
            return self.getRuntime().newString("tag:yaml.org,2002:bool");
        }
    }
    
    @JRubyClass(name="FalseClass")
    public static class YAMLFalseMethods {
        @JRubyMethod(name = "to_yaml_node", required = 1)
        public static IRubyObject false_to_yaml_node(IRubyObject self, IRubyObject arg) {
            ThreadContext context = self.getRuntime().getCurrentContext();
            return RuntimeHelpers.invoke(context, arg, "scalar", self.callMethod(context, "taguri"), self.callMethod(context, MethodIndex.TO_S, "to_s"), self.callMethod(context, "to_yaml_style"));
        }
        @JRubyMethod(name = "taguri")
        public static IRubyObject false_taguri(IRubyObject self) {
            return self.getRuntime().newString("tag:yaml.org,2002:bool");
        }
    }
    
    @JRubyClass(name="NilClass")
    public static class YAMLNilMethods {
        @JRubyMethod(name = "to_yaml_node", required = 1)
        public static IRubyObject nil_to_yaml_node(IRubyObject self, IRubyObject arg) {
            ThreadContext context = self.getRuntime().getCurrentContext();
            return RuntimeHelpers.invoke(context, arg,"scalar", self.callMethod(context, "taguri"), self.getRuntime().newString(""), self.callMethod(context, "to_yaml_style"));
        }
    }
}// RubyYAML
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2006 Ola Bini <ola@ologix.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import java.io.InputStream;
import java.io.IOException;

import java.util.List;
import java.util.ArrayList;

import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import org.jruby.anno.FrameField;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyModule;

import org.jruby.exceptions.RaiseException;
import org.jruby.javasupport.util.RuntimeHelpers;
import org.jruby.runtime.Arity;

import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;

import org.jruby.util.IOInputStream;
import org.jruby.util.IOOutputStream;
import org.jruby.util.CRC32Ext;
import org.jruby.util.Adler32Ext;
import org.jruby.util.ZlibInflate;
import org.jruby.util.ZlibDeflate;

import org.jruby.util.ByteList;

@JRubyModule(name="Zlib")
public class RubyZlib {
    /** Create the Zlib module and add it to the Ruby runtime.
     * 
     */
    public static RubyModule createZlibModule(Ruby runtime) {
        RubyModule result = runtime.defineModule("Zlib");

        RubyClass gzfile = result.defineClassUnder("GzipFile", runtime.getObject(), RubyGzipFile.GZIPFILE_ALLOCATOR);
        gzfile.defineAnnotatedMethods(RubyGzipFile.class);
        
        RubyClass gzreader = result.defineClassUnder("GzipReader", gzfile, RubyGzipReader.GZIPREADER_ALLOCATOR);
        gzreader.includeModule(runtime.getEnumerable());
        gzreader.defineAnnotatedMethods(RubyGzipReader.class);
        
        RubyClass standardError = runtime.getStandardError();
        RubyClass zlibError = result.defineClassUnder("Error", standardError, standardError.getAllocator());
        gzreader.defineClassUnder("Error", zlibError, zlibError.getAllocator());

        RubyClass gzwriter = result.defineClassUnder("GzipWriter", gzfile, RubyGzipWriter.GZIPWRITER_ALLOCATOR);
        gzwriter.defineAnnotatedMethods(RubyGzipWriter.class);

        result.defineConstant("ZLIB_VERSION",runtime.newString("1.2.1"));
        result.defineConstant("VERSION",runtime.newString("0.6.0"));

        result.defineConstant("BINARY",runtime.newFixnum(0));
        result.defineConstant("ASCII",runtime.newFixnum(1));
        result.defineConstant("UNKNOWN",runtime.newFixnum(2));

        result.defineConstant("DEF_MEM_LEVEL",runtime.newFixnum(8));
        result.defineConstant("MAX_MEM_LEVEL",runtime.newFixnum(9));

        result.defineConstant("OS_UNIX",runtime.newFixnum(3));
        result.defineConstant("OS_UNKNOWN",runtime.newFixnum(255));
        result.defineConstant("OS_CODE",runtime.newFixnum(11));
        result.defineConstant("OS_ZSYSTEM",runtime.newFixnum(8));
        result.defineConstant("OS_VMCMS",runtime.newFixnum(4));
        result.defineConstant("OS_VMS",runtime.newFixnum(2));
        result.defineConstant("OS_RISCOS",runtime.newFixnum(13));
        result.defineConstant("OS_MACOS",runtime.newFixnum(7));
        result.defineConstant("OS_OS2",runtime.newFixnum(6));
        result.defineConstant("OS_AMIGA",runtime.newFixnum(1));
        result.defineConstant("OS_QDOS",runtime.newFixnum(12));
        result.defineConstant("OS_WIN32",runtime.newFixnum(11));
        result.defineConstant("OS_ATARI",runtime.newFixnum(5));
        result.defineConstant("OS_MSDOS",runtime.newFixnum(0));
        result.defineConstant("OS_CPM",runtime.newFixnum(9));
        result.defineConstant("OS_TOPS20",runtime.newFixnum(10));

        result.defineConstant("DEFAULT_STRATEGY",runtime.newFixnum(0));
        result.defineConstant("FILTERED",runtime.newFixnum(1));
        result.defineConstant("HUFFMAN_ONLY",runtime.newFixnum(2));

        result.defineConstant("NO_FLUSH",runtime.newFixnum(0));
        result.defineConstant("SYNC_FLUSH",runtime.newFixnum(2));
        result.defineConstant("FULL_FLUSH",runtime.newFixnum(3));
        result.defineConstant("FINISH",runtime.newFixnum(4));

        result.defineConstant("NO_COMPRESSION",runtime.newFixnum(0));
        result.defineConstant("BEST_SPEED",runtime.newFixnum(1));
        result.defineConstant("DEFAULT_COMPRESSION",runtime.newFixnum(-1));
        result.defineConstant("BEST_COMPRESSION",runtime.newFixnum(9));

        result.defineConstant("MAX_WBITS",runtime.newFixnum(15));

        result.defineAnnotatedMethods(RubyZlib.class);

        result.defineClassUnder("StreamEnd",zlibError, zlibError.getAllocator());
        result.defineClassUnder("StreamError",zlibError, zlibError.getAllocator());
        result.defineClassUnder("BufError",zlibError, zlibError.getAllocator());
        result.defineClassUnder("NeedDict",zlibError, zlibError.getAllocator());
        result.defineClassUnder("MemError",zlibError, zlibError.getAllocator());
        result.defineClassUnder("VersionError",zlibError, zlibError.getAllocator());
        result.defineClassUnder("DataError",zlibError, zlibError.getAllocator());

        RubyClass gzError = gzfile.defineClassUnder("Error",zlibError, zlibError.getAllocator());
        gzfile.defineClassUnder("CRCError",gzError, gzError.getAllocator());
        gzfile.defineClassUnder("NoFooter",gzError, gzError.getAllocator());
        gzfile.defineClassUnder("LengthError",gzError, gzError.getAllocator());

        // ZStream actually *isn't* allocatable
        RubyClass zstream = result.defineClassUnder("ZStream", runtime.getObject(), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
        zstream.defineAnnotatedMethods(ZStream.class);
        zstream.undefineMethod("new");

        RubyClass infl = result.defineClassUnder("Inflate", zstream, Inflate.INFLATE_ALLOCATOR);
        infl.defineAnnotatedMethods(Inflate.class);

        RubyClass defl = result.defineClassUnder("Deflate", zstream, Deflate.DEFLATE_ALLOCATOR);
        defl.defineAnnotatedMethods(Deflate.class);

        runtime.getKernel().callMethod(runtime.getCurrentContext(),"require",runtime.newString("stringio"));

        return result;
    }

    @JRubyClass(name="Zlib::Error", parent="StandardError")
    public static class Error {}
    @JRubyClass(name="Zlib::StreamEnd", parent="Zlib::Error")
    public static class StreamEnd extends Error {}
    @JRubyClass(name="Zlib::StreamError", parent="Zlib::Error")
    public static class StreamError extends Error {}
    @JRubyClass(name="Zlib::BufError", parent="Zlib::Error")
    public static class BufError extends Error {}
    @JRubyClass(name="Zlib::NeedDict", parent="Zlib::Error")
    public static class NeedDict extends Error {}
    @JRubyClass(name="Zlib::MemError", parent="Zlib::Error")
    public static class MemError extends Error {}
    @JRubyClass(name="Zlib::VersionError", parent="Zlib::Error")
    public static class VersionError extends Error {}
    @JRubyClass(name="Zlib::DataError", parent="Zlib::Error")
    public static class DataError extends Error {}

    @JRubyMethod(name = "zlib_version", module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject zlib_version(IRubyObject recv) {
        return ((RubyModule)recv).fastGetConstant("ZLIB_VERSION");
    }

    @JRubyMethod(name = "version", module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject version(IRubyObject recv) {
        return ((RubyModule)recv).fastGetConstant("VERSION");
    }

    @JRubyMethod(name = "crc32", optional = 2, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject crc32(IRubyObject recv, IRubyObject[] args) throws Exception {
        args = Arity.scanArgs(recv.getRuntime(),args,0,2);
        long crc = 0;
        ByteList bytes = null;
        
        if (!args[0].isNil()) bytes = args[0].convertToString().getByteList();
        if (!args[1].isNil()) crc = RubyNumeric.num2long(args[1]);

        CRC32Ext ext = new CRC32Ext((int)crc);
        if (bytes != null) {
            ext.update(bytes.unsafeBytes(), bytes.begin(), bytes.length());
        }
        
        return recv.getRuntime().newFixnum(ext.getValue());
    }

    @JRubyMethod(name = "adler32", optional = 2, module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject adler32(IRubyObject recv, IRubyObject[] args) throws Exception {
        args = Arity.scanArgs(recv.getRuntime(),args,0,2);
        int adler = 1;
        ByteList bytes = null;
        if (!args[0].isNil()) bytes = args[0].convertToString().getByteList();
        if (!args[1].isNil()) adler = RubyNumeric.fix2int(args[1]);

        Adler32Ext ext = new Adler32Ext(adler);
        if (bytes != null) {
            ext.update(bytes.unsafeBytes(), bytes.begin(), bytes.length()); // it's safe since adler.update doesn't modify the array
        }
        return recv.getRuntime().newFixnum(ext.getValue());
    }

    private final static long[] crctab = new long[]{
        0L, 1996959894L, 3993919788L, 2567524794L, 124634137L, 1886057615L, 3915621685L, 2657392035L, 249268274L, 2044508324L, 3772115230L, 2547177864L, 162941995L, 
        2125561021L, 3887607047L, 2428444049L, 498536548L, 1789927666L, 4089016648L, 2227061214L, 450548861L, 1843258603L, 4107580753L, 2211677639L, 325883990L, 
        1684777152L, 4251122042L, 2321926636L, 335633487L, 1661365465L, 4195302755L, 2366115317L, 997073096L, 1281953886L, 3579855332L, 2724688242L, 1006888145L, 
        1258607687L, 3524101629L, 2768942443L, 901097722L, 1119000684L, 3686517206L, 2898065728L, 853044451L, 1172266101L, 3705015759L, 2882616665L, 651767980L, 
        1373503546L, 3369554304L, 3218104598L, 565507253L, 1454621731L, 3485111705L, 3099436303L, 671266974L, 1594198024L, 3322730930L, 2970347812L, 795835527L, 
        1483230225L, 3244367275L, 3060149565L, 1994146192L, 31158534L, 2563907772L, 4023717930L, 1907459465L, 112637215L, 2680153253L, 3904427059L, 2013776290L, 
        251722036L, 2517215374L, 3775830040L, 2137656763L, 141376813L, 2439277719L, 3865271297L, 1802195444L, 476864866L, 2238001368L, 4066508878L, 1812370925L, 
        453092731L, 2181625025L, 4111451223L, 1706088902L, 314042704L, 2344532202L, 4240017532L, 1658658271L, 366619977L, 2362670323L, 4224994405L, 1303535960L, 
        984961486L, 2747007092L, 3569037538L, 1256170817L, 1037604311L, 2765210733L, 3554079995L, 1131014506L, 879679996L, 2909243462L, 3663771856L, 1141124467L, 
        855842277L, 2852801631L, 3708648649L, 1342533948L, 654459306L, 3188396048L, 3373015174L, 1466479909L, 544179635L, 3110523913L, 3462522015L, 1591671054L, 
        702138776L, 2966460450L, 3352799412L, 1504918807L, 783551873L, 3082640443L, 3233442989L, 3988292384L, 2596254646L, 62317068L, 1957810842L, 3939845945L, 
        2647816111L, 81470997L, 1943803523L, 3814918930L, 2489596804L, 225274430L, 2053790376L, 3826175755L, 2466906013L, 167816743L, 2097651377L, 4027552580L, 
        2265490386L, 503444072L, 1762050814L, 4150417245L, 2154129355L, 426522225L, 1852507879L, 4275313526L, 2312317920L, 282753626L, 1742555852L, 4189708143L, 
        2394877945L, 397917763L, 1622183637L, 3604390888L, 2714866558L, 953729732L, 1340076626L, 3518719985L, 2797360999L, 1068828381L, 1219638859L, 3624741850L, 
        2936675148L, 906185462L, 1090812512L, 3747672003L, 2825379669L, 829329135L, 1181335161L, 3412177804L, 3160834842L, 628085408L, 1382605366L, 3423369109L, 
        3138078467L, 570562233L, 1426400815L, 3317316542L, 2998733608L, 733239954L, 1555261956L, 3268935591L, 3050360625L, 752459403L, 1541320221L, 2607071920L, 
        3965973030L, 1969922972L, 40735498L, 2617837225L, 3943577151L, 1913087877L, 83908371L, 2512341634L, 3803740692L, 2075208622L, 213261112L, 2463272603L, 
        3855990285L, 2094854071L, 198958881L, 2262029012L, 4057260610L, 1759359992L, 534414190L, 2176718541L, 4139329115L, 1873836001L, 414664567L, 2282248934L, 
        4279200368L, 1711684554L, 285281116L, 2405801727L, 4167216745L, 1634467795L, 376229701L, 2685067896L, 3608007406L, 1308918612L, 956543938L, 2808555105L, 
        3495958263L, 1231636301L, 1047427035L, 2932959818L, 3654703836L, 1088359270L, 936918000L, 2847714899L, 3736837829L, 1202900863L, 817233897L, 3183342108L, 
        3401237130L, 1404277552L, 615818150L, 3134207493L, 3453421203L, 1423857449L, 601450431L, 3009837614L, 3294710456L, 1567103746L, 711928724L, 3020668471L, 
        3272380065L, 1510334235L, 755167117};

    @JRubyMethod(name = "crc_table", module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject crc_table(IRubyObject recv) {
        List<IRubyObject> ll = new ArrayList<IRubyObject>(crctab.length);
        for(int i=0;i<crctab.length;i++) {
            ll.add(recv.getRuntime().newFixnum(crctab[i]));
        }
        return recv.getRuntime().newArray(ll);
    }


    @JRubyClass(name="Zlib::ZStream")
    public static abstract class ZStream extends RubyObject {
        protected boolean closed = false;
        protected boolean ended = false;
        protected boolean finished = false;

        protected abstract int internalTotalOut();
        protected abstract boolean internalStreamEndP();
        protected abstract void internalEnd();
        protected abstract void internalReset();
        protected abstract int internalAdler();
        protected abstract IRubyObject internalFinish() throws Exception;
        protected abstract int internalTotalIn();
        protected abstract void internalClose();

        public ZStream(Ruby runtime, RubyClass type) {
            super(runtime, type);
        }

        @JRubyMethod(name = "initialize", frame = true, visibility = Visibility.PRIVATE)
        public IRubyObject initialize(Block unusedBlock) {
            return this;
        }

        @JRubyMethod(name = "flust_next_out")
        public IRubyObject flush_next_out() {
            return getRuntime().getNil();
        }

        @JRubyMethod(name = "total_out")
        public IRubyObject total_out() {
            return getRuntime().newFixnum(internalTotalOut());
        }

        @JRubyMethod(name = "stream_end?")
        public IRubyObject stream_end_p() {
            return internalStreamEndP() ? getRuntime().getTrue() : getRuntime().getFalse();
        }

        @JRubyMethod(name = "data_type")
        public IRubyObject data_type() {
            return getRuntime().fastGetModule("Zlib").fastGetConstant("UNKNOWN");
        }

        @JRubyMethod(name = "closed?")
        public IRubyObject closed_p() {
            return closed ? getRuntime().getTrue() : getRuntime().getFalse();
        }

        @JRubyMethod(name = "ended?")
        public IRubyObject ended_p() {
            return ended ? getRuntime().getTrue() : getRuntime().getFalse();
        }

        @JRubyMethod(name = "end")
        public IRubyObject end() {
            if(!ended) {
                internalEnd();
                ended = true;
            }
            return getRuntime().getNil();
        }

        @JRubyMethod(name = "reset")
        public IRubyObject reset() {
            internalReset();
            return getRuntime().getNil();
        }

        @JRubyMethod(name = "avail_out")
        public IRubyObject avail_out() {
            return RubyFixnum.zero(getRuntime());
        }

        @JRubyMethod(name = "avail_out=", required = 1)
        public IRubyObject set_avail_out(IRubyObject p1) {
            return p1;
        }

        @JRubyMethod(name = "adler")
        public IRubyObject adler() {
            return getRuntime().newFixnum(internalAdler());
        }

        @JRubyMethod(name = "finish")
        public IRubyObject finish() throws Exception {
            if(!finished) {
                finished = true;
                return internalFinish();
            }
            return RubyString.newEmptyString(getRuntime());
        }

        @JRubyMethod(name = "avail_in")
        public IRubyObject avail_in() {
            return RubyFixnum.zero(getRuntime());
        }

        @JRubyMethod(name = "flush_next_in")
        public IRubyObject flush_next_in() {
            return getRuntime().getNil();
        }

        @JRubyMethod(name = "total_in")
        public IRubyObject total_in() {
            return getRuntime().newFixnum(internalTotalIn());
        }

        @JRubyMethod(name = "finished?")
        public IRubyObject finished_p() {
            return finished ? getRuntime().getTrue() : getRuntime().getFalse();
        }

        @JRubyMethod(name = "close")
        public IRubyObject close() {
            if(!closed) {
                internalClose();
                closed = true;
            }
            return getRuntime().getNil();
        }
    }

    @JRubyClass(name="Zlib::Inflate", parent="Zlib::ZStream")
    public static class Inflate extends ZStream {
        protected static final ObjectAllocator INFLATE_ALLOCATOR = new ObjectAllocator() {
            public IRubyObject allocate(Ruby runtime, RubyClass klass) {
                return new Inflate(runtime, klass);
            }
        };

        @JRubyMethod(name = "inflate", required = 1, meta = true)
        public static IRubyObject s_inflate(IRubyObject recv, IRubyObject string) throws Exception {
            return ZlibInflate.s_inflate(recv,string.convertToString().getByteList());
        }

        public Inflate(Ruby runtime, RubyClass type) {
            super(runtime, type);
        }

        private ZlibInflate infl;

        @JRubyMethod(name = "initialize", rest = true, visibility = Visibility.PRIVATE)
        public IRubyObject _initialize(IRubyObject[] args) throws Exception {
            infl = new ZlibInflate(this);
            return this;
        }

        @JRubyMethod(name = "<<", required = 1)
        public IRubyObject append(IRubyObject arg) {
            infl.append(arg);
            return this;
        }

        @JRubyMethod(name = "sync_point?")
        public IRubyObject sync_point_p() {
            return infl.sync_point();
        }

        @JRubyMethod(name = "set_dictionary", required = 1)
        public IRubyObject set_dictionary(IRubyObject arg) throws Exception {
            return infl.set_dictionary(arg);
        }

        @JRubyMethod(name = "inflate", required = 1)
        public IRubyObject inflate(IRubyObject string) throws Exception {
            return infl.inflate(string.convertToString().getByteList());
        }

        @JRubyMethod(name = "sync", required = 1)
        public IRubyObject sync(IRubyObject string) {
            return infl.sync(string);
        }

        protected int internalTotalOut() {
            return infl.getInflater().getTotalOut();
        }

        protected boolean internalStreamEndP() {
            return infl.getInflater().finished();
        }

        protected void internalEnd() {
            infl.getInflater().end();
        }

        protected void internalReset() {
            infl.getInflater().reset();
        }

        protected int internalAdler() {
            return infl.getInflater().getAdler();
        }

        protected IRubyObject internalFinish() throws Exception {
            infl.finish();
            return getRuntime().getNil();
        }

        public IRubyObject finished_p() {
            return infl.getInflater().finished() ? getRuntime().getTrue() : getRuntime().getFalse();
        }

        protected int internalTotalIn() {
            return infl.getInflater().getTotalIn();
        }

        protected void internalClose() {
            infl.close();
        }
    }

    @JRubyClass(name="Zlib::Deflate", parent="Zlib::ZStream")
    public static class Deflate extends ZStream {
        protected static final ObjectAllocator DEFLATE_ALLOCATOR = new ObjectAllocator() {
            public IRubyObject allocate(Ruby runtime, RubyClass klass) {
                return new Deflate(runtime, klass);
            }
        };

        @JRubyMethod(name = "deflate", required = 1, optional = 1, meta = true)
        public static IRubyObject s_deflate(IRubyObject recv, IRubyObject[] args) throws Exception {
            args = Arity.scanArgs(recv.getRuntime(),args,1,1);
            int level = -1;
            if(!args[1].isNil()) {
                level = RubyNumeric.fix2int(args[1]);
            }
            return ZlibDeflate.s_deflate(recv,args[0].convertToString().getByteList(),level);
        }

        public Deflate(Ruby runtime, RubyClass type) {
            super(runtime, type);
        }

        private ZlibDeflate defl;

        @JRubyMethod(name = "initialize", optional = 4, visibility = Visibility.PRIVATE)
        public IRubyObject _initialize(IRubyObject[] args) throws Exception {
            args = Arity.scanArgs(getRuntime(),args,0,4);
            int level = -1;
            int window_bits = 15;
            int memlevel = 8;
            int strategy = 0;
            if(!args[0].isNil()) {
                level = RubyNumeric.fix2int(args[0]);
            }
            if(!args[1].isNil()) {
                window_bits = RubyNumeric.fix2int(args[1]);
            }
            if(!args[2].isNil()) {
                memlevel = RubyNumeric.fix2int(args[2]);
            }
            if(!args[3].isNil()) {
                strategy = RubyNumeric.fix2int(args[3]);
            }
            defl = new ZlibDeflate(this,level,window_bits,memlevel,strategy);
            return this;
        }

        @JRubyMethod(name = "<<", required = 1)
        public IRubyObject append(IRubyObject arg) throws Exception {
            defl.append(arg);
            return this;
        }

        @JRubyMethod(name = "params", required = 2)
        public IRubyObject params(IRubyObject level, IRubyObject strategy) {
            defl.params(RubyNumeric.fix2int(level),RubyNumeric.fix2int(strategy));
            return getRuntime().getNil();
        }

        @JRubyMethod(name = "set_dictionary", required = 1)
        public IRubyObject set_dictionary(IRubyObject arg) throws Exception {
            return defl.set_dictionary(arg);
        }
        
        @JRubyMethod(name = "flush", optional = 1)
        public IRubyObject flush(IRubyObject[] args) throws Exception {
            int flush = 2; // SYNC_FLUSH
            if(args.length == 1) {
                if(!args[0].isNil()) {
                    flush = RubyNumeric.fix2int(args[0]);
                }
            }
            return defl.flush(flush);
        }

        @JRubyMethod(name = "deflate", required = 1, optional = 1)
        public IRubyObject deflate(IRubyObject[] args) throws Exception {
            args = Arity.scanArgs(getRuntime(),args,1,1);
            int flush = 0; // NO_FLUSH
            if(!args[1].isNil()) {
                flush = RubyNumeric.fix2int(args[1]);
            }
            return defl.deflate(args[0].convertToString().getByteList(),flush);
        }

        protected int internalTotalOut() {
            return defl.getDeflater().getTotalOut();
        }

        protected boolean internalStreamEndP() {
            return defl.getDeflater().finished();
        }

        protected void internalEnd() {
            defl.getDeflater().end();
        }

        protected void internalReset() {
            defl.getDeflater().reset();
        }

        protected int internalAdler() {
            return defl.getDeflater().getAdler();
        }

        protected IRubyObject internalFinish() throws Exception {
            return defl.finish();
        }

        protected int internalTotalIn() {
            return defl.getDeflater().getTotalIn();
        }

        protected void internalClose() {
            defl.close();
        }
    }

    @JRubyClass(name="Zlib::GzipFile")
    public static class RubyGzipFile extends RubyObject {
        @JRubyClass(name="Zlib::GzipFile::Error", parent="Zlib::Error")
        public static class Error {}
        @JRubyClass(name="Zlib::GzipFile::CRCError", parent="Zlib::GzipFile::Error")
        public static class CRCError extends Error {}
        @JRubyClass(name="Zlib::GzipFile::NoFooter", parent="Zlib::GzipFile::Error")
        public static class NoFooter extends Error {}
        @JRubyClass(name="Zlib::GzipFile::LengthError", parent="Zlib::GzipFile::Error")
        public static class LengthError extends Error {}

        private static IRubyObject wrap(ThreadContext context, RubyGzipFile instance, 
                IRubyObject io, Block block) throws IOException {
            if (block.isGiven()) {
                try {
                    block.yield(context, instance);
                    
                    return instance.getRuntime().getNil();
                } finally {
                    if (!instance.isClosed()) instance.close();
                }
            }
            
            return io;
        }
        
        @JRubyMethod(name = "wrap", required = 1, frame = true, meta = true)
        public static IRubyObject wrap(ThreadContext context, IRubyObject recv, IRubyObject io, Block block) throws IOException {
            Ruby runtime = recv.getRuntime();
            RubyGzipFile instance;
            
            // TODO: People extending GzipWriter/reader will break.  Find better way here.
            if (recv == runtime.getModule("Zlib").getClass("GzipWriter")) {
                instance = RubyGzipWriter.newGzipWriter(recv, new IRubyObject[] { io }, block);
            } else {
                instance = RubyGzipReader.newInstance(recv, new IRubyObject[] { io }, block);
            }

            return wrap(context, instance, io, block);
        }
        
        protected static final ObjectAllocator GZIPFILE_ALLOCATOR = new ObjectAllocator() {
            public IRubyObject allocate(Ruby runtime, RubyClass klass) {
                return new RubyGzipFile(runtime, klass);
            }
        };

        @JRubyMethod(name = "new", frame = true, meta = true)
        public static RubyGzipFile newInstance(IRubyObject recv, Block block) {
            RubyClass klass = (RubyClass)recv;
            
            RubyGzipFile result = (RubyGzipFile) klass.allocate();
            
            result.callInit(new IRubyObject[0], block);
            
            return result;
        }

        protected boolean closed = false;
        protected boolean finished = false;
        private int os_code = 255;
        private int level = -1;
        private String orig_name;
        private String comment;
        protected IRubyObject realIo;
        private IRubyObject mtime;

        public RubyGzipFile(Ruby runtime, RubyClass type) {
            super(runtime, type);
            mtime = runtime.getNil();
        }
        
        @JRubyMethod(name = "os_code")
        public IRubyObject os_code() {
            return getRuntime().newFixnum(os_code);
        }
        
        @JRubyMethod(name = "closed?")
        public IRubyObject closed_p() {
            return closed ? getRuntime().getTrue() : getRuntime().getFalse();
        }
        
        protected boolean isClosed() {
            return closed;
        }
        
        @JRubyMethod(name = "orig_name")
        public IRubyObject orig_name() {
            return orig_name == null ? getRuntime().getNil() : getRuntime().newString(orig_name);
        }
        
        @JRubyMethod(name = "to_io")
        public IRubyObject to_io() {
            return realIo;
        }
        
        @JRubyMethod(name = "comment")
        public IRubyObject comment() {
            return comment == null ? getRuntime().getNil() : getRuntime().newString(comment);
        }
        
        @JRubyMethod(name = "crc")
        public IRubyObject crc() {
            return RubyFixnum.zero(getRuntime());
        }
        
        @JRubyMethod(name = "mtime")
        public IRubyObject mtime() {
            return mtime;
        }
        
        @JRubyMethod(name = "sync")
        public IRubyObject sync() {
            return getRuntime().getNil();
        }
        
        @JRubyMethod(name = "finish")
        public IRubyObject finish() throws IOException {
            if (!finished) {
                //io.finish();
            }
            finished = true;
            return realIo;
        }

        @JRubyMethod(name = "close")
        public IRubyObject close() throws IOException {
            return null;
        }
        
        @JRubyMethod(name = "level")
        public IRubyObject level() {
            return getRuntime().newFixnum(level);
        }
        
        @JRubyMethod(name = "sync=", required = 1)
        public IRubyObject set_sync(IRubyObject ignored) {
            return getRuntime().getNil();
        }
    }

    @JRubyClass(name="Zlib::GzipReader", parent="Zlib::GzipFile", include="Enumerable")
    public static class RubyGzipReader extends RubyGzipFile {
        @JRubyClass(name="Zlib::GzipReader::Error", parent="Zlib::GzipReader")
        public static class Error {}
        protected static final ObjectAllocator GZIPREADER_ALLOCATOR = new ObjectAllocator() {
            public IRubyObject allocate(Ruby runtime, RubyClass klass) {
                return new RubyGzipReader(runtime, klass);
            }
        };
        
        @JRubyMethod(name = "new", rest = true, frame = true, meta = true)
        public static RubyGzipReader newInstance(IRubyObject recv, IRubyObject[] args, Block block) {
            RubyClass klass = (RubyClass)recv;
            RubyGzipReader result = (RubyGzipReader)klass.allocate();
            result.callInit(args, block);
            return result;
        }

        @JRubyMethod(name = "open", required = 1, frame = true, meta = true)
        public static IRubyObject open(final ThreadContext context, IRubyObject recv, IRubyObject filename, Block block) throws IOException {
            Ruby runtime = recv.getRuntime();
            IRubyObject io = RuntimeHelpers.invoke(context, runtime.getFile(), "open", filename, runtime.newString("rb"));
            RubyGzipFile instance = newInstance(recv, new IRubyObject[]{io}, Block.NULL_BLOCK);

            return RubyGzipFile.wrap(context, instance, io, block);
        }

        public RubyGzipReader(Ruby runtime, RubyClass type) {
            super(runtime, type);
        }
        
        private int line;
        private InputStream io;
        
        @JRubyMethod(name = "initialize", required = 1, frame = true, visibility = Visibility.PRIVATE)
        public IRubyObject initialize(IRubyObject io, Block unusedBlock) {
            realIo = io;
            try {
                this.io = new GZIPInputStream(new IOInputStream(io));
            } catch (IOException e) {
                Ruby runtime = io.getRuntime();
                RubyClass errorClass = runtime.fastGetModule("Zlib").fastGetClass("GzipReader").fastGetClass("Error");
                throw new RaiseException(RubyException.newException(runtime, errorClass, e.getMessage()));
            }

            line = 1;
            
            return this;
        }
        
        @JRubyMethod(name = "rewind")
        public IRubyObject rewind() {
            return getRuntime().getNil();
        }
        
        @JRubyMethod(name = "lineno")
        public IRubyObject lineno() {
            return getRuntime().newFixnum(line);
        }

        @JRubyMethod(name = "readline", writes = FrameField.LASTLINE)
        public IRubyObject readline(ThreadContext context) throws IOException {
            IRubyObject dst = gets(context, new IRubyObject[0]);
            if (dst.isNil()) {
                throw getRuntime().newEOFError();
            }
            return dst;
        }

        public IRubyObject internalGets(IRubyObject[] args) throws IOException {
            ByteList sep = ((RubyString)getRuntime().getGlobalVariables().get("$/")).getByteList();
            if (args.length > 0) {
                sep = args[0].convertToString().getByteList();
            }
            return internalSepGets(sep);
        }

        private IRubyObject internalSepGets(ByteList sep) throws IOException {
            ByteList result = new ByteList();
            int ce = io.read();
            while (ce != -1 && sep.indexOf(ce) == -1) {
                result.append((byte)ce);
                ce = io.read();
            }
            line++;
            result.append(sep);
            return RubyString.newString(getRuntime(),result);
        }

        @JRubyMethod(name = "gets", optional = 1, writes = FrameField.LASTLINE)
        public IRubyObject gets(ThreadContext context, IRubyObject[] args) throws IOException {
            IRubyObject result = internalGets(args);
            if (!result.isNil()) {
                context.getCurrentFrame().setLastLine(result);
            }
            return result;
        }

        private final static int BUFF_SIZE = 4096;
        
        @JRubyMethod(name = "read", optional = 1)
        public IRubyObject read(IRubyObject[] args) throws IOException {
            if (args.length == 0 || args[0].isNil()) {
                ByteList val = new ByteList(10);
                byte[] buffer = new byte[BUFF_SIZE];
                int read = io.read(buffer);
                while (read != -1) {
                    val.append(buffer,0,read);
                    read = io.read(buffer);
                }
                return RubyString.newString(getRuntime(),val);
            } 

            int len = RubyNumeric.fix2int(args[0]);
            if (len < 0) {
            	throw getRuntime().newArgumentError("negative length " + len + " given");
            } else if (len > 0) {
            	byte[] buffer = new byte[len];
            	int toRead = len;
            	int offset = 0;
            	int read = 0;
            	while (toRead > 0) {
            		read = io.read(buffer,offset,toRead);
            		if (read == -1) {
            			break;
            		}
            		toRead -= read;
            		offset += read;
            	} // hmm...
            	return RubyString.newString(getRuntime(),new ByteList(buffer,0,len-toRead,false));
            }
                
            return RubyString.newEmptyString(getRuntime());
        }

        @JRubyMethod(name = "lineno=", required = 1)
        public IRubyObject set_lineno(IRubyObject lineArg) {
            line = RubyNumeric.fix2int(lineArg);
            return lineArg;
        }

        @JRubyMethod(name = "pos")
        public IRubyObject pos() {
            return RubyFixnum.zero(getRuntime());
        }
        
        @JRubyMethod(name = "readchar")
        public IRubyObject readchar() throws IOException {
            int value = io.read();
            if (value == -1) {
                throw getRuntime().newEOFError();
            }
            return getRuntime().newFixnum(value);
        }

        @JRubyMethod(name = "getc")
        public IRubyObject getc() throws IOException {
            int value = io.read();
            return value == -1 ? getRuntime().getNil() : getRuntime().newFixnum(value);
        }

        private boolean isEof() throws IOException {
            return ((GZIPInputStream)io).available() != 1;
        }

        @JRubyMethod(name = "close")
        public IRubyObject close() throws IOException {
            if (!closed) {
                io.close();
            }
            this.closed = true;
            return getRuntime().getNil();
        }
        
        @JRubyMethod(name = "eof")
        public IRubyObject eof() throws IOException {
            return isEof() ? getRuntime().getTrue() : getRuntime().getFalse();
        }

        @JRubyMethod(name = "eof?")
        public IRubyObject eof_p() throws IOException {
            return eof();
        }

        @JRubyMethod(name = "unused")
        public IRubyObject unused() {
            return getRuntime().getNil();
        }

        @JRubyMethod(name = "tell")
        public IRubyObject tell() {
            return getRuntime().getNil();
        }

        @JRubyMethod(name = "each", optional = 1, frame = true)
        public IRubyObject each(ThreadContext context, IRubyObject[] args, Block block) throws IOException {
            ByteList sep = ((RubyString)getRuntime().getGlobalVariables().get("$/")).getByteList();
            
            if (args.length > 0 && !args[0].isNil()) {
                sep = args[0].convertToString().getByteList();
            }

            while (!isEof()) {
                block.yield(context, internalSepGets(sep));
            }
            
            return getRuntime().getNil();
        }
    
        @JRubyMethod(name = "ungetc", required = 1)
        public IRubyObject ungetc(IRubyObject arg) {
            return getRuntime().getNil();
        }

        @JRubyMethod(name = "readlines", optional = 1)
        public IRubyObject readlines(IRubyObject[] args) throws IOException {
            List<IRubyObject> array = new ArrayList<IRubyObject>();
            
            if (args.length != 0 && args[0].isNil()) {
                array.add(read(new IRubyObject[0]));
            } else {
                ByteList seperator = ((RubyString)getRuntime().getGlobalVariables().get("$/")).getByteList();
                if (args.length > 0) {
                    seperator = args[0].convertToString().getByteList();
                }
                while (!isEof()) {
                    array.add(internalSepGets(seperator));
                }
            }
            return getRuntime().newArray(array);
        }

        @JRubyMethod(name = "each_byte", frame = true)
        public IRubyObject each_byte(ThreadContext context, Block block) throws IOException {
            int value = io.read();

            while (value != -1) {
                block.yield(context, getRuntime().newFixnum(value));
                value = io.read();
            }
            
            return getRuntime().getNil();
        }
    }

    @JRubyClass(name="Zlib::GzipWriter", parent="Zlib::GzipFile")
    public static class RubyGzipWriter extends RubyGzipFile {
        protected static final ObjectAllocator GZIPWRITER_ALLOCATOR = new ObjectAllocator() {
            public IRubyObject allocate(Ruby runtime, RubyClass klass) {
                return new RubyGzipWriter(runtime, klass);
            }
        };
        
        @JRubyMethod(name = "new", rest = true, frame = true, meta = true)
        public static RubyGzipWriter newGzipWriter(IRubyObject recv, IRubyObject[] args, Block block) {
            RubyClass klass = (RubyClass)recv;
            
            RubyGzipWriter result = (RubyGzipWriter)klass.allocate();
            result.callInit(args, block);
            return result;
        }

        @JRubyMethod(name = "open", required = 1, optional = 2, frame = true, meta = true)
        public static IRubyObject open(final ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) throws IOException {
            Ruby runtime = recv.getRuntime();
            IRubyObject level = runtime.getNil();
            IRubyObject strategy = runtime.getNil();

            if (args.length > 1) {
                level = args[1];
                if (args.length > 2) strategy = args[2];
            }

            IRubyObject io = RuntimeHelpers.invoke(context, runtime.getFile(), "open", args[0], runtime.newString("wb"));
            RubyGzipFile instance = newGzipWriter(recv, new IRubyObject[]{io, level, strategy}, Block.NULL_BLOCK);
            
            return RubyGzipFile.wrap(context, instance, io, block);
        }

        public RubyGzipWriter(Ruby runtime, RubyClass type) {
            super(runtime, type);
        }

        private GZIPOutputStream io;
        
        @JRubyMethod(name = "initialize", required = 1, rest = true, frame = true, visibility = Visibility.PRIVATE)
        public IRubyObject initialize2(IRubyObject[] args, Block unusedBlock) throws IOException {
            realIo = (RubyObject)args[0];
            this.io = new GZIPOutputStream(new IOOutputStream(args[0]));
            
            return this;
        }

        @JRubyMethod(name = "close")
        public IRubyObject close() throws IOException {
            if (!closed) {
                io.close();
            }
            this.closed = true;
            
            return getRuntime().getNil();
        }

        @JRubyMethod(name = "append", required = 1)
        public IRubyObject append(IRubyObject p1) throws IOException {
            this.write(p1);
            return this;
        }

        @JRubyMethod(name = "printf", required = 1, rest = true)
        public IRubyObject printf(ThreadContext context, IRubyObject[] args) throws IOException {
            write(RubyKernel.sprintf(context, this, args));
            return context.getRuntime().getNil();
        }

        @JRubyMethod(name = "print", rest = true)
        public IRubyObject print(IRubyObject[] args) throws IOException {
            if (args.length != 0) {
                for (int i = 0, j = args.length; i < j; i++) {
                    write(args[i]);
                }
            }
            
            IRubyObject sep = getRuntime().getGlobalVariables().get("$\\");
            if (!sep.isNil()) {
                write(sep);
            }
            
            return getRuntime().getNil();
        }

        @JRubyMethod(name = "pos")
        public IRubyObject pos() {
            return getRuntime().getNil();
        }

        @JRubyMethod(name = "orig_name=", required = 1)
        public IRubyObject set_orig_name(IRubyObject ignored) {
            return getRuntime().getNil();
        }

        @JRubyMethod(name = "comment=", required = 1)
        public IRubyObject set_comment(IRubyObject ignored) {
            return getRuntime().getNil();
        }

        @JRubyMethod(name = "putc", required = 1)
        public IRubyObject putc(IRubyObject p1) throws IOException {
            io.write(RubyNumeric.fix2int(p1));
            return p1;
        }
        
        @JRubyMethod(name = "puts", rest = true)
        public IRubyObject puts(ThreadContext context, IRubyObject[] args) throws IOException {
            RubyStringIO sio = (RubyStringIO)getRuntime().fastGetClass("StringIO").newInstance(context, new IRubyObject[0], Block.NULL_BLOCK);
            sio.puts(context, args);
            write(sio.string());
            
            return getRuntime().getNil();
        }

        public IRubyObject finish() throws IOException {
            if (!finished) {
                io.finish();
            }
            finished = true;
            return realIo;
        }

        @JRubyMethod(name = "flush", optional = 1)
        public IRubyObject flush(IRubyObject[] args) throws IOException {
            if (args.length == 0 || args[0].isNil() || RubyNumeric.fix2int(args[0]) != 0) { // Zlib::NO_FLUSH
                io.flush();
            }
            return getRuntime().getNil();
        }

        @JRubyMethod(name = "mtime=", required = 1)
        public IRubyObject set_mtime(IRubyObject ignored) {
            return getRuntime().getNil();
        }

        @JRubyMethod(name = "tell")
        public IRubyObject tell() {
            return getRuntime().getNil();
        }

        @JRubyMethod(name = "write", required = 1)
        public IRubyObject write(IRubyObject p1) throws IOException {
            ByteList bytes = p1.convertToString().getByteList();
            io.write(bytes.unsafeBytes(), bytes.begin(), bytes.length());
            return getRuntime().newFixnum(bytes.length());
        }
    }
}
/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2002 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package org.jruby;

import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.callback.Callback;

/**
 * 
 * @author jpetersen
 */
public final class TopSelfFactory {

    /**
     * Constructor for TopSelfFactory.
     */
    private TopSelfFactory() {
        super();
    }
    
    public static IRubyObject createTopSelf(final Ruby runtime) {
        IRubyObject topSelf = new RubyObject(runtime, runtime.getObject());
        
        topSelf.getSingletonClass().defineFastMethod("to_s", new Callback() {
            /**
             * @see org.jruby.runtime.callback.Callback#execute(IRubyObject, IRubyObject[])
             */
            public IRubyObject execute(IRubyObject recv, IRubyObject[] args, Block block) {
                return runtime.newString("main");
            }

            /**
             * @see org.jruby.runtime.callback.Callback#getArity()
             */
            public Arity getArity() {
                return Arity.noArguments();
            }
        });
        
        // The following three methods must be defined fast, since they expect to modify the current frame
        // (i.e. they expect no frame will be allocated for them). JRUBY-1185.
        topSelf.getSingletonClass().defineFastPrivateMethod("include", new Callback() {
            /**
             * @see org.jruby.runtime.callback.Callback#execute(IRubyObject, IRubyObject[])
             */
            public IRubyObject execute(IRubyObject recv, IRubyObject[] args, Block block) {
                runtime.secure(4);
                return runtime.getObject().include(args);
            }

            /**
             * @see org.jruby.runtime.callback.Callback#getArity()
             */
            public Arity getArity() {
                return Arity.optional();
            }
        });
        
        topSelf.getSingletonClass().defineFastPrivateMethod("public", new Callback() {
            /**
             * @see org.jruby.runtime.callback.Callback#execute(IRubyObject, IRubyObject[])
             */
            public IRubyObject execute(IRubyObject recv, IRubyObject[] args, Block unusedBlock) {
                return runtime.getObject().rbPublic(recv.getRuntime().getCurrentContext(), args);
            }

            /**
             * @see org.jruby.runtime.callback.Callback#getArity()
             */
            public Arity getArity() {
                return Arity.optional();
            }
        });
        
        topSelf.getSingletonClass().defineFastPrivateMethod("private", new Callback() {
            /**
             * @see org.jruby.runtime.callback.Callback#execute(IRubyObject, IRubyObject[])
             */
            public IRubyObject execute(IRubyObject recv, IRubyObject[] args, Block unusedBlock) {
                return runtime.getObject().rbPrivate(recv.getRuntime().getCurrentContext(), args);
            }

            /**
             * @see org.jruby.runtime.callback.Callback#getArity()
             */
            public Arity getArity() {
                return Arity.optional();
            }
        });
        
        return topSelf;
    }
}
