diff options
author | murphy <murphy@rubychan.de> | 2008-09-21 16:25:44 +0000 |
---|---|---|
committer | murphy <murphy@rubychan.de> | 2008-09-21 16:25:44 +0000 |
commit | 9a5f5e6217db0b7689b64ca0892feacf32be3d66 (patch) | |
tree | 98a25e39c452f0d7d1268810b014d929ab9930cc /test/scanners/java/jruby.in.java | |
parent | 41acfacb91970c8fa4e8b34f35c718eb329a3733 (diff) | |
download | coderay-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.java | 51193 |
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; + } +} |