RubyStruct.java

  1. /***** BEGIN LICENSE BLOCK *****
  2.  * Version: EPL 1.0/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Eclipse Public
  5.  * License Version 1.0 (the "License"); you may not use this file
  6.  * except in compliance with the License. You may obtain a copy of
  7.  * the License at http://www.eclipse.org/legal/epl-v10.html
  8.  *
  9.  * Software distributed under the License is distributed on an "AS
  10.  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  11.  * implied. See the License for the specific language governing
  12.  * rights and limitations under the License.
  13.  *
  14.  * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
  15.  * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
  16.  * Copyright (C) 2002-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
  17.  * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
  18.  * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
  19.  * Copyright (C) 2005 Charles O Nutter <headius@headius.com>
  20.  *
  21.  * Alternatively, the contents of this file may be used under the terms of
  22.  * either of the GNU General Public License Version 2 or later (the "GPL"),
  23.  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  24.  * in which case the provisions of the GPL or the LGPL are applicable instead
  25.  * of those above. If you wish to allow use of your version of this file only
  26.  * under the terms of either the GPL or the LGPL, and not to allow others to
  27.  * use your version of this file under the terms of the EPL, indicate your
  28.  * decision by deleting the provisions above and replace them with the notice
  29.  * and other provisions required by the GPL or the LGPL. If you do not delete
  30.  * the provisions above, a recipient may use your version of this file under
  31.  * the terms of any one of the EPL, the GPL or the LGPL.
  32.  ***** END LICENSE BLOCK *****/
  33. package org.jruby;

  34. import org.jruby.anno.JRubyClass;
  35. import org.jruby.anno.JRubyMethod;
  36. import org.jruby.common.IRubyWarnings.ID;
  37. import org.jruby.exceptions.RaiseException;
  38. import org.jruby.internal.runtime.methods.CallConfiguration;
  39. import org.jruby.internal.runtime.methods.DynamicMethod;
  40. import org.jruby.runtime.Arity;
  41. import org.jruby.runtime.Block;
  42. import org.jruby.runtime.ClassIndex;
  43. import org.jruby.runtime.Helpers;
  44. import org.jruby.runtime.ObjectAllocator;
  45. import org.jruby.runtime.ThreadContext;
  46. import org.jruby.runtime.Visibility;
  47. import org.jruby.runtime.builtin.IRubyObject;
  48. import org.jruby.runtime.marshal.MarshalStream;
  49. import org.jruby.runtime.marshal.UnmarshalStream;
  50. import org.jruby.util.ByteList;
  51. import org.jruby.util.IdUtil;

  52. import static org.jruby.RubyEnumerator.enumeratorizeWithSize;
  53. import static org.jruby.runtime.Helpers.invokedynamic;
  54. import static org.jruby.runtime.Visibility.PRIVATE;
  55. import static org.jruby.runtime.invokedynamic.MethodNames.HASH;
  56. import static org.jruby.RubyEnumerator.SizeFn;

  57. /**
  58.  * @author  jpetersen
  59.  */
  60. @JRubyClass(name="Struct")
  61. public class RubyStruct extends RubyObject {
  62.     private final IRubyObject[] values;

  63.     /**
  64.      * Constructor for RubyStruct.
  65.      * @param runtime
  66.      * @param rubyClass
  67.      */
  68.     private RubyStruct(Ruby runtime, RubyClass rubyClass) {
  69.         super(runtime, rubyClass);
  70.        
  71.         int size = RubyNumeric.fix2int(getInternalVariable((RubyClass)rubyClass, "__size__"));

  72.         values = new IRubyObject[size];

  73.         Helpers.fillNil(values, runtime);
  74.     }

  75.     public static RubyClass createStructClass(Ruby runtime) {
  76.         RubyClass structClass = runtime.defineClass("Struct", runtime.getObject(), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
  77.         runtime.setStructClass(structClass);
  78.         structClass.setClassIndex(ClassIndex.STRUCT);
  79.         structClass.includeModule(runtime.getEnumerable());
  80.         structClass.defineAnnotatedMethods(RubyStruct.class);

  81.         return structClass;
  82.     }
  83.    
  84.     @Override
  85.     public ClassIndex getNativeClassIndex() {
  86.         return ClassIndex.STRUCT;
  87.     }
  88.    
  89.     private static IRubyObject getInternalVariable(RubyClass type, String internedName) {
  90.         RubyClass structClass = type.getRuntime().getStructClass();
  91.         IRubyObject variable;

  92.         while (type != null && type != structClass) {
  93.             if ((variable = (IRubyObject)type.getInternalVariable(internedName)) != null) {
  94.                 return variable;
  95.             }

  96.             type = type.getSuperClass();
  97.         }

  98.         return type.getRuntime().getNil();
  99.     }

  100.     private RubyClass classOf() {
  101.         return getMetaClass() instanceof MetaClass ? getMetaClass().getSuperClass() : getMetaClass();
  102.     }

  103.     private void modify() {
  104.         testFrozen();
  105.     }
  106.    
  107.     @JRubyMethod
  108.     public RubyFixnum hash(ThreadContext context) {
  109.         int h = getMetaClass().getRealClass().hashCode();

  110.         for (int i = 0; i < values.length; i++) {
  111.             h = (h << 1) | (h < 0 ? 1 : 0);
  112.             h ^= RubyNumeric.num2long(invokedynamic(context, values[i], HASH));
  113.         }
  114.        
  115.         return context.runtime.newFixnum(h);
  116.     }

  117.     private IRubyObject setByName(String name, IRubyObject value) {
  118.         RubyArray member = __member__();

  119.         modify();

  120.         for (int i = 0,k=member.getLength(); i < k; i++) {
  121.             if (member.eltInternal(i).asJavaString().equals(name)) return values[i] = value;
  122.         }

  123.         throw notStructMemberError(name);
  124.     }

  125.     private IRubyObject getByName(String name) {
  126.         RubyArray member = __member__();

  127.         for (int i = 0,k=member.getLength(); i < k; i++) {
  128.             if (member.eltInternal(i).asJavaString().equals(name)) return values[i];
  129.         }

  130.         throw notStructMemberError(name);
  131.     }

  132.     // Struct methods

  133.     /** Create new Struct class.
  134.      *
  135.      * MRI: rb_struct_s_def / make_struct
  136.      *
  137.      */
  138.     @JRubyMethod(name = "new", required = 1, rest = true, meta = true)
  139.     public static RubyClass newInstance(IRubyObject recv, IRubyObject[] args, Block block) {
  140.         String name = null;
  141.         boolean nilName = false;
  142.         Ruby runtime = recv.getRuntime();

  143.         if (args.length > 0) {
  144.             IRubyObject firstArgAsString = args[0].checkStringType();
  145.             if (!firstArgAsString.isNil()) {
  146.                 name = ((RubyString)firstArgAsString).getByteList().toString();
  147.             } else if (args[0].isNil()) {
  148.                 nilName = true;
  149.             }
  150.         }

  151.         RubyArray member = runtime.newArray();

  152.         for (int i = (name == null && !nilName) ? 0 : 1; i < args.length; i++) {
  153.             member.append(runtime.newSymbol(args[i].asJavaString()));
  154.         }

  155.         RubyClass newStruct;
  156.         RubyClass superClass = (RubyClass)recv;

  157.         if (name == null || nilName) {
  158.             newStruct = RubyClass.newClass(runtime, superClass);
  159.             newStruct.setAllocator(STRUCT_INSTANCE_ALLOCATOR);
  160.             newStruct.makeMetaClass(superClass.getMetaClass());
  161.             newStruct.inherit(superClass);
  162.         } else {
  163.             if (!IdUtil.isConstant(name)) {
  164.                 throw runtime.newNameError("identifier " + name + " needs to be constant", name);
  165.             }

  166.             IRubyObject type = superClass.getConstantAt(name);
  167.             if (type != null) {
  168.                 ThreadContext context = runtime.getCurrentContext();
  169.                 runtime.getWarnings().warn(ID.STRUCT_CONSTANT_REDEFINED, context.getFile(), context.getLine(), "redefining constant Struct::" + name);
  170.                 superClass.remove_const(context, runtime.newString(name));
  171.             }
  172.             newStruct = superClass.defineClassUnder(name, superClass, STRUCT_INSTANCE_ALLOCATOR);
  173.         }

  174.         // set reified class to RubyStruct, for Java subclasses to use
  175.         newStruct.setReifiedClass(RubyStruct.class);
  176.         newStruct.setClassIndex(ClassIndex.STRUCT);
  177.        
  178.         newStruct.setInternalVariable("__size__", member.length());
  179.         newStruct.setInternalVariable("__member__", member);

  180.         newStruct.getSingletonClass().defineAnnotatedMethods(StructMethods.class);

  181.         // define access methods.
  182.         for (int i = (name == null && !nilName) ? 0 : 1; i < args.length; i++) {
  183.             final String memberName = args[i].asJavaString();
  184.             // if we are storing a name as well, index is one too high for values
  185.             final int index = (name == null && !nilName) ? i : i - 1;
  186.             newStruct.addMethod(memberName, new DynamicMethod(newStruct, Visibility.PUBLIC, CallConfiguration.FrameNoneScopeNone) {
  187.                 @Override
  188.                 public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args, Block block) {
  189.                     Arity.checkArgumentCount(context.runtime, name, args, 0, 0);
  190.                     return ((RubyStruct)self).get(index);
  191.                 }

  192.                 @Override
  193.                 public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name) {
  194.                     return ((RubyStruct)self).get(index);
  195.                 }

  196.                 @Override
  197.                 public DynamicMethod dup() {
  198.                     return this;
  199.                 }
  200.             });
  201.             newStruct.addMethod(memberName + "=", new DynamicMethod(newStruct, Visibility.PUBLIC, CallConfiguration.FrameNoneScopeNone) {
  202.                 @Override
  203.                 public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args, Block block) {
  204.                     Arity.checkArgumentCount(context.runtime, name, args, 1, 1);
  205.                     return ((RubyStruct)self).set(args[0], index);
  206.                 }

  207.                 @Override
  208.                 public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg) {
  209.                     return ((RubyStruct)self).set(arg, index);
  210.                 }

  211.                 @Override
  212.                 public DynamicMethod dup() {
  213.                     return this;
  214.                 }
  215.             });
  216.         }
  217.        
  218.         if (block.isGiven()) {
  219.             // Since this defines a new class, run the block as a module-eval.
  220.             block.setEvalType(EvalType.MODULE_EVAL);
  221.             // Struct bodies should be public by default, so set block visibility to public. JRUBY-1185.
  222.             block.getBinding().setVisibility(Visibility.PUBLIC);
  223.             block.yieldNonArray(runtime.getCurrentContext(), null, newStruct);
  224.         }

  225.         return newStruct;
  226.     }
  227.    
  228.     // For binding purposes on the newly created struct types
  229.     public static class StructMethods {
  230.         @JRubyMethod(name = {"new", "[]"}, rest = true)
  231.         public static IRubyObject newStruct(IRubyObject recv, IRubyObject[] args, Block block) {
  232.             return RubyStruct.newStruct(recv, args, block);
  233.         }

  234.         @JRubyMethod(name = {"new", "[]"})
  235.         public static IRubyObject newStruct(IRubyObject recv, Block block) {
  236.             return RubyStruct.newStruct(recv, block);
  237.         }

  238.         @JRubyMethod(name = {"new", "[]"})
  239.         public static IRubyObject newStruct(IRubyObject recv, IRubyObject arg0, Block block) {
  240.             return RubyStruct.newStruct(recv, arg0, block);
  241.         }

  242.         @JRubyMethod(name = {"new", "[]"})
  243.         public static IRubyObject newStruct(IRubyObject recv, IRubyObject arg0, IRubyObject arg1, Block block) {
  244.             return RubyStruct.newStruct(recv, arg0, arg1, block);
  245.         }

  246.         @JRubyMethod(name = {"new", "[]"})
  247.         public static IRubyObject newStruct(IRubyObject recv, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Block block) {
  248.             return RubyStruct.newStruct(recv, arg0, arg1, arg2, block);
  249.         }

  250.         @JRubyMethod
  251.         public static IRubyObject members(IRubyObject recv, Block block) {
  252.             return RubyStruct.members19(recv, block);
  253.         }
  254.     }

  255.     /** Create new Structure.
  256.      *
  257.      * MRI: struct_alloc
  258.      *
  259.      */
  260.     public static RubyStruct newStruct(IRubyObject recv, IRubyObject[] args, Block block) {
  261.         RubyStruct struct = new RubyStruct(recv.getRuntime(), (RubyClass) recv);

  262.         struct.callInit(args, block);

  263.         return struct;
  264.     }

  265.     public static RubyStruct newStruct(IRubyObject recv, Block block) {
  266.         RubyStruct struct = new RubyStruct(recv.getRuntime(), (RubyClass) recv);

  267.         struct.callInit(block);

  268.         return struct;
  269.     }

  270.     public static RubyStruct newStruct(IRubyObject recv, IRubyObject arg0, Block block) {
  271.         RubyStruct struct = new RubyStruct(recv.getRuntime(), (RubyClass) recv);

  272.         struct.callInit(arg0, block);

  273.         return struct;
  274.     }

  275.     public static RubyStruct newStruct(IRubyObject recv, IRubyObject arg0, IRubyObject arg1, Block block) {
  276.         RubyStruct struct = new RubyStruct(recv.getRuntime(), (RubyClass) recv);

  277.         struct.callInit(arg0, arg1, block);

  278.         return struct;
  279.     }

  280.     public static RubyStruct newStruct(IRubyObject recv, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Block block) {
  281.         RubyStruct struct = new RubyStruct(recv.getRuntime(), (RubyClass) recv);

  282.         struct.callInit(arg0, arg1, arg2, block);

  283.         return struct;
  284.     }

  285.     private void checkSize(int length) {
  286.         if (length > values.length) {
  287.             throw getRuntime().newArgumentError("struct size differs (" + length +" for " + values.length + ")");
  288.         }
  289.     }

  290.     @JRubyMethod(rest = true, visibility = PRIVATE)
  291.     public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
  292.         modify();
  293.         checkSize(args.length);

  294.         System.arraycopy(args, 0, values, 0, args.length);
  295.         Helpers.fillNil(values, args.length, values.length, context.runtime);

  296.         return context.nil;
  297.     }

  298.     @JRubyMethod(visibility = PRIVATE)
  299.     @Override
  300.     public IRubyObject initialize(ThreadContext context) {
  301.         IRubyObject nil = context.nil;
  302.         return initializeInternal(context, 0, nil, nil, nil);
  303.     }

  304.     @JRubyMethod(visibility = PRIVATE)
  305.     public IRubyObject initialize(ThreadContext context, IRubyObject arg0) {
  306.         IRubyObject nil = context.nil;
  307.         return initializeInternal(context, 1, arg0, nil, nil);
  308.     }

  309.     @JRubyMethod(visibility = PRIVATE)
  310.     public IRubyObject initialize(ThreadContext context, IRubyObject arg0, IRubyObject arg1) {
  311.         return initializeInternal(context, 2, arg0, arg1, context.nil);
  312.     }

  313.     @JRubyMethod(visibility = PRIVATE)
  314.     public IRubyObject initialize(ThreadContext context, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) {
  315.         return initializeInternal(context, 3, arg0, arg1, arg2);
  316.     }
  317.    
  318.     public IRubyObject initializeInternal(ThreadContext context, int provided, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) {
  319.         modify();
  320.         checkSize(provided);

  321.         switch (provided) {
  322.         case 3:
  323.             values[2] = arg2;
  324.         case 2:
  325.             values[1] = arg1;
  326.         case 1:
  327.             values[0] = arg0;
  328.         }
  329.         if (provided < values.length) {
  330.             Helpers.fillNil(values, provided, values.length, context.runtime);
  331.         }

  332.         return getRuntime().getNil();
  333.     }
  334.    
  335.     public static RubyArray members(IRubyObject recv, Block block) {
  336.         RubyArray member = __member__((RubyClass) recv);
  337.         RubyArray result = recv.getRuntime().newArray(member.getLength());
  338.        
  339.         for (int i = 0,k=member.getLength(); i < k; i++) {
  340.             // this looks weird, but it's because they're RubySymbol and that's java.lang.String internally
  341.             result.append(recv.getRuntime().newString(member.eltInternal(i).asJavaString()));
  342.         }

  343.         return result;
  344.     }

  345.     public static RubyArray members19(IRubyObject recv, Block block) {
  346.         RubyArray member = __member__((RubyClass) recv);
  347.         RubyArray result = recv.getRuntime().newArray(member.getLength());
  348.        
  349.         for (int i = 0,k=member.getLength(); i < k; i++) {
  350.             result.append(member.eltInternal(i));
  351.         }

  352.         return result;
  353.     }
  354.    
  355.     private static RubyArray __member__(RubyClass clazz) {
  356.         RubyArray member = (RubyArray) getInternalVariable(clazz, "__member__");

  357.         assert !member.isNil() : "uninitialized struct";
  358.        
  359.         return member;
  360.     }
  361.    
  362.     private RubyArray __member__() {
  363.         return __member__(classOf());
  364.     }

  365.     public RubyArray members() {
  366.         return members19();
  367.     }

  368.     @JRubyMethod(name = "members")
  369.     public RubyArray members19() {
  370.         return members19(classOf(), Block.NULL_BLOCK);
  371.     }

  372.     @JRubyMethod
  373.     public IRubyObject select(ThreadContext context, Block block) {
  374.         if (!block.isGiven()) {
  375.             return enumeratorizeWithSize(context, this, "select", enumSizeFn());
  376.         }

  377.         RubyArray array = RubyArray.newArray(context.runtime);
  378.        
  379.         for (int i = 0; i < values.length; i++) {
  380.             if (block.yield(context, values[i]).isTrue()) {
  381.                 array.append(values[i]);
  382.             }
  383.         }
  384.        
  385.         return array;
  386.     }

  387.     private SizeFn enumSizeFn() {
  388.         final RubyStruct self = this;
  389.         return new SizeFn() {
  390.             @Override
  391.             public IRubyObject size(IRubyObject[] args) {
  392.                 return self.size();
  393.             }
  394.         };
  395.     }

  396.     public IRubyObject set(IRubyObject value, int index) {
  397.         modify();

  398.         return values[index] = value;
  399.     }

  400.     private RaiseException notStructMemberError(String name) {
  401.         return getRuntime().newNameError("no member '" + name + "' in struct", name);
  402.     }

  403.     public IRubyObject get(int index) {
  404.         return values[index];
  405.     }

  406.     @Override
  407.     public void copySpecialInstanceVariables(IRubyObject clone) {
  408.         RubyStruct struct = (RubyStruct)clone;
  409.         System.arraycopy(values, 0, struct.values, 0, values.length);
  410.     }

  411.     @JRubyMethod(name = "==", required = 1)
  412.     @Override
  413.     public IRubyObject op_equal(final ThreadContext context, IRubyObject other) {
  414.         if (this == other) return getRuntime().getTrue();
  415.         if (!(other instanceof RubyStruct)) return getRuntime().getFalse();
  416.         if (getMetaClass().getRealClass() != other.getMetaClass().getRealClass()) return getRuntime().getFalse();

  417.         if (other == this) return context.runtime.getTrue();
  418.        
  419.         final Ruby runtime = context.runtime;
  420.         final RubyStruct otherStruct = (RubyStruct)other;        

  421.         // recursion guard
  422.         return runtime.execRecursiveOuter(new Ruby.RecursiveFunction() {
  423.             @Override
  424.             public IRubyObject call(IRubyObject obj, boolean recur) {
  425.                 if (recur) return runtime.getTrue();

  426.                 for (int i = 0; i < values.length; i++) {
  427.                     if (!equalInternal(context, values[i], otherStruct.values[i])) return runtime.getFalse();
  428.                 }
  429.                 return runtime.getTrue();
  430.             }
  431.         }, this);
  432.     }
  433.    
  434.     @JRubyMethod(name = "eql?", required = 1)
  435.     public IRubyObject eql_p(final ThreadContext context, IRubyObject other) {
  436.         if (this == other) return getRuntime().getTrue();
  437.         if (!(other instanceof RubyStruct)) return getRuntime().getFalse();
  438.         if (getMetaClass() != other.getMetaClass()) return getRuntime().getFalse();

  439.         if (other == this) return context.runtime.getTrue();
  440.        
  441.         final Ruby runtime = context.runtime;
  442.         final RubyStruct otherStruct = (RubyStruct)other;

  443.         // recursion guard
  444.         return runtime.execRecursiveOuter(new Ruby.RecursiveFunction() {
  445.             @Override
  446.             public IRubyObject call(IRubyObject obj, boolean recur) {
  447.                 if (recur) return runtime.getTrue();

  448.                 for (int i = 0; i < values.length; i++) {
  449.                     if (!eqlInternal(context, values[i], otherStruct.values[i])) return runtime.getFalse();
  450.                 }
  451.                 return runtime.getTrue();
  452.             }
  453.         }, this);
  454.     }

  455.     /** inspect_struct
  456.     *
  457.     */
  458.     private IRubyObject inspectStruct(final ThreadContext context, boolean recur) {
  459.         Ruby runtime = context.runtime;
  460.         RubyArray member = __member__();
  461.         ByteList buffer = new ByteList("#<struct ".getBytes());
  462.         String cpath = getMetaClass().getRealClass().getName();
  463.         char first = cpath.charAt(0);

  464.         if (recur || first != '#') {
  465.             buffer.append(cpath.getBytes());
  466.             buffer.append(' ');
  467.         }

  468.         if (recur) {
  469.             buffer.append(":...>".getBytes());
  470.             return runtime.newString(buffer);
  471.         }

  472.         for (int i = 0,k=member.getLength(); i < k; i++) {
  473.             if (i > 0) {
  474.                 buffer.append(',').append(' ');
  475.             }
  476.             RubySymbol slot = (RubySymbol)member.eltInternal(i);
  477.             String name = slot.toString();
  478.             if (IdUtil.isLocal(name) || IdUtil.isConstant(name)) {
  479.                 buffer.append(RubyString.objAsString(context, slot).getByteList());
  480.             } else {
  481.                 buffer.append(((RubyString) slot.inspect(context)).getByteList());
  482.             }
  483.             buffer.append('=');
  484.             buffer.append(inspect(context, values[i]).getByteList());
  485.         }

  486.         buffer.append('>');
  487.         return getRuntime().newString(buffer); // OBJ_INFECT
  488.     }

  489.     @JRubyMethod(name = {"inspect", "to_s"})
  490.     public IRubyObject inspect(final ThreadContext context) {
  491.         final Ruby runtime = context.runtime;

  492.         // recursion guard
  493.         return runtime.execRecursiveOuter(new Ruby.RecursiveFunction() {
  494.             @Override
  495.             public IRubyObject call(IRubyObject obj, boolean recur) {
  496.                 return inspectStruct(context, recur);
  497.             }
  498.         }, this);
  499.     }

  500.     @JRubyMethod(name = {"to_a", "values"})
  501.     @Override
  502.     public RubyArray to_a() {
  503.         return getRuntime().newArray(values);
  504.     }
  505.    
  506.     @JRubyMethod
  507.     public RubyHash to_h(ThreadContext context) {
  508.         RubyHash hash = RubyHash.newHash(context.runtime);
  509.         RubyArray members = __member__();
  510.        
  511.         for (int i = 0; i < values.length; i++) {
  512.             hash.op_aset(context, members.eltOk(i), values[i]);
  513.         }
  514.        
  515.         return hash;
  516.     }

  517.     @JRubyMethod(name = {"size", "length"} )
  518.     public RubyFixnum size() {
  519.         return getRuntime().newFixnum(values.length);
  520.     }

  521.     public IRubyObject eachInternal(ThreadContext context, Block block) {
  522.         for (int i = 0; i < values.length; i++) {
  523.             block.yield(context, values[i]);
  524.         }

  525.         return this;
  526.     }

  527.     @JRubyMethod
  528.     public IRubyObject each(final ThreadContext context, final Block block) {
  529.         return block.isGiven() ? eachInternal(context, block) : enumeratorizeWithSize(context, this, "each", enumSizeFn());
  530.     }

  531.     public IRubyObject each_pairInternal(ThreadContext context, Block block) {
  532.         RubyArray member = __member__();

  533.         for (int i = 0; i < values.length; i++) {
  534.             block.yield(context, getRuntime().newArrayNoCopy(new IRubyObject[]{member.eltInternal(i), values[i]}));
  535.         }

  536.         return this;
  537.     }

  538.     @JRubyMethod
  539.     public IRubyObject each_pair(final ThreadContext context, final Block block) {
  540.         return block.isGiven() ? each_pairInternal(context, block) : enumeratorizeWithSize(context, this, "each_pair", enumSizeFn());
  541.     }

  542.     @JRubyMethod(name = "[]", required = 1)
  543.     public IRubyObject aref(IRubyObject key) {
  544.         if (key instanceof RubyString || key instanceof RubySymbol) {
  545.             return getByName(key.asJavaString());
  546.         }

  547.         int idx = RubyNumeric.fix2int(key);

  548.         idx = idx < 0 ? values.length + idx : idx;

  549.         if (idx < 0) {
  550.             throw getRuntime().newIndexError("offset " + idx + " too small for struct(size:" + values.length + ")");
  551.         } else if (idx >= values.length) {
  552.             throw getRuntime().newIndexError("offset " + idx + " too large for struct(size:" + values.length + ")");
  553.         }

  554.         return values[idx];
  555.     }

  556.     @JRubyMethod(name = "[]=", required = 2)
  557.     public IRubyObject aset(IRubyObject key, IRubyObject value) {
  558.         if (key instanceof RubyString || key instanceof RubySymbol) {
  559.             return setByName(key.asJavaString(), value);
  560.         }

  561.         int idx = RubyNumeric.fix2int(key);

  562.         idx = idx < 0 ? values.length + idx : idx;

  563.         if (idx < 0) {
  564.             throw getRuntime().newIndexError("offset " + idx + " too small for struct(size:" + values.length + ")");
  565.         } else if (idx >= values.length) {
  566.             throw getRuntime().newIndexError("offset " + idx + " too large for struct(size:" + values.length + ")");
  567.         }

  568.         modify();
  569.         return values[idx] = value;
  570.     }
  571.    
  572.     // FIXME: This is copied code from RubyArray.  Both RE, Struct, and Array should share one impl
  573.     // This is also hacky since I construct ruby objects to access ruby arrays through aref instead
  574.     // of something lower.
  575.     @JRubyMethod(rest = true)
  576.     public IRubyObject values_at(IRubyObject[] args) {
  577.         int olen = values.length;
  578.         RubyArray result = getRuntime().newArray(args.length);

  579.         for (int i = 0; i < args.length; i++) {
  580.             if (args[i] instanceof RubyFixnum) {
  581.                 result.append(aref(args[i]));
  582.                 continue;
  583.             }

  584.             int beglen[];
  585.             if (!(args[i] instanceof RubyRange)) {
  586.             } else if ((beglen = ((RubyRange) args[i]).begLenInt(olen, 0)) == null) {
  587.                 continue;
  588.             } else {
  589.                 int beg = beglen[0];
  590.                 int len = beglen[1];
  591.                 int end = len;
  592.                 for (int j = 0; j < end; j++) {
  593.                     result.append(aref(getRuntime().newFixnum(j + beg)));
  594.                 }
  595.                 continue;
  596.             }
  597.             result.append(aref(getRuntime().newFixnum(RubyNumeric.num2long(args[i]))));
  598.         }

  599.         return result;
  600.     }

  601.     public static void marshalTo(RubyStruct struct, MarshalStream output) throws java.io.IOException {
  602.         output.registerLinkTarget(struct);
  603.         output.dumpDefaultObjectHeader('S', struct.getMetaClass());

  604.         RubyArray member = __member__(struct.classOf());
  605.         output.writeInt(member.size());

  606.         for (int i = 0; i < member.size(); i++) {
  607.             RubySymbol name = (RubySymbol) member.eltInternal(i);
  608.             output.dumpObject(name);
  609.             output.dumpObject(struct.values[i]);
  610.         }
  611.     }

  612.     public static RubyStruct unmarshalFrom(UnmarshalStream input) throws java.io.IOException {
  613.         Ruby runtime = input.getRuntime();

  614.         RubySymbol className = (RubySymbol) input.unmarshalObject(false);
  615.         RubyClass rbClass = pathToClass(runtime, className.asJavaString());
  616.         if (rbClass == null) {
  617.             throw runtime.newNameError("uninitialized constant " + className, className.asJavaString());
  618.         }

  619.         RubyArray mem = members(rbClass, Block.NULL_BLOCK);

  620.         int len = input.unmarshalInt();

  621.         // FIXME: This could all be more efficient, but it's how struct works
  622.         RubyStruct result;
  623.         // 1.9 does not appear to call initialize (JRUBY-5875)
  624.         result = new RubyStruct(runtime, rbClass);
  625.         input.registerLinkTarget(result);

  626.         for (int i = 0; i < len; i++) {
  627.             IRubyObject slot = input.unmarshalObject(false);
  628.             if (!mem.eltInternal(i).toString().equals(slot.toString())) {
  629.                 throw runtime.newTypeError("struct " + rbClass.getName() + " not compatible (:" + slot + " for :" + mem.eltInternal(i) + ")");
  630.             }
  631.             result.aset(runtime.newFixnum(i), input.unmarshalObject());
  632.         }
  633.         return result;
  634.     }

  635.     private static RubyClass pathToClass(Ruby runtime, String path) {
  636.         // FIXME: Throw the right ArgumentError's if the class is missing
  637.         // or if it's a module.
  638.         return (RubyClass) runtime.getClassFromPath(path);
  639.     }
  640.    
  641.     private static ObjectAllocator STRUCT_INSTANCE_ALLOCATOR = new ObjectAllocator() {
  642.         @Override
  643.         public IRubyObject allocate(Ruby runtime, RubyClass klass) {
  644.             RubyStruct instance = new RubyStruct(runtime, klass);
  645.            
  646.             instance.setMetaClass(klass);
  647.            
  648.             return instance;
  649.         }
  650.     };
  651.    
  652.     @Override
  653.     @JRubyMethod(required = 1, visibility = Visibility.PRIVATE)
  654.     public IRubyObject initialize_copy(IRubyObject arg) {
  655.         if (this == arg) return this;
  656.         RubyStruct original = (RubyStruct) arg;

  657.         checkFrozen();
  658.        
  659.         System.arraycopy(original.values, 0, values, 0, original.values.length);

  660.         return this;
  661.     }
  662.    
  663. }