• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
createRubyRepeatedField(Ruby runtime)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 
RubyRepeatedField(Ruby runtime, RubyClass klazz)60     public RubyRepeatedField(Ruby runtime, RubyClass klazz) {
61         super(runtime, klazz);
62     }
63 
RubyRepeatedField(Ruby runtime, RubyClass klazz, Descriptors.FieldDescriptor.Type fieldType, IRubyObject typeClass)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)
initialize(ThreadContext context, IRubyObject[] args)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 = "[]=")
indexSet(ThreadContext context, IRubyObject index, IRubyObject value)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", "[]"})
index(ThreadContext context, IRubyObject[] args)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", "<<"})
push(ThreadContext context, IRubyObject value)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)
pop_one(ThreadContext context)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
replace(ThreadContext context, IRubyObject list)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
clear(ThreadContext context)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"})
length(ThreadContext context)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 = {"+"})
plus(ThreadContext context, IRubyObject list)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
concat(ThreadContext context, IRubyObject list)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
hash(ThreadContext context)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 = "==")
eq(ThreadContext context, IRubyObject other)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
each(ThreadContext context, Block block)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"})
toArray(ThreadContext context)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
dup(ThreadContext context)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
get(int index)330     protected IRubyObject get(int index) {
331         return this.storage.eltInternal(index);
332     }
333 
deepCopy(ThreadContext context)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 
size()347     protected int size() {
348         return this.storage.size();
349     }
350 
defaultValue(ThreadContext context)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 
checkArrayElementType(ThreadContext context, RubyArray arr)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 
normalizeArrayIndex(IRubyObject index)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