Commit b481a4f9 authored by Chris Fallin's avatar Chris Fallin

Merge pull request #340 from skippy/add-array-like-methods

ruby: make repeated_field quack like an array
parents 5bd8b680 cd7ebbe5
...@@ -361,13 +361,13 @@ extern VALUE cRepeatedField; ...@@ -361,13 +361,13 @@ extern VALUE cRepeatedField;
RepeatedField* ruby_to_RepeatedField(VALUE value); RepeatedField* ruby_to_RepeatedField(VALUE value);
VALUE RepeatedField_each(VALUE _self); VALUE RepeatedField_each(VALUE _self);
VALUE RepeatedField_index(VALUE _self, VALUE _index); VALUE RepeatedField_index(int argc, VALUE* argv, VALUE _self);
void* RepeatedField_index_native(VALUE _self, int index); void* RepeatedField_index_native(VALUE _self, int index);
VALUE RepeatedField_index_set(VALUE _self, VALUE _index, VALUE val); VALUE RepeatedField_index_set(VALUE _self, VALUE _index, VALUE val);
void RepeatedField_reserve(RepeatedField* self, int new_size); void RepeatedField_reserve(RepeatedField* self, int new_size);
VALUE RepeatedField_push(VALUE _self, VALUE val); VALUE RepeatedField_push(VALUE _self, VALUE val);
void RepeatedField_push_native(VALUE _self, void* data); void RepeatedField_push_native(VALUE _self, void* data);
VALUE RepeatedField_pop(VALUE _self); VALUE RepeatedField_pop_one(VALUE _self);
VALUE RepeatedField_insert(int argc, VALUE* argv, VALUE _self); VALUE RepeatedField_insert(int argc, VALUE* argv, VALUE _self);
VALUE RepeatedField_replace(VALUE _self, VALUE list); VALUE RepeatedField_replace(VALUE _self, VALUE list);
VALUE RepeatedField_clear(VALUE _self); VALUE RepeatedField_clear(VALUE _self);
......
...@@ -55,6 +55,21 @@ static int index_position(VALUE _index, RepeatedField* repeated_field) { ...@@ -55,6 +55,21 @@ static int index_position(VALUE _index, RepeatedField* repeated_field) {
return index; return index;
} }
VALUE RepeatedField_subarray(VALUE _self, long beg, long len) {
RepeatedField* self = ruby_to_RepeatedField(_self);
int element_size = native_slot_size(self->field_type);
upb_fieldtype_t field_type = self->field_type;
VALUE field_type_class = self->field_type_class;
size_t off = beg * element_size;
VALUE ary = rb_ary_new2(len);
for (int i = beg; i < beg + len; i++, off += element_size) {
void* mem = ((uint8_t *)self->elements) + off;
VALUE elem = native_slot_get(field_type, field_type_class, mem);
rb_ary_push(ary, elem);
}
return ary;
}
/* /*
* call-seq: * call-seq:
...@@ -76,28 +91,57 @@ VALUE RepeatedField_each(VALUE _self) { ...@@ -76,28 +91,57 @@ VALUE RepeatedField_each(VALUE _self) {
VALUE val = native_slot_get(field_type, field_type_class, memory); VALUE val = native_slot_get(field_type, field_type_class, memory);
rb_yield(val); rb_yield(val);
} }
return Qnil; return _self;
} }
/* /*
* call-seq: * call-seq:
* RepeatedField.[](index) => value * RepeatedField.[](index) => value
* *
* Accesses the element at the given index. Returns nil on out-of-bounds * Accesses the element at the given index. Returns nil on out-of-bounds
*/ */
VALUE RepeatedField_index(VALUE _self, VALUE _index) { VALUE RepeatedField_index(int argc, VALUE* argv, VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self); RepeatedField* self = ruby_to_RepeatedField(_self);
int element_size = native_slot_size(self->field_type); int element_size = native_slot_size(self->field_type);
upb_fieldtype_t field_type = self->field_type; upb_fieldtype_t field_type = self->field_type;
VALUE field_type_class = self->field_type_class; VALUE field_type_class = self->field_type_class;
int index = index_position(_index, self); VALUE arg = argv[0];
if (index < 0 || index >= self->size) { long beg, len;
if (argc == 1){
if (FIXNUM_P(arg)) {
/* standard case */
int index = index_position(argv[0], self);
if (index < 0 || index >= self->size) {
return Qnil;
}
void* memory = (void *) (((uint8_t *)self->elements) + index * element_size);
return native_slot_get(field_type, field_type_class, memory);
}else{
/* check if idx is Range */
size_t off;
switch (rb_range_beg_len(arg, &beg, &len, self->size, 0)) {
case Qfalse:
break;
case Qnil:
return Qnil;
default:
return RepeatedField_subarray(_self, beg, len);
}
}
}
/* assume 2 arguments */
beg = NUM2LONG(argv[0]);
len = NUM2LONG(argv[1]);
if (beg < 0) {
beg += self->size;
}
if (beg >= self->size) {
return Qnil; return Qnil;
} }
return RepeatedField_subarray(_self, beg, len);
void* memory = (void *) (((uint8_t *)self->elements) + index * element_size);
return native_slot_get(field_type, field_type_class, memory);
} }
/* /*
...@@ -173,6 +217,7 @@ VALUE RepeatedField_push(VALUE _self, VALUE val) { ...@@ -173,6 +217,7 @@ VALUE RepeatedField_push(VALUE _self, VALUE val) {
return _self; return _self;
} }
// Used by parsing handlers. // Used by parsing handlers.
void RepeatedField_push_native(VALUE _self, void* data) { void RepeatedField_push_native(VALUE _self, void* data) {
RepeatedField* self = ruby_to_RepeatedField(_self); RepeatedField* self = ruby_to_RepeatedField(_self);
...@@ -193,19 +238,15 @@ void* RepeatedField_index_native(VALUE _self, int index) { ...@@ -193,19 +238,15 @@ void* RepeatedField_index_native(VALUE _self, int index) {
} }
/* /*
* call-seq: * Private ruby method, used by RepeatedField.pop
* RepeatedField.pop => value
*
* Removes the last element and returns it. Throws an exception if the repeated
* field is empty.
*/ */
VALUE RepeatedField_pop(VALUE _self) { VALUE RepeatedField_pop_one(VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self); RepeatedField* self = ruby_to_RepeatedField(_self);
upb_fieldtype_t field_type = self->field_type; upb_fieldtype_t field_type = self->field_type;
VALUE field_type_class = self->field_type_class; VALUE field_type_class = self->field_type_class;
int element_size = native_slot_size(field_type); int element_size = native_slot_size(field_type);
if (self->size == 0) { if (self->size == 0) {
rb_raise(rb_eRangeError, "Pop from empty repeated field is not allowed."); return Qnil;
} }
int index = self->size - 1; int index = self->size - 1;
void* memory = (void *) (((uint8_t *)self->elements) + index * element_size); void* memory = (void *) (((uint8_t *)self->elements) + index * element_size);
...@@ -214,19 +255,6 @@ VALUE RepeatedField_pop(VALUE _self) { ...@@ -214,19 +255,6 @@ VALUE RepeatedField_pop(VALUE _self) {
return ret; return ret;
} }
/*
* call-seq:
* RepeatedField.insert(*args)
*
* Pushes each arg in turn onto the end of the repeated field.
*/
VALUE RepeatedField_insert(int argc, VALUE* argv, VALUE _self) {
for (int i = 0; i < argc; i++) {
RepeatedField_push(_self, argv[i]);
}
return Qnil;
}
/* /*
* call-seq: * call-seq:
* RepeatedField.replace(list) * RepeatedField.replace(list)
...@@ -240,7 +268,7 @@ VALUE RepeatedField_replace(VALUE _self, VALUE list) { ...@@ -240,7 +268,7 @@ VALUE RepeatedField_replace(VALUE _self, VALUE list) {
for (int i = 0; i < RARRAY_LEN(list); i++) { for (int i = 0; i < RARRAY_LEN(list); i++) {
RepeatedField_push(_self, rb_ary_entry(list, i)); RepeatedField_push(_self, rb_ary_entry(list, i));
} }
return Qnil; return list;
} }
/* /*
...@@ -252,7 +280,7 @@ VALUE RepeatedField_replace(VALUE _self, VALUE list) { ...@@ -252,7 +280,7 @@ VALUE RepeatedField_replace(VALUE _self, VALUE list) {
VALUE RepeatedField_clear(VALUE _self) { VALUE RepeatedField_clear(VALUE _self) {
RepeatedField* self = ruby_to_RepeatedField(_self); RepeatedField* self = ruby_to_RepeatedField(_self);
self->size = 0; self->size = 0;
return Qnil; return _self;
} }
/* /*
...@@ -341,7 +369,6 @@ VALUE RepeatedField_to_ary(VALUE _self) { ...@@ -341,7 +369,6 @@ VALUE RepeatedField_to_ary(VALUE _self) {
for (int i = 0; i < self->size; i++, off += elem_size) { for (int i = 0; i < self->size; i++, off += elem_size) {
void* mem = ((uint8_t *)self->elements) + off; void* mem = ((uint8_t *)self->elements) + off;
VALUE elem = native_slot_get(field_type, self->field_type_class, mem); VALUE elem = native_slot_get(field_type, self->field_type_class, mem);
rb_ary_push(ary, elem); rb_ary_push(ary, elem);
} }
return ary; return ary;
...@@ -417,19 +444,6 @@ VALUE RepeatedField_hash(VALUE _self) { ...@@ -417,19 +444,6 @@ VALUE RepeatedField_hash(VALUE _self) {
return hash; return hash;
} }
/*
* call-seq:
* RepeatedField.inspect => string
*
* Returns a string representing this repeated field's elements. It will be
* formated as "[<element>, <element>, ...]", with each element's string
* representation computed by its own #inspect method.
*/
VALUE RepeatedField_inspect(VALUE _self) {
VALUE self_ary = RepeatedField_to_ary(_self);
return rb_funcall(self_ary, rb_intern("inspect"), 0);
}
/* /*
* call-seq: * call-seq:
* RepeatedField.+(other) => repeated field * RepeatedField.+(other) => repeated field
...@@ -466,6 +480,22 @@ VALUE RepeatedField_plus(VALUE _self, VALUE list) { ...@@ -466,6 +480,22 @@ VALUE RepeatedField_plus(VALUE _self, VALUE list) {
return dupped; return dupped;
} }
/*
* call-seq:
* RepeatedField.concat(other) => self
*
* concats the passed in array to self. Returns a Ruby array.
*/
VALUE RepeatedField_concat(VALUE _self, VALUE list) {
RepeatedField* self = ruby_to_RepeatedField(_self);
Check_Type(list, T_ARRAY);
for (int i = 0; i < RARRAY_LEN(list); i++) {
RepeatedField_push(_self, rb_ary_entry(list, i));
}
return _self;
}
void validate_type_class(upb_fieldtype_t type, VALUE klass) { void validate_type_class(upb_fieldtype_t type, VALUE klass) {
if (rb_iv_get(klass, kDescriptorInstanceVar) == Qnil) { if (rb_iv_get(klass, kDescriptorInstanceVar) == Qnil) {
rb_raise(rb_eArgError, rb_raise(rb_eArgError,
...@@ -585,22 +615,23 @@ void RepeatedField_register(VALUE module) { ...@@ -585,22 +615,23 @@ void RepeatedField_register(VALUE module) {
rb_define_method(klass, "initialize", rb_define_method(klass, "initialize",
RepeatedField_init, -1); RepeatedField_init, -1);
rb_define_method(klass, "each", RepeatedField_each, 0); rb_define_method(klass, "each", RepeatedField_each, 0);
rb_define_method(klass, "[]", RepeatedField_index, 1); rb_define_method(klass, "[]", RepeatedField_index, -1);
rb_define_method(klass, "at", RepeatedField_index, -1);
rb_define_method(klass, "[]=", RepeatedField_index_set, 2); rb_define_method(klass, "[]=", RepeatedField_index_set, 2);
rb_define_method(klass, "push", RepeatedField_push, 1); rb_define_method(klass, "push", RepeatedField_push, 1);
rb_define_method(klass, "<<", RepeatedField_push, 1); rb_define_method(klass, "<<", RepeatedField_push, 1);
rb_define_method(klass, "pop", RepeatedField_pop, 0); rb_define_private_method(klass, "pop_one", RepeatedField_pop_one, 0);
rb_define_method(klass, "insert", RepeatedField_insert, -1);
rb_define_method(klass, "replace", RepeatedField_replace, 1); rb_define_method(klass, "replace", RepeatedField_replace, 1);
rb_define_method(klass, "clear", RepeatedField_clear, 0); rb_define_method(klass, "clear", RepeatedField_clear, 0);
rb_define_method(klass, "length", RepeatedField_length, 0); rb_define_method(klass, "length", RepeatedField_length, 0);
rb_define_method(klass, "size", RepeatedField_length, 0);
rb_define_method(klass, "dup", RepeatedField_dup, 0); rb_define_method(klass, "dup", RepeatedField_dup, 0);
// Also define #clone so that we don't inherit Object#clone. // Also define #clone so that we don't inherit Object#clone.
rb_define_method(klass, "clone", RepeatedField_dup, 0); rb_define_method(klass, "clone", RepeatedField_dup, 0);
rb_define_method(klass, "==", RepeatedField_eq, 1); rb_define_method(klass, "==", RepeatedField_eq, 1);
rb_define_method(klass, "to_ary", RepeatedField_to_ary, 0); rb_define_method(klass, "to_ary", RepeatedField_to_ary, 0);
rb_define_method(klass, "hash", RepeatedField_hash, 0); rb_define_method(klass, "hash", RepeatedField_hash, 0);
rb_define_method(klass, "inspect", RepeatedField_inspect, 0);
rb_define_method(klass, "+", RepeatedField_plus, 1); rb_define_method(klass, "+", RepeatedField_plus, 1);
rb_define_method(klass, "concat", RepeatedField_concat, 1);
rb_include_module(klass, rb_mEnumerable); rb_include_module(klass, rb_mEnumerable);
} }
...@@ -28,12 +28,160 @@ ...@@ -28,12 +28,160 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# add syntatic sugar on top of the core library require 'forwardable'
#
# This class makes RepeatedField act (almost-) like a Ruby Array.
# It has convenience methods that extend the core C or Java based
# methods.
#
# This is a best-effort to mirror Array behavior. Two comments:
# 1) patches always welcome :)
# 2) if performance is an issue, feel free to rewrite the method
# in jruby and C. The source code has plenty of examples
#
# KNOWN ISSUES
# - #[]= doesn't allow less used approaches such as `arr[1, 2] = 'fizz'`
# - #concat should return the orig array
# - #push should accept multiple arguments and push them all at the same time
#
module Google module Google
module Protobuf module Protobuf
class RepeatedField class RepeatedField
extend Forwardable
# methods defined in C or Java:
# +
# [], at
# []=
# concat
# clear
# dup, clone
# each
# push, <<
# replace
# length, size
# ==
# to_ary, to_a
# also all enumerable
#
# NOTE: using delegators rather than method_missing to make the
# relationship explicit instead of implicit
def_delegators :to_ary,
:&, :*, :-, :'<=>',
:assoc, :bsearch, :combination, :compact, :count, :cycle,
:drop, :drop_while, :eql?, :fetch, :find_index, :flatten,
:include?, :index, :inspect, :join,
:pack, :permutation, :product, :pretty_print, :pretty_print_cycle,
:rassoc, :repeated_combination, :repeated_permutation, :reverse,
:rindex, :rotate, :sample, :shuffle, :shelljoin, :slice,
:to_s, :transpose, :uniq, :|
def first(n=nil)
n ? self[0..n] : self[0]
end
def last(n=nil)
n ? self[(self.size-n-1)..-1] : self[-1]
end
def pop(n=nil)
if n
results = []
n.times{ results << pop_one }
return results
else
return pop_one
end
end
def empty?
self.size == 0
end
# array aliases into enumerable
alias_method :each_index, :each_with_index
alias_method :slice, :[]
alias_method :values_at, :select
alias_method :map, :collect
class << self
def define_array_wrapper_method(method_name)
define_method(method_name) do |*args, &block|
arr = self.to_a
result = arr.send(method_name, *args)
self.replace(arr)
return result if result
return block ? block.call : result
end
end
private :define_array_wrapper_method
def define_array_wrapper_with_result_method(method_name)
define_method(method_name) do |*args, &block|
# result can be an Enumerator, Array, or nil
# Enumerator can sometimes be returned if a block is an optional argument and it is not passed in
# nil usually specifies that no change was made
result = self.to_a.send(method_name, *args, &block)
if result
new_arr = result.to_a
self.replace(new_arr)
if result.is_a?(Enumerator)
# generate a fresh enum; rewinding the exiting one, in Ruby 2.2, will
# reset the enum with the same length, but all the #next calls will
# return nil
result = new_arr.to_enum
# generate a wrapper enum so any changes which occur by a chained
# enum can be captured
ie = ProxyingEnumerator.new(self, result)
result = ie.to_enum
end
end
result
end
end
private :define_array_wrapper_with_result_method
end
%w(delete delete_at delete_if shift slice! unshift).each do |method_name|
define_array_wrapper_method(method_name)
end
%w(collect! compact! fill flatten! insert reverse!
rotate! select! shuffle! sort! sort_by! uniq!).each do |method_name|
define_array_wrapper_with_result_method(method_name)
end
alias_method :keep_if, :select!
alias_method :map!, :collect!
alias_method :reject!, :delete_if
# propagates changes made by user of enumerator back to the original repeated field.
# This only applies in cases where the calling function which created the enumerator,
# such as #sort!, modifies itself rather than a new array, such as #sort
class ProxyingEnumerator < Struct.new(:repeated_field, :external_enumerator)
def each(*args, &block)
results = []
external_enumerator.each_with_index do |val, i|
result = yield(val)
results << result
#nil means no change occured from yield; usually occurs when #to_a is called
if result
repeated_field[i] = result if result != val
end
end
results
end
end
alias_method :size, :length
end end
end end
......
...@@ -71,6 +71,17 @@ public class RubyFieldDescriptor extends RubyObject { ...@@ -71,6 +71,17 @@ public class RubyFieldDescriptor extends RubyObject {
return this; return this;
} }
/*
* call-seq:
* FieldDescriptor.label
*
* Return the label of this field.
*/
@JRubyMethod(name = "label")
public IRubyObject getLabel(ThreadContext context) {
return this.label;
}
/* /*
* call-seq: * call-seq:
* FieldDescriptor.label = label * FieldDescriptor.label = label
...@@ -80,8 +91,10 @@ public class RubyFieldDescriptor extends RubyObject { ...@@ -80,8 +91,10 @@ public class RubyFieldDescriptor extends RubyObject {
*/ */
@JRubyMethod(name = "label=") @JRubyMethod(name = "label=")
public IRubyObject setLabel(ThreadContext context, IRubyObject value) { public IRubyObject setLabel(ThreadContext context, IRubyObject value) {
String labelName = value.asJavaString();
this.label = context.runtime.newSymbol(labelName.toLowerCase());
this.builder.setLabel( this.builder.setLabel(
DescriptorProtos.FieldDescriptorProto.Label.valueOf("LABEL_" + value.asJavaString().toUpperCase())); DescriptorProtos.FieldDescriptorProto.Label.valueOf("LABEL_" + labelName.toUpperCase()));
return context.runtime.getNil(); return context.runtime.getNil();
} }
...@@ -89,18 +102,13 @@ public class RubyFieldDescriptor extends RubyObject { ...@@ -89,18 +102,13 @@ public class RubyFieldDescriptor extends RubyObject {
* call-seq: * call-seq:
* FieldDescriptor.name => name * FieldDescriptor.name => name
* *
* Returns the name of this field. * Returns the name of this field as a Ruby String, or nil if it is not set.
*/ */
@JRubyMethod(name = "name") @JRubyMethod(name = "name")
public IRubyObject getName(ThreadContext context) { public IRubyObject getName(ThreadContext context) {
return this.name; return this.name;
} }
@JRubyMethod(name = "subtype")
public IRubyObject getSubType(ThreadContext context) {
return subType;
}
/* /*
* call-seq: * call-seq:
* FieldDescriptor.name = name * FieldDescriptor.name = name
...@@ -116,6 +124,12 @@ public class RubyFieldDescriptor extends RubyObject { ...@@ -116,6 +124,12 @@ public class RubyFieldDescriptor extends RubyObject {
return context.runtime.getNil(); return context.runtime.getNil();
} }
@JRubyMethod(name = "subtype")
public IRubyObject getSubType(ThreadContext context) {
return subType;
}
/* /*
* call-seq: * call-seq:
* FieldDescriptor.type => type * FieldDescriptor.type => type
...@@ -144,6 +158,18 @@ public class RubyFieldDescriptor extends RubyObject { ...@@ -144,6 +158,18 @@ public class RubyFieldDescriptor extends RubyObject {
return context.runtime.getNil(); return context.runtime.getNil();
} }
/*
* call-seq:
* FieldDescriptor.number => number
*
* Returns this field's number, as a Ruby Integer, or nil if not yet set.
*
*/
@JRubyMethod(name = "number")
public IRubyObject getnumber(ThreadContext context) {
return this.number;
}
/* /*
* call-seq: * call-seq:
* FieldDescriptor.number = number * FieldDescriptor.number = number
...@@ -153,6 +179,7 @@ public class RubyFieldDescriptor extends RubyObject { ...@@ -153,6 +179,7 @@ public class RubyFieldDescriptor extends RubyObject {
*/ */
@JRubyMethod(name = "number=") @JRubyMethod(name = "number=")
public IRubyObject setNumber(ThreadContext context, IRubyObject value) { public IRubyObject setNumber(ThreadContext context, IRubyObject value) {
this.number = value;
this.builder.setNumber(RubyNumeric.num2int(value)); this.builder.setNumber(RubyNumeric.num2int(value));
return context.runtime.getNil(); return context.runtime.getNil();
} }
...@@ -240,6 +267,8 @@ public class RubyFieldDescriptor extends RubyObject { ...@@ -240,6 +267,8 @@ public class RubyFieldDescriptor extends RubyObject {
private DescriptorProtos.FieldDescriptorProto.Builder builder; private DescriptorProtos.FieldDescriptorProto.Builder builder;
private IRubyObject name; private IRubyObject name;
private IRubyObject label;
private IRubyObject number;
private IRubyObject subType; private IRubyObject subType;
private IRubyObject oneofName; private IRubyObject oneofName;
private Descriptors.FieldDescriptor fieldDef; private Descriptors.FieldDescriptor fieldDef;
......
...@@ -40,6 +40,7 @@ import org.jruby.runtime.Block; ...@@ -40,6 +40,7 @@ import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator; import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext; import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject; import org.jruby.runtime.builtin.IRubyObject;
import java.util.Arrays;
@JRubyClass(name = "RepeatedClass", include = "Enumerable") @JRubyClass(name = "RepeatedClass", include = "Enumerable")
public class RubyRepeatedField extends RubyObject { public class RubyRepeatedField extends RubyObject {
...@@ -110,6 +111,10 @@ public class RubyRepeatedField extends RubyObject { ...@@ -110,6 +111,10 @@ public class RubyRepeatedField extends RubyObject {
public IRubyObject indexSet(ThreadContext context, IRubyObject index, IRubyObject value) { public IRubyObject indexSet(ThreadContext context, IRubyObject index, IRubyObject value) {
int arrIndex = normalizeArrayIndex(index); int arrIndex = normalizeArrayIndex(index);
Utils.checkType(context, fieldType, value, (RubyModule) typeClass); Utils.checkType(context, fieldType, value, (RubyModule) typeClass);
IRubyObject defaultValue = defaultValue(context);
for (int i = this.storage.size(); i < arrIndex; i++) {
this.storage.set(i, defaultValue);
}
this.storage.set(arrIndex, value); this.storage.set(arrIndex, value);
return context.runtime.getNil(); return context.runtime.getNil();
} }
...@@ -120,27 +125,35 @@ public class RubyRepeatedField extends RubyObject { ...@@ -120,27 +125,35 @@ public class RubyRepeatedField extends RubyObject {
* *
* Accesses the element at the given index. Returns nil on out-of-bounds * Accesses the element at the given index. Returns nil on out-of-bounds
*/ */
@JRubyMethod(name = "[]") @JRubyMethod(required=1, optional=1, name = {"at", "[]"})
public IRubyObject index(ThreadContext context, IRubyObject index) { public IRubyObject index(ThreadContext context, IRubyObject[] args) {
int arrIndex = normalizeArrayIndex(index); if (args.length == 1){
if (arrIndex < 0 || arrIndex >= this.storage.size()) { IRubyObject arg = args[0];
return context.runtime.getNil(); if (Utils.isRubyNum(arg)) {
/* standard case */
int arrIndex = normalizeArrayIndex(arg);
if (arrIndex < 0 || arrIndex >= this.storage.size()) {
return context.runtime.getNil();
}
return this.storage.eltInternal(arrIndex);
} else if (arg instanceof RubyRange) {
RubyRange range = ((RubyRange) arg);
int beg = RubyNumeric.num2int(range.first(context));
int to = RubyNumeric.num2int(range.last(context));
int len = to - beg + 1;
return this.storage.subseq(beg, len);
}
} }
return this.storage.eltInternal(arrIndex); /* assume 2 arguments */
} int beg = RubyNumeric.num2int(args[0]);
int len = RubyNumeric.num2int(args[1]);
/* if (beg < 0) {
* call-seq: beg += this.storage.size();
* RepeatedField.insert(*args)
*
* Pushes each arg in turn onto the end of the repeated field.
*/
@JRubyMethod(rest = true)
public IRubyObject insert(ThreadContext context, IRubyObject[] args) {
for (int i = 0; i < args.length; i++) {
push(context, args[i]);
} }
return context.runtime.getNil(); if (beg >= this.storage.size()) {
return context.runtime.getNil();
}
return this.storage.subseq(beg, len);
} }
/* /*
...@@ -151,20 +164,19 @@ public class RubyRepeatedField extends RubyObject { ...@@ -151,20 +164,19 @@ public class RubyRepeatedField extends RubyObject {
*/ */
@JRubyMethod(name = {"push", "<<"}) @JRubyMethod(name = {"push", "<<"})
public IRubyObject push(ThreadContext context, IRubyObject value) { public IRubyObject push(ThreadContext context, IRubyObject value) {
Utils.checkType(context, fieldType, value, (RubyModule) typeClass); if (!(fieldType == Descriptors.FieldDescriptor.Type.MESSAGE &&
value == context.runtime.getNil())) {
Utils.checkType(context, fieldType, value, (RubyModule) typeClass);
}
this.storage.add(value); this.storage.add(value);
return this; return this.storage;
} }
/* /*
* call-seq: * private Ruby method used by RepeatedField.pop
* RepeatedField.pop => value
*
* Removes the last element and returns it. Throws an exception if the repeated
* field is empty.
*/ */
@JRubyMethod @JRubyMethod(visibility = org.jruby.runtime.Visibility.PRIVATE)
public IRubyObject pop(ThreadContext context) { public IRubyObject pop_one(ThreadContext context) {
IRubyObject ret = this.storage.last(); IRubyObject ret = this.storage.last();
this.storage.remove(ret); this.storage.remove(ret);
return ret; return ret;
...@@ -181,7 +193,7 @@ public class RubyRepeatedField extends RubyObject { ...@@ -181,7 +193,7 @@ public class RubyRepeatedField extends RubyObject {
RubyArray arr = (RubyArray) list; RubyArray arr = (RubyArray) list;
checkArrayElementType(context, arr); checkArrayElementType(context, arr);
this.storage = arr; this.storage = arr;
return context.runtime.getNil(); return this.storage;
} }
/* /*
...@@ -193,7 +205,7 @@ public class RubyRepeatedField extends RubyObject { ...@@ -193,7 +205,7 @@ public class RubyRepeatedField extends RubyObject {
@JRubyMethod @JRubyMethod
public IRubyObject clear(ThreadContext context) { public IRubyObject clear(ThreadContext context) {
this.storage.clear(); this.storage.clear();
return context.runtime.getNil(); return this.storage;
} }
/* /*
...@@ -202,7 +214,7 @@ public class RubyRepeatedField extends RubyObject { ...@@ -202,7 +214,7 @@ public class RubyRepeatedField extends RubyObject {
* *
* Returns the length of this repeated field. * Returns the length of this repeated field.
*/ */
@JRubyMethod(name = {"count", "length"}) @JRubyMethod(name = {"length", "size"})
public IRubyObject length(ThreadContext context) { public IRubyObject length(ThreadContext context) {
return context.runtime.newFixnum(this.storage.size()); return context.runtime.newFixnum(this.storage.size());
} }
...@@ -215,7 +227,7 @@ public class RubyRepeatedField extends RubyObject { ...@@ -215,7 +227,7 @@ public class RubyRepeatedField extends RubyObject {
* repeated field's elements and other's elements. The other (second) list may * repeated field's elements and other's elements. The other (second) list may
* be either another repeated field or a Ruby array. * be either another repeated field or a Ruby array.
*/ */
@JRubyMethod(name = "+") @JRubyMethod(name = {"+"})
public IRubyObject plus(ThreadContext context, IRubyObject list) { public IRubyObject plus(ThreadContext context, IRubyObject list) {
RubyRepeatedField dup = (RubyRepeatedField) dup(context); RubyRepeatedField dup = (RubyRepeatedField) dup(context);
if (list instanceof RubyArray) { if (list instanceof RubyArray) {
...@@ -231,6 +243,27 @@ public class RubyRepeatedField extends RubyObject { ...@@ -231,6 +243,27 @@ public class RubyRepeatedField extends RubyObject {
return dup; return dup;
} }
/*
* call-seq:
* RepeatedField.concat(other) => self
*
* concats the passed in array to self. Returns a Ruby array.
*/
@JRubyMethod
public IRubyObject concat(ThreadContext context, IRubyObject list) {
if (list instanceof RubyArray) {
checkArrayElementType(context, (RubyArray) list);
this.storage.addAll((RubyArray) list);
} else {
RubyRepeatedField repeatedField = (RubyRepeatedField) list;
if (! fieldType.equals(repeatedField.fieldType) || (typeClass != null && !
typeClass.equals(repeatedField.typeClass)))
throw context.runtime.newArgumentError("Attempt to append RepeatedField with different element type.");
this.storage.addAll((RubyArray) repeatedField.toArray(context));
}
return this.storage;
}
/* /*
* call-seq: * call-seq:
* RepeatedField.hash => hash_value * RepeatedField.hash => hash_value
...@@ -239,7 +272,7 @@ public class RubyRepeatedField extends RubyObject { ...@@ -239,7 +272,7 @@ public class RubyRepeatedField extends RubyObject {
*/ */
@JRubyMethod @JRubyMethod
public IRubyObject hash(ThreadContext context) { public IRubyObject hash(ThreadContext context) {
int hashCode = System.identityHashCode(this.storage); int hashCode = this.storage.hashCode();
return context.runtime.newFixnum(hashCode); return context.runtime.newFixnum(hashCode);
} }
...@@ -268,17 +301,12 @@ public class RubyRepeatedField extends RubyObject { ...@@ -268,17 +301,12 @@ public class RubyRepeatedField extends RubyObject {
@JRubyMethod @JRubyMethod
public IRubyObject each(ThreadContext context, Block block) { public IRubyObject each(ThreadContext context, Block block) {
this.storage.each(context, block); this.storage.each(context, block);
return context.runtime.getNil(); return this.storage;
} }
@JRubyMethod(name = {"to_ary", "to_a"}) @JRubyMethod(name = {"to_ary", "to_a"})
public IRubyObject toArray(ThreadContext context) { public IRubyObject toArray(ThreadContext context) {
for (int i = 0; i < this.storage.size(); i++) {
IRubyObject defaultValue = defaultValue(context);
if (storage.eltInternal(i).isNil()) {
storage.set(i, defaultValue);
}
}
return this.storage; return this.storage;
} }
...@@ -298,31 +326,6 @@ public class RubyRepeatedField extends RubyObject { ...@@ -298,31 +326,6 @@ public class RubyRepeatedField extends RubyObject {
return dup; return dup;
} }
/*
* call-seq:
* RepeatedField.inspect => string
*
* Returns a string representing this repeated field's elements. It will be
* formated as "[<element>, <element>, ...]", with each element's string
* representation computed by its own #inspect method.
*/
@JRubyMethod
public IRubyObject inspect() {
StringBuilder str = new StringBuilder("[");
for (int i = 0; i < this.storage.size(); i++) {
str.append(storage.eltInternal(i).inspect());
str.append(", ");
}
if (str.length() > 1) {
str.replace(str.length() - 2, str.length(), "]");
} else {
str.append("]");
}
return getRuntime().newString(str.toString());
}
// Java API // Java API
protected IRubyObject get(int index) { protected IRubyObject get(int index) {
return this.storage.eltInternal(index); return this.storage.eltInternal(index);
...@@ -376,6 +379,9 @@ public class RubyRepeatedField extends RubyObject { ...@@ -376,6 +379,9 @@ public class RubyRepeatedField extends RubyObject {
case STRING: case STRING:
value = sentinel.getDefaultString(); value = sentinel.getDefaultString();
break; break;
case ENUM:
IRubyObject defaultEnumLoc = context.runtime.newFixnum(0);
return RubyEnum.lookup(context, typeClass, defaultEnumLoc);
default: default:
return context.runtime.getNil(); return context.runtime.getNil();
} }
......
...@@ -178,7 +178,7 @@ module BasicTest ...@@ -178,7 +178,7 @@ module BasicTest
:optional_msg => TestMessage2.new, :optional_msg => TestMessage2.new,
:repeated_string => ["hello", "there", "world"]) :repeated_string => ["hello", "there", "world"])
expected = '<BasicTest::TestMessage: optional_int32: -42, optional_int64: 0, optional_uint32: 0, optional_uint64: 0, optional_bool: false, optional_float: 0.0, optional_double: 0.0, optional_string: "", optional_bytes: "", optional_msg: <BasicTest::TestMessage2: foo: 0>, optional_enum: :A, repeated_int32: [], repeated_int64: [], repeated_uint32: [], repeated_uint64: [], repeated_bool: [], repeated_float: [], repeated_double: [], repeated_string: ["hello", "there", "world"], repeated_bytes: [], repeated_msg: [], repeated_enum: []>' expected = '<BasicTest::TestMessage: optional_int32: -42, optional_int64: 0, optional_uint32: 0, optional_uint64: 0, optional_bool: false, optional_float: 0.0, optional_double: 0.0, optional_string: "", optional_bytes: "", optional_msg: <BasicTest::TestMessage2: foo: 0>, optional_enum: :A, repeated_int32: [], repeated_int64: [], repeated_uint32: [], repeated_uint64: [], repeated_bool: [], repeated_float: [], repeated_double: [], repeated_string: ["hello", "there", "world"], repeated_bytes: [], repeated_msg: [], repeated_enum: []>'
assert m.inspect == expected assert_equal expected, m.inspect
end end
def test_hash def test_hash
...@@ -276,7 +276,7 @@ module BasicTest ...@@ -276,7 +276,7 @@ module BasicTest
assert l.inspect == '[5, 2, 3, 4]' assert l.inspect == '[5, 2, 3, 4]'
l.insert(7, 8, 9) l.concat([7, 8, 9])
assert l == [5, 2, 3, 4, 7, 8, 9] assert l == [5, 2, 3, 4, 7, 8, 9]
assert l.pop == 9 assert l.pop == 9
assert l == [5, 2, 3, 4, 7, 8] assert l == [5, 2, 3, 4, 7, 8]
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment