• 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.*;
36 import org.jruby.*;
37 import org.jruby.anno.JRubyMethod;
38 import org.jruby.runtime.Block;
39 import org.jruby.runtime.Helpers;
40 import org.jruby.runtime.ThreadContext;
41 import org.jruby.runtime.builtin.IRubyObject;
42 import org.jruby.util.ByteList;
43 
44 import java.util.HashMap;
45 import java.util.Map;
46 
47 public class RubyMessage extends RubyObject {
RubyMessage(Ruby ruby, RubyClass klazz, Descriptors.Descriptor descriptor)48     public RubyMessage(Ruby ruby, RubyClass klazz, Descriptors.Descriptor descriptor) {
49         super(ruby, klazz);
50         this.descriptor = descriptor;
51     }
52 
53     /*
54      * call-seq:
55      *     Message.new(kwargs) => new_message
56      *
57      * Creates a new instance of the given message class. Keyword arguments may be
58      * provided with keywords corresponding to field names.
59      *
60      * Note that no literal Message class exists. Only concrete classes per message
61      * type exist, as provided by the #msgclass method on Descriptors after they
62      * have been added to a pool. The method definitions described here on the
63      * Message class are provided on each concrete message class.
64      */
65     @JRubyMethod(optional = 1)
initialize(final ThreadContext context, IRubyObject[] args)66     public IRubyObject initialize(final ThreadContext context, IRubyObject[] args) {
67         final Ruby runtime = context.runtime;
68         this.cRepeatedField = (RubyClass) runtime.getClassFromPath("Google::Protobuf::RepeatedField");
69         this.cMap = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Map");
70         this.builder = DynamicMessage.newBuilder(this.descriptor);
71         this.repeatedFields = new HashMap<Descriptors.FieldDescriptor, RubyRepeatedField>();
72         this.maps = new HashMap<Descriptors.FieldDescriptor, RubyMap>();
73         this.fields = new HashMap<Descriptors.FieldDescriptor, IRubyObject>();
74         this.oneofCases = new HashMap<Descriptors.OneofDescriptor, Descriptors.FieldDescriptor>();
75         if (args.length == 1) {
76             if (!(args[0] instanceof RubyHash)) {
77                 throw runtime.newArgumentError("expected Hash arguments.");
78             }
79             RubyHash hash = args[0].convertToHash();
80             hash.visitAll(new RubyHash.Visitor() {
81                 @Override
82                 public void visit(IRubyObject key, IRubyObject value) {
83                     if (!(key instanceof RubySymbol))
84                         throw runtime.newTypeError("Expected symbols as hash keys in initialization map.");
85                     final Descriptors.FieldDescriptor fieldDescriptor = findField(context, key);
86 
87                     if (Utils.isMapEntry(fieldDescriptor)) {
88                         if (!(value instanceof RubyHash))
89                             throw runtime.newArgumentError("Expected Hash object as initializer value for map field '" +  key.asJavaString() + "'.");
90 
91                         final RubyMap map = newMapForField(context, fieldDescriptor);
92                         map.mergeIntoSelf(context, value);
93                         maps.put(fieldDescriptor, map);
94                     } else if (fieldDescriptor.isRepeated()) {
95                         if (!(value instanceof RubyArray))
96                             throw runtime.newArgumentError("Expected array as initializer value for repeated field '" +  key.asJavaString() + "'.");
97                         RubyRepeatedField repeatedField = rubyToRepeatedField(context, fieldDescriptor, value);
98                         addRepeatedField(fieldDescriptor, repeatedField);
99                     } else {
100                         Descriptors.OneofDescriptor oneof = fieldDescriptor.getContainingOneof();
101                         if (oneof != null) {
102                             oneofCases.put(oneof, fieldDescriptor);
103                         }
104                         fields.put(fieldDescriptor, value);
105                     }
106 
107                 }
108             });
109         }
110         return this;
111     }
112 
113     /*
114      * call-seq:
115      *     Message.[]=(index, value)
116      *
117      * Sets a field's value by field name. The provided field name should be a
118      * string.
119      */
120     @JRubyMethod(name = "[]=")
indexSet(ThreadContext context, IRubyObject fieldName, IRubyObject value)121     public IRubyObject indexSet(ThreadContext context, IRubyObject fieldName, IRubyObject value) {
122         Descriptors.FieldDescriptor fieldDescriptor = findField(context, fieldName);
123         return setField(context, fieldDescriptor, value);
124     }
125 
126     /*
127      * call-seq:
128      *     Message.[](index) => value
129      *
130      * Accesses a field's value by field name. The provided field name should be a
131      * string.
132      */
133     @JRubyMethod(name = "[]")
index(ThreadContext context, IRubyObject fieldName)134     public IRubyObject index(ThreadContext context, IRubyObject fieldName) {
135         Descriptors.FieldDescriptor fieldDescriptor = findField(context, fieldName);
136         return getField(context, fieldDescriptor);
137     }
138 
139     /*
140      * call-seq:
141      *     Message.inspect => string
142      *
143      * Returns a human-readable string representing this message. It will be
144      * formatted as "<MessageType: field1: value1, field2: value2, ...>". Each
145      * field's value is represented according to its own #inspect method.
146      */
147     @JRubyMethod
inspect()148     public IRubyObject inspect() {
149         String cname = metaClass.getName();
150         StringBuilder sb = new StringBuilder("<");
151         sb.append(cname);
152         sb.append(": ");
153         sb.append(this.layoutInspect());
154         sb.append(">");
155 
156         return getRuntime().newString(sb.toString());
157     }
158 
159     /*
160      * call-seq:
161      *     Message.hash => hash_value
162      *
163      * Returns a hash value that represents this message's field values.
164      */
165     @JRubyMethod
hash(ThreadContext context)166     public IRubyObject hash(ThreadContext context) {
167         int hashCode = System.identityHashCode(this);
168         return context.runtime.newFixnum(hashCode);
169     }
170 
171     /*
172      * call-seq:
173      *     Message.==(other) => boolean
174      *
175      * Performs a deep comparison of this message with another. Messages are equal
176      * if they have the same type and if each field is equal according to the :==
177      * method's semantics (a more efficient comparison may actually be done if the
178      * field is of a primitive type).
179      */
180     @JRubyMethod(name = "==")
eq(ThreadContext context, IRubyObject other)181     public IRubyObject eq(ThreadContext context, IRubyObject other) {
182         Ruby runtime = context.runtime;
183         if (!(other instanceof RubyMessage))
184             return runtime.getFalse();
185         RubyMessage message = (RubyMessage) other;
186         if (descriptor != message.descriptor) {
187             return runtime.getFalse();
188         }
189 
190         for (Descriptors.FieldDescriptor fdef : descriptor.getFields()) {
191             IRubyObject thisVal = getField(context, fdef);
192             IRubyObject thatVal = message.getField(context, fdef);
193             IRubyObject ret = thisVal.callMethod(context, "==", thatVal);
194             if (!ret.isTrue()) {
195                 return runtime.getFalse();
196             }
197         }
198         return runtime.getTrue();
199     }
200 
201     /*
202      * call-seq:
203      *     Message.method_missing(*args)
204      *
205      * Provides accessors and setters for message fields according to their field
206      * names. For any field whose name does not conflict with a built-in method, an
207      * accessor is provided with the same name as the field, and a setter is
208      * provided with the name of the field plus the '=' suffix. Thus, given a
209      * message instance 'msg' with field 'foo', the following code is valid:
210      *
211      *     msg.foo = 42
212      *     puts msg.foo
213      */
214     @JRubyMethod(name = "method_missing", rest = true)
methodMissing(ThreadContext context, IRubyObject[] args)215     public IRubyObject methodMissing(ThreadContext context, IRubyObject[] args) {
216         if (args.length == 1) {
217             RubyDescriptor rubyDescriptor = (RubyDescriptor) getDescriptor(context, metaClass);
218             IRubyObject oneofDescriptor = rubyDescriptor.lookupOneof(context, args[0]);
219             if (oneofDescriptor.isNil()) {
220                 if (!hasField(args[0])) {
221                     return Helpers.invokeSuper(context, this, metaClass, "method_missing", args, Block.NULL_BLOCK);
222                 }
223                 return index(context, args[0]);
224             }
225             RubyOneofDescriptor rubyOneofDescriptor = (RubyOneofDescriptor) oneofDescriptor;
226             Descriptors.FieldDescriptor fieldDescriptor =
227                     oneofCases.get(rubyOneofDescriptor.getOneofDescriptor());
228             if (fieldDescriptor == null)
229                 return context.runtime.getNil();
230 
231             return context.runtime.newSymbol(fieldDescriptor.getName());
232         } else {
233             // fieldName is RubySymbol
234             RubyString field = args[0].asString();
235             RubyString equalSign = context.runtime.newString(Utils.EQUAL_SIGN);
236             if (field.end_with_p(context, equalSign).isTrue()) {
237                 field.chomp_bang(context, equalSign);
238             }
239 
240             if (!hasField(field)) {
241                 return Helpers.invokeSuper(context, this, metaClass, "method_missing", args, Block.NULL_BLOCK);
242             }
243             return indexSet(context, field, args[1]);
244         }
245     }
246 
247     /**
248      * call-seq:
249      * Message.dup => new_message
250      * Performs a shallow copy of this message and returns the new copy.
251      */
252     @JRubyMethod
dup(ThreadContext context)253     public IRubyObject dup(ThreadContext context) {
254         RubyMessage dup = (RubyMessage) metaClass.newInstance(context, Block.NULL_BLOCK);
255         IRubyObject value;
256         for (Descriptors.FieldDescriptor fieldDescriptor : this.descriptor.getFields()) {
257             if (fieldDescriptor.isRepeated()) {
258                 dup.addRepeatedField(fieldDescriptor, this.getRepeatedField(context, fieldDescriptor));
259             } else if (fields.containsKey(fieldDescriptor)) {
260                 dup.fields.put(fieldDescriptor, fields.get(fieldDescriptor));
261             } else if (this.builder.hasField(fieldDescriptor)) {
262                 dup.fields.put(fieldDescriptor, wrapField(context, fieldDescriptor, this.builder.getField(fieldDescriptor)));
263             }
264         }
265         for (Descriptors.FieldDescriptor fieldDescriptor : maps.keySet()) {
266             dup.maps.put(fieldDescriptor, maps.get(fieldDescriptor));
267         }
268         return dup;
269     }
270 
271     /*
272      * call-seq:
273      *     Message.descriptor => descriptor
274      *
275      * Class method that returns the Descriptor instance corresponding to this
276      * message class's type.
277      */
278     @JRubyMethod(name = "descriptor", meta = true)
getDescriptor(ThreadContext context, IRubyObject recv)279     public static IRubyObject getDescriptor(ThreadContext context, IRubyObject recv) {
280         return ((RubyClass) recv).getInstanceVariable(Utils.DESCRIPTOR_INSTANCE_VAR);
281     }
282 
283     /*
284      * call-seq:
285      *     MessageClass.encode(msg) => bytes
286      *
287      * Encodes the given message object to its serialized form in protocol buffers
288      * wire format.
289      */
290     @JRubyMethod(meta = true)
encode(ThreadContext context, IRubyObject recv, IRubyObject value)291     public static IRubyObject encode(ThreadContext context, IRubyObject recv, IRubyObject value) {
292         RubyMessage message = (RubyMessage) value;
293         return context.runtime.newString(new ByteList(message.build(context).toByteArray()));
294     }
295 
296     /*
297      * call-seq:
298      *     MessageClass.decode(data) => message
299      *
300      * Decodes the given data (as a string containing bytes in protocol buffers wire
301      * format) under the interpretration given by this message class's definition
302      * and returns a message object with the corresponding field values.
303      */
304     @JRubyMethod(meta = true)
decode(ThreadContext context, IRubyObject recv, IRubyObject data)305     public static IRubyObject decode(ThreadContext context, IRubyObject recv, IRubyObject data) {
306         byte[] bin = data.convertToString().getBytes();
307         RubyMessage ret = (RubyMessage) ((RubyClass) recv).newInstance(context, Block.NULL_BLOCK);
308         try {
309             ret.builder.mergeFrom(bin);
310         } catch (InvalidProtocolBufferException e) {
311             throw context.runtime.newRuntimeError(e.getMessage());
312         }
313         return ret;
314     }
315 
316     /*
317      * call-seq:
318      *     MessageClass.encode_json(msg) => json_string
319      *
320      * Encodes the given message object into its serialized JSON representation.
321      */
322     @JRubyMethod(name = "encode_json", meta = true)
encodeJson(ThreadContext context, IRubyObject recv, IRubyObject msgRb)323     public static IRubyObject encodeJson(ThreadContext context, IRubyObject recv, IRubyObject msgRb) {
324         RubyMessage message = (RubyMessage) msgRb;
325         return Helpers.invoke(context, message.toHash(context), "to_json");
326     }
327 
328     /*
329      * call-seq:
330      *     MessageClass.decode_json(data) => message
331      *
332      * Decodes the given data (as a string containing bytes in protocol buffers wire
333      * format) under the interpretration given by this message class's definition
334      * and returns a message object with the corresponding field values.
335      */
336     @JRubyMethod(name = "decode_json", meta = true)
decodeJson(ThreadContext context, IRubyObject recv, IRubyObject json)337     public static IRubyObject decodeJson(ThreadContext context, IRubyObject recv, IRubyObject json) {
338         Ruby runtime = context.runtime;
339         RubyMessage ret = (RubyMessage) ((RubyClass) recv).newInstance(context, Block.NULL_BLOCK);
340         RubyModule jsonModule = runtime.getClassFromPath("JSON");
341         RubyHash opts = RubyHash.newHash(runtime);
342         opts.fastASet(runtime.newSymbol("symbolize_names"), runtime.getTrue());
343         IRubyObject[] args = new IRubyObject[] { Helpers.invoke(context, jsonModule, "parse", json, opts) };
344         ret.initialize(context, args);
345         return ret;
346     }
347 
348     @JRubyMethod(name = {"to_h", "to_hash"})
toHash(ThreadContext context)349     public IRubyObject toHash(ThreadContext context) {
350         Ruby runtime = context.runtime;
351         RubyHash ret = RubyHash.newHash(runtime);
352         for (Descriptors.FieldDescriptor fdef : this.descriptor.getFields()) {
353             IRubyObject value = getField(context, fdef);
354             if (!value.isNil()) {
355                 if (value.respondsTo("to_h")) {
356                     value = Helpers.invoke(context, value, "to_h");
357                 } else if (value.respondsTo("to_a")) {
358                     value = Helpers.invoke(context, value, "to_a");
359                 }
360             }
361             ret.fastASet(runtime.newSymbol(fdef.getName()), value);
362         }
363         return ret;
364     }
365 
build(ThreadContext context)366     protected DynamicMessage build(ThreadContext context) {
367         return build(context, 0);
368     }
369 
build(ThreadContext context, int depth)370     protected DynamicMessage build(ThreadContext context, int depth) {
371         if (depth > SINK_MAXIMUM_NESTING) {
372             throw context.runtime.newRuntimeError("Maximum recursion depth exceeded during encoding.");
373         }
374         for (Descriptors.FieldDescriptor fieldDescriptor : maps.keySet()) {
375             this.builder.clearField(fieldDescriptor);
376             RubyDescriptor mapDescriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
377             for (DynamicMessage kv : maps.get(fieldDescriptor).build(context, mapDescriptor)) {
378                 this.builder.addRepeatedField(fieldDescriptor, kv);
379             }
380         }
381         for (Descriptors.FieldDescriptor fieldDescriptor : repeatedFields.keySet()) {
382             RubyRepeatedField repeatedField = repeatedFields.get(fieldDescriptor);
383             this.builder.clearField(fieldDescriptor);
384             for (int i = 0; i < repeatedField.size(); i++) {
385                 Object item = convert(context, fieldDescriptor, repeatedField.get(i), depth);
386                 this.builder.addRepeatedField(fieldDescriptor, item);
387             }
388         }
389         for (Descriptors.FieldDescriptor fieldDescriptor : fields.keySet()) {
390             IRubyObject value = fields.get(fieldDescriptor);
391             this.builder.setField(fieldDescriptor, convert(context, fieldDescriptor, value, depth));
392         }
393         return this.builder.build();
394     }
395 
getDescriptor()396     protected Descriptors.Descriptor getDescriptor() {
397         return this.descriptor;
398     }
399 
400     // Internal use only, called by Google::Protobuf.deep_copy
deepCopy(ThreadContext context)401     protected IRubyObject deepCopy(ThreadContext context) {
402         RubyMessage copy = (RubyMessage) metaClass.newInstance(context, Block.NULL_BLOCK);
403         for (Descriptors.FieldDescriptor fdef : this.descriptor.getFields()) {
404             if (fdef.isRepeated()) {
405                 copy.addRepeatedField(fdef, this.getRepeatedField(context, fdef).deepCopy(context));
406             } else if (fields.containsKey(fdef)) {
407                 copy.fields.put(fdef, fields.get(fdef));
408             } else if (this.builder.hasField(fdef)) {
409                 copy.fields.put(fdef, wrapField(context, fdef, this.builder.getField(fdef)));
410             }
411         }
412         return copy;
413     }
414 
getRepeatedField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor)415     private RubyRepeatedField getRepeatedField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor) {
416         if (this.repeatedFields.containsKey(fieldDescriptor)) {
417             return this.repeatedFields.get(fieldDescriptor);
418         }
419         int count = this.builder.getRepeatedFieldCount(fieldDescriptor);
420         RubyRepeatedField ret = repeatedFieldForFieldDescriptor(context, fieldDescriptor);
421         for (int i = 0; i < count; i++) {
422             ret.push(context, wrapField(context, fieldDescriptor, this.builder.getRepeatedField(fieldDescriptor, i)));
423         }
424         addRepeatedField(fieldDescriptor, ret);
425         return ret;
426     }
427 
addRepeatedField(Descriptors.FieldDescriptor fieldDescriptor, RubyRepeatedField repeatedField)428     private void addRepeatedField(Descriptors.FieldDescriptor fieldDescriptor, RubyRepeatedField repeatedField) {
429         this.repeatedFields.put(fieldDescriptor, repeatedField);
430     }
431 
buildFrom(ThreadContext context, DynamicMessage dynamicMessage)432     private IRubyObject buildFrom(ThreadContext context, DynamicMessage dynamicMessage) {
433         this.builder.mergeFrom(dynamicMessage);
434         return this;
435     }
436 
findField(ThreadContext context, IRubyObject fieldName)437     private Descriptors.FieldDescriptor findField(ThreadContext context, IRubyObject fieldName) {
438         String nameStr = fieldName.asJavaString();
439         Descriptors.FieldDescriptor ret = this.descriptor.findFieldByName(Utils.escapeIdentifier(nameStr));
440         if (ret == null)
441             throw context.runtime.newArgumentError("field " + fieldName.asJavaString() + " is not found");
442         return ret;
443     }
444 
hasField(IRubyObject fieldName)445     private boolean hasField(IRubyObject fieldName) {
446         String nameStr = fieldName.asJavaString();
447         return this.descriptor.findFieldByName(Utils.escapeIdentifier(nameStr)) != null;
448     }
449 
checkRepeatedFieldType(ThreadContext context, IRubyObject value, Descriptors.FieldDescriptor fieldDescriptor)450     private void checkRepeatedFieldType(ThreadContext context, IRubyObject value,
451                                         Descriptors.FieldDescriptor fieldDescriptor) {
452         Ruby runtime = context.runtime;
453         if (!(value instanceof RubyRepeatedField)) {
454             throw runtime.newTypeError("Expected repeated field array");
455         }
456     }
457 
458     // convert a ruby object to protobuf type, with type check
convert(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor, IRubyObject value, int depth)459     private Object convert(ThreadContext context,
460                            Descriptors.FieldDescriptor fieldDescriptor,
461                            IRubyObject value, int depth) {
462         Ruby runtime = context.runtime;
463         Object val = null;
464         switch (fieldDescriptor.getType()) {
465             case INT32:
466             case INT64:
467             case UINT32:
468             case UINT64:
469                 if (!Utils.isRubyNum(value)) {
470                     throw runtime.newTypeError("Expected number type for integral field.");
471                 }
472                 Utils.checkIntTypePrecision(context, fieldDescriptor.getType(), value);
473                 switch (fieldDescriptor.getType()) {
474                     case INT32:
475                         val = RubyNumeric.num2int(value);
476                         break;
477                     case INT64:
478                         val = RubyNumeric.num2long(value);
479                         break;
480                     case UINT32:
481                         val = Utils.num2uint(value);
482                         break;
483                     case UINT64:
484                         val = Utils.num2ulong(context.runtime, value);
485                         break;
486                     default:
487                         break;
488                 }
489                 break;
490             case FLOAT:
491                 if (!Utils.isRubyNum(value))
492                     throw runtime.newTypeError("Expected number type for float field.");
493                 val = (float) RubyNumeric.num2dbl(value);
494                 break;
495             case DOUBLE:
496                 if (!Utils.isRubyNum(value))
497                     throw runtime.newTypeError("Expected number type for double field.");
498                 val = RubyNumeric.num2dbl(value);
499                 break;
500             case BOOL:
501                 if (!(value instanceof RubyBoolean))
502                     throw runtime.newTypeError("Invalid argument for boolean field.");
503                 val = value.isTrue();
504                 break;
505             case BYTES:
506             case STRING:
507                 Utils.validateStringEncoding(context.runtime, fieldDescriptor.getType(), value);
508                 RubyString str = (RubyString) value;
509                 switch (fieldDescriptor.getType()) {
510                     case BYTES:
511                         val = ByteString.copyFrom(str.getBytes());
512                         break;
513                     case STRING:
514                         val = str.asJavaString();
515                         break;
516                     default:
517                         break;
518                 }
519                 break;
520             case MESSAGE:
521                 RubyClass typeClass = (RubyClass) ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor)).msgclass(context);
522                 if (!value.getMetaClass().equals(typeClass))
523                     throw runtime.newTypeError(value, "Invalid type to assign to submessage field.");
524                 val = ((RubyMessage) value).build(context, depth + 1);
525                 break;
526             case ENUM:
527                 Descriptors.EnumDescriptor enumDescriptor = fieldDescriptor.getEnumType();
528 
529                 if (Utils.isRubyNum(value)) {
530                     val = enumDescriptor.findValueByNumberCreatingIfUnknown(RubyNumeric.num2int(value));
531                 } else if (value instanceof RubySymbol) {
532                     val = enumDescriptor.findValueByName(value.asJavaString());
533                 } else {
534                     throw runtime.newTypeError("Expected number or symbol type for enum field.");
535                 }
536                 if (val == null) {
537                     throw runtime.newRangeError("Enum value " + value + " is not found.");
538                 }
539                 break;
540             default:
541                 break;
542         }
543         return val;
544     }
545 
wrapField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor, Object value)546     private IRubyObject wrapField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor, Object value) {
547         if (value == null) {
548             return context.runtime.getNil();
549         }
550         Ruby runtime = context.runtime;
551         switch (fieldDescriptor.getType()) {
552             case INT32:
553             case INT64:
554             case UINT32:
555             case UINT64:
556             case FLOAT:
557             case DOUBLE:
558             case BOOL:
559             case BYTES:
560             case STRING:
561                 return Utils.wrapPrimaryValue(context, fieldDescriptor.getType(), value);
562             case MESSAGE:
563                 RubyClass typeClass = (RubyClass) ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor)).msgclass(context);
564                 RubyMessage msg = (RubyMessage) typeClass.newInstance(context, Block.NULL_BLOCK);
565                 return msg.buildFrom(context, (DynamicMessage) value);
566             case ENUM:
567                 Descriptors.EnumValueDescriptor enumValueDescriptor = (Descriptors.EnumValueDescriptor) value;
568                 if (enumValueDescriptor.getIndex() == -1) { // UNKNOWN ENUM VALUE
569                     return runtime.newFixnum(enumValueDescriptor.getNumber());
570                 }
571                 return runtime.newSymbol(enumValueDescriptor.getName());
572             default:
573                 return runtime.newString(value.toString());
574         }
575     }
576 
repeatedFieldForFieldDescriptor(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor)577     private RubyRepeatedField repeatedFieldForFieldDescriptor(ThreadContext context,
578                                                               Descriptors.FieldDescriptor fieldDescriptor) {
579         IRubyObject typeClass = context.runtime.getNilClass();
580 
581         IRubyObject descriptor = getDescriptorForField(context, fieldDescriptor);
582         Descriptors.FieldDescriptor.Type type = fieldDescriptor.getType();
583         if (type == Descriptors.FieldDescriptor.Type.MESSAGE) {
584             typeClass = ((RubyDescriptor) descriptor).msgclass(context);
585 
586         } else if (type == Descriptors.FieldDescriptor.Type.ENUM) {
587             typeClass = ((RubyEnumDescriptor) descriptor).enummodule(context);
588         }
589         return new RubyRepeatedField(context.runtime, cRepeatedField, type, typeClass);
590     }
591 
getField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor)592     protected IRubyObject getField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor) {
593         Descriptors.OneofDescriptor oneofDescriptor = fieldDescriptor.getContainingOneof();
594         if (oneofDescriptor != null) {
595             if (oneofCases.containsKey(oneofDescriptor)) {
596                 if (oneofCases.get(oneofDescriptor) != fieldDescriptor)
597                     return context.runtime.getNil();
598                 return fields.get(fieldDescriptor);
599             } else {
600                 Descriptors.FieldDescriptor oneofCase = builder.getOneofFieldDescriptor(oneofDescriptor);
601                 if (oneofCase != fieldDescriptor) return context.runtime.getNil();
602                 IRubyObject value = wrapField(context, oneofCase, builder.getField(oneofCase));
603                 fields.put(fieldDescriptor, value);
604                 return value;
605             }
606         }
607 
608         if (Utils.isMapEntry(fieldDescriptor)) {
609             RubyMap map = maps.get(fieldDescriptor);
610             if (map == null) {
611                 map = newMapForField(context, fieldDescriptor);
612                 int mapSize = this.builder.getRepeatedFieldCount(fieldDescriptor);
613                 Descriptors.FieldDescriptor keyField = fieldDescriptor.getMessageType().findFieldByNumber(1);
614                 Descriptors.FieldDescriptor valueField = fieldDescriptor.getMessageType().findFieldByNumber(2);
615                 RubyDescriptor kvDescriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
616                 RubyClass kvClass = (RubyClass) kvDescriptor.msgclass(context);
617                 for (int i = 0; i < mapSize; i++) {
618                     RubyMessage kvMessage = (RubyMessage) kvClass.newInstance(context, Block.NULL_BLOCK);
619                     DynamicMessage message = (DynamicMessage) this.builder.getRepeatedField(fieldDescriptor, i);
620                     kvMessage.buildFrom(context, message);
621                     map.indexSet(context, kvMessage.getField(context, keyField), kvMessage.getField(context, valueField));
622                 }
623                 maps.put(fieldDescriptor, map);
624             }
625             return map;
626         }
627         if (fieldDescriptor.isRepeated()) {
628             return getRepeatedField(context, fieldDescriptor);
629         }
630         if (fieldDescriptor.getType() != Descriptors.FieldDescriptor.Type.MESSAGE ||
631                 this.builder.hasField(fieldDescriptor) || fields.containsKey(fieldDescriptor)) {
632             if (fields.containsKey(fieldDescriptor)) {
633                 return fields.get(fieldDescriptor);
634             } else {
635                 IRubyObject value = wrapField(context, fieldDescriptor, this.builder.getField(fieldDescriptor));
636                 if (this.builder.hasField(fieldDescriptor)) {
637                     fields.put(fieldDescriptor, value);
638                 }
639                 return value;
640             }
641         }
642         return context.runtime.getNil();
643     }
644 
setField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor, IRubyObject value)645     protected IRubyObject setField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor, IRubyObject value) {
646         if (Utils.isMapEntry(fieldDescriptor)) {
647             if (!(value instanceof RubyMap)) {
648                 throw context.runtime.newTypeError("Expected Map instance");
649             }
650             RubyMap thisMap = (RubyMap) getField(context, fieldDescriptor);
651             thisMap.mergeIntoSelf(context, value);
652         } else if (fieldDescriptor.isRepeated()) {
653             checkRepeatedFieldType(context, value, fieldDescriptor);
654             if (value instanceof RubyRepeatedField) {
655                 addRepeatedField(fieldDescriptor, (RubyRepeatedField) value);
656             } else {
657                 RubyArray ary = value.convertToArray();
658                 RubyRepeatedField repeatedField = rubyToRepeatedField(context, fieldDescriptor, ary);
659                 addRepeatedField(fieldDescriptor, repeatedField);
660             }
661         } else {
662             Descriptors.OneofDescriptor oneofDescriptor = fieldDescriptor.getContainingOneof();
663             if (oneofDescriptor != null) {
664                 Descriptors.FieldDescriptor oneofCase = oneofCases.get(oneofDescriptor);
665                 if (oneofCase != null && oneofCase != fieldDescriptor) {
666                     fields.remove(oneofCase);
667                 }
668                 if (value.isNil()) {
669                     oneofCases.remove(oneofDescriptor);
670                     fields.remove(fieldDescriptor);
671                 } else {
672                     oneofCases.put(oneofDescriptor, fieldDescriptor);
673                     fields.put(fieldDescriptor, value);
674                 }
675             } else {
676                 Descriptors.FieldDescriptor.Type fieldType = fieldDescriptor.getType();
677                 IRubyObject typeClass = context.runtime.getObject();
678                 boolean addValue = true;
679                 if (fieldType == Descriptors.FieldDescriptor.Type.MESSAGE) {
680                     typeClass = ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor)).msgclass(context);
681                     if (value.isNil()){
682                         addValue = false;
683                     }
684                 } else if (fieldType == Descriptors.FieldDescriptor.Type.ENUM) {
685                     typeClass = ((RubyEnumDescriptor) getDescriptorForField(context, fieldDescriptor)).enummodule(context);
686                     Descriptors.EnumDescriptor enumDescriptor = fieldDescriptor.getEnumType();
687                     if (Utils.isRubyNum(value)) {
688                         Descriptors.EnumValueDescriptor val =
689                                 enumDescriptor.findValueByNumberCreatingIfUnknown(RubyNumeric.num2int(value));
690                         if (val.getIndex() != -1) value = context.runtime.newSymbol(val.getName());
691                     }
692                 }
693                 if (addValue) {
694                     Utils.checkType(context, fieldType, value, (RubyModule) typeClass);
695                     this.fields.put(fieldDescriptor, value);
696                 } else {
697                     this.fields.remove(fieldDescriptor);
698                 }
699             }
700         }
701         return context.runtime.getNil();
702     }
703 
layoutInspect()704     private String layoutInspect() {
705         ThreadContext context = getRuntime().getCurrentContext();
706         StringBuilder sb = new StringBuilder();
707         for (Descriptors.FieldDescriptor fdef : descriptor.getFields()) {
708             sb.append(Utils.unescapeIdentifier(fdef.getName()));
709             sb.append(": ");
710             sb.append(getField(context, fdef).inspect());
711             sb.append(", ");
712         }
713         return sb.substring(0, sb.length() - 2);
714     }
715 
getDescriptorForField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor)716     private IRubyObject getDescriptorForField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor) {
717         RubyDescriptor thisRbDescriptor = (RubyDescriptor) getDescriptor(context, metaClass);
718         return thisRbDescriptor.lookup(fieldDescriptor.getName()).getSubType(context);
719     }
720 
rubyToRepeatedField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor, IRubyObject value)721     private RubyRepeatedField rubyToRepeatedField(ThreadContext context,
722                                                   Descriptors.FieldDescriptor fieldDescriptor, IRubyObject value) {
723         RubyArray arr = value.convertToArray();
724         RubyRepeatedField repeatedField = repeatedFieldForFieldDescriptor(context, fieldDescriptor);
725         for (int i = 0; i < arr.size(); i++) {
726             repeatedField.push(context, arr.eltInternal(i));
727         }
728         return repeatedField;
729     }
730 
newMapForField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor)731     private RubyMap newMapForField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor) {
732         RubyDescriptor mapDescriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
733         Descriptors.FieldDescriptor keyField = fieldDescriptor.getMessageType().findFieldByNumber(1);
734         Descriptors.FieldDescriptor valueField = fieldDescriptor.getMessageType().findFieldByNumber(2);
735         IRubyObject keyType = RubySymbol.newSymbol(context.runtime, keyField.getType().name());
736         IRubyObject valueType = RubySymbol.newSymbol(context.runtime, valueField.getType().name());
737         if (valueField.getType() == Descriptors.FieldDescriptor.Type.MESSAGE) {
738             RubyFieldDescriptor rubyFieldDescriptor = (RubyFieldDescriptor) mapDescriptor.lookup(context,
739                     context.runtime.newString("value"));
740             RubyDescriptor rubyDescriptor = (RubyDescriptor) rubyFieldDescriptor.getSubType(context);
741             return (RubyMap) cMap.newInstance(context, keyType, valueType,
742                     rubyDescriptor.msgclass(context), Block.NULL_BLOCK);
743         } else {
744             return (RubyMap) cMap.newInstance(context, keyType, valueType, Block.NULL_BLOCK);
745         }
746     }
747 
getOneofCase(Descriptors.OneofDescriptor oneof)748     private Descriptors.FieldDescriptor getOneofCase(Descriptors.OneofDescriptor oneof) {
749         if (oneofCases.containsKey(oneof)) {
750             return oneofCases.get(oneof);
751         }
752         return builder.getOneofFieldDescriptor(oneof);
753     }
754 
755     private Descriptors.Descriptor descriptor;
756     private DynamicMessage.Builder builder;
757     private RubyClass cRepeatedField;
758     private RubyClass cMap;
759     private Map<Descriptors.FieldDescriptor, RubyRepeatedField> repeatedFields;
760     private Map<Descriptors.FieldDescriptor, RubyMap> maps;
761     private Map<Descriptors.FieldDescriptor, IRubyObject> fields;
762     private Map<Descriptors.OneofDescriptor, Descriptors.FieldDescriptor> oneofCases;
763 
764     private static final int SINK_MAXIMUM_NESTING = 64;
765 }
766