Home | History | Annotate | Download | only in jruby
      1 /*
      2  * Protocol Buffers - Google's data interchange format
      3  * Copyright 2014 Google Inc.  All rights reserved.
      4  * https://developers.google.com/protocol-buffers/
      5  *
      6  * Redistribution and use in source and binary forms, with or without
      7  * modification, are permitted provided that the following conditions are
      8  * met:
      9  *
     10  *     * Redistributions of source code must retain the above copyright
     11  * notice, this list of conditions and the following disclaimer.
     12  *     * Redistributions in binary form must reproduce the above
     13  * copyright notice, this list of conditions and the following disclaimer
     14  * in the documentation and/or other materials provided with the
     15  * distribution.
     16  *     * Neither the name of Google Inc. nor the names of its
     17  * contributors may be used to endorse or promote products derived from
     18  * this software without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     33 package com.google.protobuf.jruby;
     34 
     35 import com.google.protobuf.Descriptors;
     36 import org.jruby.*;
     37 import org.jruby.anno.JRubyClass;
     38 import org.jruby.anno.JRubyMethod;
     39 import org.jruby.runtime.Block;
     40 import org.jruby.runtime.ObjectAllocator;
     41 import org.jruby.runtime.ThreadContext;
     42 import org.jruby.runtime.builtin.IRubyObject;
     43 import java.util.Arrays;
     44 
     45 @JRubyClass(name = "RepeatedClass", include = "Enumerable")
     46 public class RubyRepeatedField extends RubyObject {
     47     public static void createRubyRepeatedField(Ruby runtime) {
     48         RubyModule mProtobuf = runtime.getClassFromPath("Google::Protobuf");
     49         RubyClass cRepeatedField = mProtobuf.defineClassUnder("RepeatedField", runtime.getObject(),
     50                 new ObjectAllocator() {
     51                     @Override
     52                     public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
     53                         return new RubyRepeatedField(runtime, klazz);
     54                     }
     55                 });
     56         cRepeatedField.defineAnnotatedMethods(RubyRepeatedField.class);
     57         cRepeatedField.includeModule(runtime.getEnumerable());
     58     }
     59 
     60     public RubyRepeatedField(Ruby runtime, RubyClass klazz) {
     61         super(runtime, klazz);
     62     }
     63 
     64     public RubyRepeatedField(Ruby runtime, RubyClass klazz, Descriptors.FieldDescriptor.Type fieldType, IRubyObject typeClass) {
     65         this(runtime, klazz);
     66         this.fieldType = fieldType;
     67         this.storage = runtime.newArray();
     68         this.typeClass = typeClass;
     69     }
     70 
     71     @JRubyMethod(required = 1, optional = 2)
     72     public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
     73         Ruby runtime = context.runtime;
     74         this.storage = runtime.newArray();
     75         IRubyObject ary = null;
     76         if (!(args[0] instanceof RubySymbol)) {
     77             throw runtime.newArgumentError("Expected Symbol for type name");
     78         }
     79         this.fieldType = Utils.rubyToFieldType(args[0]);
     80         if (fieldType == Descriptors.FieldDescriptor.Type.MESSAGE
     81                 || fieldType == Descriptors.FieldDescriptor.Type.ENUM) {
     82             if (args.length < 2)
     83                 throw runtime.newArgumentError("Expected at least 2 arguments for message/enum");
     84             typeClass = args[1];
     85             if (args.length > 2)
     86                 ary = args[2];
     87             Utils.validateTypeClass(context, fieldType, typeClass);
     88         } else {
     89             if (args.length > 2)
     90                 throw runtime.newArgumentError("Too many arguments: expected 1 or 2");
     91             if (args.length > 1)
     92                 ary = args[1];
     93         }
     94         if (ary != null) {
     95             RubyArray arr = ary.convertToArray();
     96             for (int i = 0; i < arr.size(); i++) {
     97                 this.storage.add(arr.eltInternal(i));
     98             }
     99         }
    100         return this;
    101     }
    102 
    103     /*
    104      * call-seq:
    105      *     RepeatedField.[]=(index, value)
    106      *
    107      * Sets the element at the given index. On out-of-bounds assignments, extends
    108      * the array and fills the hole (if any) with default values.
    109      */
    110     @JRubyMethod(name = "[]=")
    111     public IRubyObject indexSet(ThreadContext context, IRubyObject index, IRubyObject value) {
    112         int arrIndex = normalizeArrayIndex(index);
    113         Utils.checkType(context, fieldType, value, (RubyModule) typeClass);
    114         IRubyObject defaultValue = defaultValue(context);
    115         for (int i = this.storage.size(); i < arrIndex; i++) {
    116             this.storage.set(i, defaultValue);
    117         }
    118         this.storage.set(arrIndex, value);
    119         return context.runtime.getNil();
    120     }
    121 
    122     /*
    123      * call-seq:
    124      *     RepeatedField.[](index) => value
    125      *
    126      * Accesses the element at the given index. Returns nil on out-of-bounds
    127      */
    128     @JRubyMethod(required=1, optional=1, name = {"at", "[]"})
    129     public IRubyObject index(ThreadContext context, IRubyObject[] args) {
    130         if (args.length == 1){
    131             IRubyObject arg = args[0];
    132             if (Utils.isRubyNum(arg)) {
    133                 /* standard case */
    134                 int arrIndex = normalizeArrayIndex(arg);
    135                 if (arrIndex < 0 || arrIndex >= this.storage.size()) {
    136                     return context.runtime.getNil();
    137                 }
    138                 return this.storage.eltInternal(arrIndex);
    139             } else if (arg instanceof RubyRange) {
    140                 RubyRange range = ((RubyRange) arg);
    141                 int beg = RubyNumeric.num2int(range.first(context));
    142                 int to = RubyNumeric.num2int(range.last(context));
    143                 int len = to - beg + 1;
    144                 return this.storage.subseq(beg, len);
    145             }
    146         }
    147         /* assume 2 arguments */
    148         int beg = RubyNumeric.num2int(args[0]);
    149         int len = RubyNumeric.num2int(args[1]);
    150         if (beg < 0) {
    151             beg += this.storage.size();
    152         }
    153         if (beg >= this.storage.size()) {
    154             return context.runtime.getNil();
    155         }
    156         return this.storage.subseq(beg, len);
    157     }
    158 
    159     /*
    160      * call-seq:
    161      *     RepeatedField.push(value)
    162      *
    163      * Adds a new element to the repeated field.
    164      */
    165     @JRubyMethod(name = {"push", "<<"})
    166     public IRubyObject push(ThreadContext context, IRubyObject value) {
    167         if (!(fieldType == Descriptors.FieldDescriptor.Type.MESSAGE &&
    168             value == context.runtime.getNil())) {
    169             Utils.checkType(context, fieldType, value, (RubyModule) typeClass);
    170         }
    171         this.storage.add(value);
    172         return this.storage;
    173     }
    174 
    175     /*
    176      * private Ruby method used by RepeatedField.pop
    177      */
    178     @JRubyMethod(visibility = org.jruby.runtime.Visibility.PRIVATE)
    179     public IRubyObject pop_one(ThreadContext context) {
    180         IRubyObject ret = this.storage.last();
    181         this.storage.remove(ret);
    182         return ret;
    183     }
    184 
    185     /*
    186      * call-seq:
    187      *     RepeatedField.replace(list)
    188      *
    189      * Replaces the contents of the repeated field with the given list of elements.
    190      */
    191     @JRubyMethod
    192     public IRubyObject replace(ThreadContext context, IRubyObject list) {
    193         RubyArray arr = (RubyArray) list;
    194         checkArrayElementType(context, arr);
    195         this.storage = arr;
    196         return this.storage;
    197     }
    198 
    199     /*
    200      * call-seq:
    201      *     RepeatedField.clear
    202      *
    203      * Clears (removes all elements from) this repeated field.
    204      */
    205     @JRubyMethod
    206     public IRubyObject clear(ThreadContext context) {
    207         this.storage.clear();
    208         return this.storage;
    209     }
    210 
    211     /*
    212      * call-seq:
    213      *     RepeatedField.length
    214      *
    215      * Returns the length of this repeated field.
    216      */
    217     @JRubyMethod(name = {"length", "size"})
    218     public IRubyObject length(ThreadContext context) {
    219         return context.runtime.newFixnum(this.storage.size());
    220     }
    221 
    222     /*
    223      * call-seq:
    224      *     RepeatedField.+(other) => repeated field
    225      *
    226      * Returns a new repeated field that contains the concatenated list of this
    227      * repeated field's elements and other's elements. The other (second) list may
    228      * be either another repeated field or a Ruby array.
    229      */
    230     @JRubyMethod(name = {"+"})
    231     public IRubyObject plus(ThreadContext context, IRubyObject list) {
    232         RubyRepeatedField dup = (RubyRepeatedField) dup(context);
    233         if (list instanceof RubyArray) {
    234             checkArrayElementType(context, (RubyArray) list);
    235             dup.storage.addAll((RubyArray) list);
    236         } else {
    237             RubyRepeatedField repeatedField = (RubyRepeatedField) list;
    238             if (! fieldType.equals(repeatedField.fieldType) || (typeClass != null && !
    239                     typeClass.equals(repeatedField.typeClass)))
    240                 throw context.runtime.newArgumentError("Attempt to append RepeatedField with different element type.");
    241             dup.storage.addAll((RubyArray) repeatedField.toArray(context));
    242         }
    243         return dup;
    244     }
    245 
    246     /*
    247      * call-seq:
    248      *     RepeatedField.concat(other) => self
    249      *
    250      * concats the passed in array to self.  Returns a Ruby array.
    251      */
    252     @JRubyMethod
    253     public IRubyObject concat(ThreadContext context, IRubyObject list) {
    254         if (list instanceof RubyArray) {
    255             checkArrayElementType(context, (RubyArray) list);
    256             this.storage.addAll((RubyArray) list);
    257         } else {
    258             RubyRepeatedField repeatedField = (RubyRepeatedField) list;
    259             if (! fieldType.equals(repeatedField.fieldType) || (typeClass != null && !
    260                     typeClass.equals(repeatedField.typeClass)))
    261                 throw context.runtime.newArgumentError("Attempt to append RepeatedField with different element type.");
    262             this.storage.addAll((RubyArray) repeatedField.toArray(context));
    263         }
    264         return this.storage;
    265     }
    266 
    267     /*
    268      * call-seq:
    269      *     RepeatedField.hash => hash_value
    270      *
    271      * Returns a hash value computed from this repeated field's elements.
    272      */
    273     @JRubyMethod
    274     public IRubyObject hash(ThreadContext context) {
    275         int hashCode = this.storage.hashCode();
    276         return context.runtime.newFixnum(hashCode);
    277     }
    278 
    279     /*
    280      * call-seq:
    281      *     RepeatedField.==(other) => boolean
    282      *
    283      * Compares this repeated field to another. Repeated fields are equal if their
    284      * element types are equal, their lengths are equal, and each element is equal.
    285      * Elements are compared as per normal Ruby semantics, by calling their :==
    286      * methods (or performing a more efficient comparison for primitive types).
    287      */
    288     @JRubyMethod(name = "==")
    289     public IRubyObject eq(ThreadContext context, IRubyObject other) {
    290         return this.toArray(context).op_equal(context, other);
    291     }
    292 
    293     /*
    294      * call-seq:
    295      *     RepeatedField.each(&block)
    296      *
    297      * Invokes the block once for each element of the repeated field. RepeatedField
    298      * also includes Enumerable; combined with this method, the repeated field thus
    299      * acts like an ordinary Ruby sequence.
    300      */
    301     @JRubyMethod
    302     public IRubyObject each(ThreadContext context, Block block) {
    303         this.storage.each(context, block);
    304         return this.storage;
    305     }
    306 
    307 
    308     @JRubyMethod(name = {"to_ary", "to_a"})
    309     public IRubyObject toArray(ThreadContext context) {
    310         return this.storage;
    311     }
    312 
    313     /*
    314      * call-seq:
    315      *     RepeatedField.dup => repeated_field
    316      *
    317      * Duplicates this repeated field with a shallow copy. References to all
    318      * non-primitive element objects (e.g., submessages) are shared.
    319      */
    320     @JRubyMethod
    321     public IRubyObject dup(ThreadContext context) {
    322         RubyRepeatedField dup = new RubyRepeatedField(context.runtime, metaClass, fieldType, typeClass);
    323         for (int i = 0; i < this.storage.size(); i++) {
    324             dup.push(context, this.storage.eltInternal(i));
    325         }
    326         return dup;
    327     }
    328 
    329     // Java API
    330     protected IRubyObject get(int index) {
    331         return this.storage.eltInternal(index);
    332     }
    333 
    334     protected RubyRepeatedField deepCopy(ThreadContext context) {
    335         RubyRepeatedField copy = new RubyRepeatedField(context.runtime, metaClass, fieldType, typeClass);
    336         for (int i = 0; i < size(); i++) {
    337             IRubyObject value = storage.eltInternal(i);
    338             if (fieldType == Descriptors.FieldDescriptor.Type.MESSAGE) {
    339                 copy.storage.add(((RubyMessage) value).deepCopy(context));
    340             } else {
    341                 copy.storage.add(value);
    342             }
    343         }
    344         return copy;
    345     }
    346 
    347     protected int size() {
    348         return this.storage.size();
    349     }
    350 
    351     private IRubyObject defaultValue(ThreadContext context) {
    352         SentinelOuterClass.Sentinel sentinel = SentinelOuterClass.Sentinel.getDefaultInstance();
    353         Object value;
    354         switch (fieldType) {
    355             case INT32:
    356                 value = sentinel.getDefaultInt32();
    357                 break;
    358             case INT64:
    359                 value = sentinel.getDefaultInt64();
    360                 break;
    361             case UINT32:
    362                 value = sentinel.getDefaultUnit32();
    363                 break;
    364             case UINT64:
    365                 value = sentinel.getDefaultUint64();
    366                 break;
    367             case FLOAT:
    368                 value = sentinel.getDefaultFloat();
    369                 break;
    370             case DOUBLE:
    371                 value = sentinel.getDefaultDouble();
    372                 break;
    373             case BOOL:
    374                 value = sentinel.getDefaultBool();
    375                 break;
    376             case BYTES:
    377                 value = sentinel.getDefaultBytes();
    378                 break;
    379             case STRING:
    380                 value = sentinel.getDefaultString();
    381                 break;
    382             case ENUM:
    383                 IRubyObject defaultEnumLoc = context.runtime.newFixnum(0);
    384                 return RubyEnum.lookup(context, typeClass, defaultEnumLoc);
    385             default:
    386                 return context.runtime.getNil();
    387         }
    388         return Utils.wrapPrimaryValue(context, fieldType, value);
    389     }
    390 
    391     private void checkArrayElementType(ThreadContext context, RubyArray arr) {
    392         for (int i = 0; i < arr.getLength(); i++) {
    393             Utils.checkType(context, fieldType, arr.eltInternal(i), (RubyModule) typeClass);
    394         }
    395     }
    396 
    397     private int normalizeArrayIndex(IRubyObject index) {
    398         int arrIndex = RubyNumeric.num2int(index);
    399         int arrSize = this.storage.size();
    400         if (arrIndex < 0 && arrSize > 0) {
    401             arrIndex = arrSize + arrIndex;
    402         }
    403         return arrIndex;
    404     }
    405 
    406     private RubyArray storage;
    407     private Descriptors.FieldDescriptor.Type fieldType;
    408     private IRubyObject typeClass;
    409 }
    410