summaryrefslogtreecommitdiff
path: root/test/scanners/java/jruby.in.java
diff options
context:
space:
mode:
authormurphy <murphy@rubychan.de>2008-09-21 16:25:44 +0000
committermurphy <murphy@rubychan.de>2008-09-21 16:25:44 +0000
commit9a5f5e6217db0b7689b64ca0892feacf32be3d66 (patch)
tree98a25e39c452f0d7d1268810b014d929ab9930cc /test/scanners/java/jruby.in.java
parent41acfacb91970c8fa4e8b34f35c718eb329a3733 (diff)
downloadcoderay-9a5f5e6217db0b7689b64ca0892feacf32be3d66.tar.gz
New: *Java Scanner* (closes #42).
* Based on JavaScript, does a good job, but may need more polish. * Java::BuiltinTypes::List is a helper constant that contains 2389 Java types. ** The list was generated from TextMate's Java bundle with the help of SimpleRegexpScanner. * I added the JRuby core classes as example code for testing (1.8 MB) JavaScript Scanner: * Fixed recognition of floats and algebraic signs. ** Still needs work - we need to distinguish i-1 from i+-1. More changes: * New: "SimpleRegexpScanner":http://murfy.de/simple-regexp-scanner * Added new token class :annotation along with CSS styles. ** Should be useful for Python, too. * coderay_suite warns if no scanner was found for this language. * PluginHost#default can be called without parameter (will return default id)
Diffstat (limited to 'test/scanners/java/jruby.in.java')
-rw-r--r--test/scanners/java/jruby.in.java51193
1 files changed, 51193 insertions, 0 deletions
diff --git a/test/scanners/java/jruby.in.java b/test/scanners/java/jruby.in.java
new file mode 100644
index 0000000..538abfa
--- /dev/null
+++ b/test/scanners/java/jruby.in.java
@@ -0,0 +1,51193 @@
+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;
+ }
+}