• 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.ByteString;
36 import com.google.protobuf.CodedInputStream;
37 import com.google.protobuf.Descriptors.Descriptor;
38 import com.google.protobuf.Descriptors.EnumDescriptor;
39 import com.google.protobuf.Descriptors.EnumValueDescriptor;
40 import com.google.protobuf.Descriptors.FieldDescriptor;
41 import com.google.protobuf.Descriptors.OneofDescriptor;
42 import com.google.protobuf.DynamicMessage;
43 import com.google.protobuf.InvalidProtocolBufferException;
44 import com.google.protobuf.Message;
45 import com.google.protobuf.UnknownFieldSet;
46 import com.google.protobuf.util.JsonFormat;
47 import java.nio.ByteBuffer;
48 import java.security.MessageDigest;
49 import java.security.NoSuchAlgorithmException;
50 import java.util.HashMap;
51 import java.util.List;
52 import java.util.Map;
53 import org.jruby.*;
54 import org.jruby.anno.JRubyMethod;
55 import org.jruby.exceptions.RaiseException;
56 import org.jruby.runtime.Block;
57 import org.jruby.runtime.Helpers;
58 import org.jruby.runtime.ThreadContext;
59 import org.jruby.runtime.builtin.IRubyObject;
60 import org.jruby.util.ByteList;
61 
62 public class RubyMessage extends RubyObject {
63   private final String DEFAULT_VALUE = "google.protobuf.FieldDescriptorProto.default_value";
64   private final String TYPE = "type";
65 
RubyMessage(Ruby runtime, RubyClass klazz, Descriptor descriptor)66   public RubyMessage(Ruby runtime, RubyClass klazz, Descriptor descriptor) {
67     super(runtime, klazz);
68 
69     this.descriptor = descriptor;
70     this.cRepeatedField = (RubyClass) runtime.getClassFromPath("Google::Protobuf::RepeatedField");
71     this.cMap = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Map");
72     this.builder = DynamicMessage.newBuilder(descriptor);
73     this.fields = new HashMap<FieldDescriptor, IRubyObject>();
74     this.oneofCases = new HashMap<OneofDescriptor, FieldDescriptor>();
75   }
76 
77   /*
78    * call-seq:
79    *     Message.new(kwargs) => new_message
80    *
81    * Creates a new instance of the given message class. Keyword arguments may be
82    * provided with keywords corresponding to field names.
83    *
84    * Note that no literal Message class exists. Only concrete classes per message
85    * type exist, as provided by the #msgclass method on Descriptors after they
86    * have been added to a pool. The method definitions described here on the
87    * Message class are provided on each concrete message class.
88    */
89   @JRubyMethod(optional = 1)
initialize(final ThreadContext context, IRubyObject[] args)90   public IRubyObject initialize(final ThreadContext context, IRubyObject[] args) {
91     final Ruby runtime = context.runtime;
92     if (args.length == 1) {
93       if (!(args[0] instanceof RubyHash)) {
94         throw runtime.newArgumentError("expected Hash arguments.");
95       }
96       RubyHash hash = args[0].convertToHash();
97       hash.visitAll(
98           context,
99           new RubyHash.Visitor() {
100             @Override
101             public void visit(IRubyObject key, IRubyObject value) {
102               if (!(key instanceof RubySymbol) && !(key instanceof RubyString)) {
103                 throw Utils.createTypeError(
104                     context, "Expected string or symbols as hash keys in initialization map.");
105               }
106               final FieldDescriptor fieldDescriptor =
107                   findField(context, key, ignoreUnknownFieldsOnInit);
108 
109               if (value == null || value.isNil()) return;
110 
111               if (fieldDescriptor.isMapField()) {
112                 if (!(value instanceof RubyHash))
113                   throw runtime.newArgumentError(
114                       "Expected Hash object as initializer value for map field '"
115                           + key.asJavaString()
116                           + "' (given "
117                           + value.getMetaClass()
118                           + ").");
119 
120                 final RubyMap map = newMapForField(context, fieldDescriptor);
121                 map.mergeIntoSelf(context, value);
122                 fields.put(fieldDescriptor, map);
123               } else if (fieldDescriptor.isRepeated()) {
124                 if (!(value instanceof RubyArray))
125                   throw runtime.newArgumentError(
126                       "Expected array as initializer value for repeated field '"
127                           + key.asJavaString()
128                           + "' (given "
129                           + value.getMetaClass()
130                           + ").");
131                 fields.put(fieldDescriptor, rubyToRepeatedField(context, fieldDescriptor, value));
132               } else {
133                 OneofDescriptor oneof = fieldDescriptor.getContainingOneof();
134                 if (oneof != null) {
135                   oneofCases.put(oneof, fieldDescriptor);
136                 }
137 
138                 if (value instanceof RubyHash
139                     && fieldDescriptor.getType() == FieldDescriptor.Type.MESSAGE) {
140                   RubyDescriptor descriptor =
141                       (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
142                   RubyClass typeClass = (RubyClass) descriptor.msgclass(context);
143                   value = (IRubyObject) typeClass.newInstance(context, value, Block.NULL_BLOCK);
144                   fields.put(fieldDescriptor, value);
145                 } else {
146                   indexSet(context, key, value);
147                 }
148               }
149             }
150           },
151           null);
152     }
153     return this;
154   }
155 
156   /*
157    * call-seq:
158    *     Message.[]=(index, value)
159    *
160    * Sets a field's value by field name. The provided field name should be a
161    * string.
162    */
163   @JRubyMethod(name = "[]=")
indexSet(ThreadContext context, IRubyObject fieldName, IRubyObject value)164   public IRubyObject indexSet(ThreadContext context, IRubyObject fieldName, IRubyObject value) {
165     FieldDescriptor fieldDescriptor = findField(context, fieldName);
166     return setFieldInternal(context, fieldDescriptor, value);
167   }
168 
169   /*
170    * call-seq:
171    *     Message.[](index) => value
172    *
173    * Accesses a field's value by field name. The provided field name should be a
174    * string.
175    */
176   @JRubyMethod(name = "[]")
index(ThreadContext context, IRubyObject fieldName)177   public IRubyObject index(ThreadContext context, IRubyObject fieldName) {
178     FieldDescriptor fieldDescriptor = findField(context, fieldName);
179     return getFieldInternal(context, fieldDescriptor);
180   }
181 
182   /*
183    * call-seq:
184    *     Message.inspect => string
185    *
186    * Returns a human-readable string representing this message. It will be
187    * formatted as "<MessageType: field1: value1, field2: value2, ...>". Each
188    * field's value is represented according to its own #inspect method.
189    */
190   @JRubyMethod(name = {"inspect", "to_s"})
inspect()191   public IRubyObject inspect() {
192     ThreadContext context = getRuntime().getCurrentContext();
193     String cname = metaClass.getName();
194     String colon = ": ";
195     String comma = ", ";
196     StringBuilder sb = new StringBuilder("<");
197     boolean addComma = false;
198 
199     sb.append(cname).append(colon);
200 
201     for (FieldDescriptor fd : descriptor.getFields()) {
202       if (fd.hasPresence() && !(fields.containsKey(fd) || builder.hasField(fd))) {
203         continue;
204       }
205       if (addComma) {
206         sb.append(comma);
207       } else {
208         addComma = true;
209       }
210 
211       sb.append(fd.getName()).append(colon);
212 
213       IRubyObject value = getFieldInternal(context, fd);
214       if (value instanceof RubyBoolean) {
215         // Booleans don't implement internal "inspect" methods so have to call handle them manually
216         sb.append(value.isTrue() ? "true" : "false");
217       } else {
218         sb.append(value.inspect());
219       }
220     }
221     sb.append(">");
222 
223     return context.runtime.newString(sb.toString());
224   }
225 
226   /*
227    * call-seq:
228    *     Message.hash => hash_value
229    *
230    * Returns a hash value that represents this message's field values.
231    */
232   @JRubyMethod
hash(ThreadContext context)233   public IRubyObject hash(ThreadContext context) {
234     try {
235       MessageDigest digest = MessageDigest.getInstance("SHA-256");
236       for (FieldDescriptor fd : descriptor.getFields()) {
237         digest.update((byte) getFieldInternal(context, fd).hashCode());
238       }
239       return context.runtime.newFixnum(ByteBuffer.wrap(digest.digest()).getLong());
240     } catch (NoSuchAlgorithmException ignore) {
241       return context.runtime.newFixnum(System.identityHashCode(this));
242     }
243   }
244 
245   /*
246    * call-seq:
247    *     Message.==(other) => boolean
248    *
249    * Performs a deep comparison of this message with another. Messages are equal
250    * if they have the same type and if each field is equal according to the :==
251    * method's semantics (a more efficient comparison may actually be done if the
252    * field is of a primitive type).
253    */
254   @JRubyMethod(name = {"==", "eql?"})
eq(ThreadContext context, IRubyObject other)255   public IRubyObject eq(ThreadContext context, IRubyObject other) {
256     Ruby runtime = context.runtime;
257     if (!(other instanceof RubyMessage)) return runtime.getFalse();
258     RubyMessage message = (RubyMessage) other;
259     if (descriptor != message.descriptor) {
260       return runtime.getFalse();
261     }
262 
263     for (FieldDescriptor fdef : descriptor.getFields()) {
264       IRubyObject thisVal = getFieldInternal(context, fdef);
265       IRubyObject thatVal = message.getFieldInternal(context, fdef);
266       IRubyObject ret = thisVal.callMethod(context, "==", thatVal);
267       if (!ret.isTrue()) {
268         return runtime.getFalse();
269       }
270     }
271     return runtime.getTrue();
272   }
273 
274   /*
275    * call-seq:
276    *     Message.respond_to?(method_name, search_private_and_protected) => boolean
277    *
278    *  Parallels method_missing, returning true when this object implements a method with the given
279    *  method_name.
280    */
281   @JRubyMethod(name = "respond_to?", required = 1, optional = 1)
respondTo(ThreadContext context, IRubyObject[] args)282   public IRubyObject respondTo(ThreadContext context, IRubyObject[] args) {
283     String methodName = args[0].asJavaString();
284     if (descriptor.findFieldByName(methodName) != null) {
285       return context.runtime.getTrue();
286     }
287     RubyDescriptor rubyDescriptor = (RubyDescriptor) getDescriptor(context, metaClass);
288     IRubyObject oneofDescriptor = rubyDescriptor.lookupOneof(context, args[0]);
289     if (!oneofDescriptor.isNil()) {
290       return context.runtime.getTrue();
291     }
292     if (methodName.startsWith(CLEAR_PREFIX)) {
293       String strippedMethodName = methodName.substring(6);
294       oneofDescriptor =
295           rubyDescriptor.lookupOneof(context, context.runtime.newSymbol(strippedMethodName));
296       if (!oneofDescriptor.isNil()) {
297         return context.runtime.getTrue();
298       }
299 
300       if (descriptor.findFieldByName(strippedMethodName) != null) {
301         return context.runtime.getTrue();
302       }
303     }
304     if (methodName.startsWith(HAS_PREFIX) && methodName.endsWith(QUESTION_MARK)) {
305       String strippedMethodName = methodName.substring(4, methodName.length() - 1);
306       FieldDescriptor fieldDescriptor = descriptor.findFieldByName(strippedMethodName);
307       if (fieldDescriptor != null && fieldDescriptor.hasPresence()) {
308         return context.runtime.getTrue();
309       }
310       oneofDescriptor =
311           rubyDescriptor.lookupOneof(
312               context, RubyString.newString(context.runtime, strippedMethodName));
313       if (!oneofDescriptor.isNil()) {
314         return context.runtime.getTrue();
315       }
316     }
317     if (methodName.endsWith(AS_VALUE_SUFFIX)) {
318       FieldDescriptor fieldDescriptor =
319           descriptor.findFieldByName(methodName.substring(0, methodName.length() - 9));
320       if (fieldDescriptor != null && isWrappable(fieldDescriptor)) {
321         return context.runtime.getTrue();
322       }
323     }
324     if (methodName.endsWith(CONST_SUFFIX)) {
325       FieldDescriptor fieldDescriptor =
326           descriptor.findFieldByName(methodName.substring(0, methodName.length() - 6));
327       if (fieldDescriptor != null) {
328         if (fieldDescriptor.getType() == FieldDescriptor.Type.ENUM) {
329           return context.runtime.getTrue();
330         }
331       }
332     }
333     if (methodName.endsWith(Utils.EQUAL_SIGN)) {
334       String strippedMethodName = methodName.substring(0, methodName.length() - 1);
335       FieldDescriptor fieldDescriptor = descriptor.findFieldByName(strippedMethodName);
336       if (fieldDescriptor != null) {
337         return context.runtime.getTrue();
338       }
339       if (strippedMethodName.endsWith(AS_VALUE_SUFFIX)) {
340         strippedMethodName = methodName.substring(0, strippedMethodName.length() - 9);
341         fieldDescriptor = descriptor.findFieldByName(strippedMethodName);
342         if (fieldDescriptor != null && isWrappable(fieldDescriptor)) {
343           return context.runtime.getTrue();
344         }
345       }
346     }
347     boolean includePrivate = false;
348     if (args.length == 2) {
349       includePrivate = context.runtime.getTrue().equals(args[1]);
350     }
351     return metaClass.respondsToMethod(methodName, includePrivate)
352         ? context.runtime.getTrue()
353         : context.runtime.getFalse();
354   }
355 
356   /*
357    * call-seq:
358    *     Message.method_missing(*args)
359    *
360    * Provides accessors and setters and methods to clear and check for presence of
361    * message fields according to their field names.
362    *
363    * For any field whose name does not conflict with a built-in method, an
364    * accessor is provided with the same name as the field, and a setter is
365    * provided with the name of the field plus the '=' suffix. Thus, given a
366    * message instance 'msg' with field 'foo', the following code is valid:
367    *
368    *     msg.foo = 42
369    *     puts msg.foo
370    *
371    * This method also provides read-only accessors for oneofs. If a oneof exists
372    * with name 'my_oneof', then msg.my_oneof will return a Ruby symbol equal to
373    * the name of the field in that oneof that is currently set, or nil if none.
374    *
375    * It also provides methods of the form 'clear_fieldname' to clear the value
376    * of the field 'fieldname'. For basic data types, this will set the default
377    * value of the field.
378    *
379    * Additionally, it provides methods of the form 'has_fieldname?', which returns
380    * true if the field 'fieldname' is set in the message object, else false. For
381    * 'proto3' syntax, calling this for a basic type field will result in an error.
382    */
383   @JRubyMethod(name = "method_missing", rest = true)
methodMissing(ThreadContext context, IRubyObject[] args)384   public IRubyObject methodMissing(ThreadContext context, IRubyObject[] args) {
385     Ruby runtime = context.runtime;
386     String methodName = args[0].asJavaString();
387     RubyDescriptor rubyDescriptor = (RubyDescriptor) getDescriptor(context, metaClass);
388 
389     if (args.length == 1) {
390       // If we find a Oneof return it's name (use lookupOneof because it has an index)
391       IRubyObject oneofDescriptor = rubyDescriptor.lookupOneof(context, args[0]);
392 
393       if (!oneofDescriptor.isNil()) {
394         RubyOneofDescriptor rubyOneofDescriptor = (RubyOneofDescriptor) oneofDescriptor;
395         OneofDescriptor ood = rubyOneofDescriptor.getDescriptor();
396 
397         // Check to see if we set this through ruby
398         FieldDescriptor fieldDescriptor = oneofCases.get(ood);
399 
400         if (fieldDescriptor == null) {
401           // See if we set this from decoding a message
402           fieldDescriptor = builder.getOneofFieldDescriptor(ood);
403 
404           if (fieldDescriptor == null) {
405             return context.nil;
406           } else {
407             // Cache it so we don't need to do multiple checks next time
408             oneofCases.put(ood, fieldDescriptor);
409             return runtime.newSymbol(fieldDescriptor.getName());
410           }
411         } else {
412           return runtime.newSymbol(fieldDescriptor.getName());
413         }
414       }
415 
416       // If we find a field return its value
417       FieldDescriptor fieldDescriptor = descriptor.findFieldByName(methodName);
418 
419       if (fieldDescriptor != null) {
420         return getFieldInternal(context, fieldDescriptor);
421       }
422 
423       if (methodName.startsWith(CLEAR_PREFIX)) {
424         methodName = methodName.substring(6);
425         oneofDescriptor = rubyDescriptor.lookupOneof(context, runtime.newSymbol(methodName));
426         if (!oneofDescriptor.isNil()) {
427           fieldDescriptor = oneofCases.get(((RubyOneofDescriptor) oneofDescriptor).getDescriptor());
428           if (fieldDescriptor == null) {
429             // Clearing an already cleared oneof; return here to avoid NoMethodError.
430             return context.nil;
431           }
432         }
433 
434         if (fieldDescriptor == null) {
435           fieldDescriptor = descriptor.findFieldByName(methodName);
436         }
437 
438         if (fieldDescriptor != null) {
439           return clearFieldInternal(context, fieldDescriptor);
440         }
441 
442       } else if (methodName.startsWith(HAS_PREFIX) && methodName.endsWith(QUESTION_MARK)) {
443         methodName =
444             methodName.substring(
445                 4, methodName.length() - 1); // Trim "has_" and "?" off the field name
446         oneofDescriptor = rubyDescriptor.lookupOneof(context, runtime.newSymbol(methodName));
447         if (!oneofDescriptor.isNil()) {
448           RubyOneofDescriptor rubyOneofDescriptor = (RubyOneofDescriptor) oneofDescriptor;
449           return oneofCases.containsKey(rubyOneofDescriptor.getDescriptor())
450               ? runtime.getTrue()
451               : runtime.getFalse();
452         }
453 
454         fieldDescriptor = descriptor.findFieldByName(methodName);
455 
456         if (fieldDescriptor != null && fieldDescriptor.hasPresence()) {
457           return fields.containsKey(fieldDescriptor) ? runtime.getTrue() : runtime.getFalse();
458         }
459 
460       } else if (methodName.endsWith(AS_VALUE_SUFFIX)) {
461         methodName = methodName.substring(0, methodName.length() - 9);
462         fieldDescriptor = descriptor.findFieldByName(methodName);
463 
464         if (fieldDescriptor != null && isWrappable(fieldDescriptor)) {
465           IRubyObject value = getFieldInternal(context, fieldDescriptor);
466 
467           if (!value.isNil() && value instanceof RubyMessage) {
468             return ((RubyMessage) value).index(context, runtime.newString("value"));
469           }
470 
471           return value;
472         }
473 
474       } else if (methodName.endsWith(CONST_SUFFIX)) {
475         methodName = methodName.substring(0, methodName.length() - 6);
476         fieldDescriptor = descriptor.findFieldByName(methodName);
477         if (fieldDescriptor != null && fieldDescriptor.getType() == FieldDescriptor.Type.ENUM) {
478           IRubyObject enumValue = getFieldInternal(context, fieldDescriptor);
479 
480           if (!enumValue.isNil()) {
481             EnumDescriptor enumDescriptor = fieldDescriptor.getEnumType();
482             if (enumValue instanceof RubyRepeatedField) {
483               RubyArray values = (RubyArray) ((RubyRepeatedField) enumValue).toArray(context);
484               RubyArray retValues = runtime.newArray(values.getLength());
485               for (int i = 0; i < values.getLength(); i++) {
486                 String val = values.eltInternal(i).toString();
487                 retValues.store(
488                     (long) i, runtime.newFixnum(enumDescriptor.findValueByName(val).getNumber()));
489               }
490               return retValues;
491             }
492 
493             return runtime.newFixnum(
494                 enumDescriptor.findValueByName(enumValue.asJavaString()).getNumber());
495           }
496         }
497       }
498 
499     } else if (args.length == 2 && methodName.endsWith(Utils.EQUAL_SIGN)) {
500 
501       methodName = methodName.substring(0, methodName.length() - 1); // Trim equals sign
502       FieldDescriptor fieldDescriptor = descriptor.findFieldByName(methodName);
503       if (fieldDescriptor != null) {
504         return setFieldInternal(context, fieldDescriptor, args[1]);
505       }
506 
507       IRubyObject oneofDescriptor =
508           rubyDescriptor.lookupOneof(context, RubyString.newString(context.runtime, methodName));
509       if (!oneofDescriptor.isNil()) {
510         throw runtime.newRuntimeError("Oneof accessors are read-only.");
511       }
512 
513       if (methodName.endsWith(AS_VALUE_SUFFIX)) {
514         methodName = methodName.substring(0, methodName.length() - 9);
515 
516         fieldDescriptor = descriptor.findFieldByName(methodName);
517 
518         if (fieldDescriptor != null && isWrappable(fieldDescriptor)) {
519           if (args[1].isNil()) {
520             return setFieldInternal(context, fieldDescriptor, args[1]);
521           }
522 
523           RubyClass typeClass =
524               (RubyClass)
525                   ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor))
526                       .msgclass(context);
527           RubyMessage msg = (RubyMessage) typeClass.newInstance(context, Block.NULL_BLOCK);
528           msg.indexSet(context, runtime.newString("value"), args[1]);
529           return setFieldInternal(context, fieldDescriptor, msg);
530         }
531       }
532     }
533 
534     return Helpers.invokeSuper(context, this, metaClass, "method_missing", args, Block.NULL_BLOCK);
535   }
536 
537   /**
538    * call-seq: Message.dup => new_message Performs a shallow copy of this message and returns the
539    * new copy.
540    */
541   @JRubyMethod
dup(ThreadContext context)542   public IRubyObject dup(ThreadContext context) {
543     RubyMessage dup = (RubyMessage) metaClass.newInstance(context, Block.NULL_BLOCK);
544     for (FieldDescriptor fieldDescriptor : this.descriptor.getFields()) {
545       if (fieldDescriptor.isRepeated()) {
546         dup.fields.put(fieldDescriptor, this.getRepeatedField(context, fieldDescriptor));
547       } else if (fields.containsKey(fieldDescriptor)) {
548         dup.setFieldInternal(context, fieldDescriptor, fields.get(fieldDescriptor));
549       } else if (this.builder.hasField(fieldDescriptor)) {
550         dup.setFieldInternal(
551             context,
552             fieldDescriptor,
553             wrapField(context, fieldDescriptor, this.builder.getField(fieldDescriptor)));
554       }
555     }
556     return dup;
557   }
558 
559   /*
560    * call-seq:
561    *     Message.descriptor => descriptor
562    *
563    * Class method that returns the Descriptor instance corresponding to this
564    * message class's type.
565    */
566   @JRubyMethod(name = "descriptor", meta = true)
getDescriptor(ThreadContext context, IRubyObject recv)567   public static IRubyObject getDescriptor(ThreadContext context, IRubyObject recv) {
568     return ((RubyClass) recv).getInstanceVariable(Utils.DESCRIPTOR_INSTANCE_VAR);
569   }
570 
571   /*
572    * call-seq:
573    *     MessageClass.encode(msg, options = {}) => bytes
574    *
575    * Encodes the given message object to its serialized form in protocol buffers
576    * wire format.
577    * @param options [Hash] options for the encoder
578    *  recursion_limit: set to maximum encoding depth for message (default is 64)
579    */
580   @JRubyMethod(required = 1, optional = 1, meta = true)
encode(ThreadContext context, IRubyObject recv, IRubyObject[] args)581   public static IRubyObject encode(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
582     if (recv != args[0].getMetaClass()) {
583       throw context.runtime.newArgumentError(
584           "Tried to encode a " + args[0].getMetaClass() + " message with " + recv);
585     }
586     RubyMessage message = (RubyMessage) args[0];
587     int recursionLimitInt = SINK_MAXIMUM_NESTING;
588 
589     if (args.length > 1) {
590       RubyHash options = (RubyHash) args[1];
591       IRubyObject recursionLimit = options.fastARef(context.runtime.newSymbol("recursion_limit"));
592 
593       if (recursionLimit != null) {
594         recursionLimitInt = ((RubyNumeric) recursionLimit).getIntValue();
595       }
596     }
597     return context.runtime.newString(
598         new ByteList(message.build(context, 0, recursionLimitInt).toByteArray()));
599   }
600 
601   /*
602    * call-seq:
603    *     MessageClass.decode(data, options = {}) => message
604    *
605    * Decodes the given data (as a string containing bytes in protocol buffers wire
606    * format) under the interpretation given by this message class's definition
607    * and returns a message object with the corresponding field values.
608    * @param options [Hash] options for the decoder
609    *  recursion_limit: set to maximum decoding depth for message (default is 100)
610    */
611   @JRubyMethod(required = 1, optional = 1, meta = true)
decode(ThreadContext context, IRubyObject recv, IRubyObject[] args)612   public static IRubyObject decode(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
613     IRubyObject data = args[0];
614     byte[] bin = data.convertToString().getBytes();
615     CodedInputStream input = CodedInputStream.newInstance(bin);
616     RubyMessage ret = (RubyMessage) ((RubyClass) recv).newInstance(context, Block.NULL_BLOCK);
617 
618     if (args.length == 2) {
619       if (!(args[1] instanceof RubyHash)) {
620         throw context.runtime.newArgumentError("Expected hash arguments.");
621       }
622 
623       IRubyObject recursionLimit =
624           ((RubyHash) args[1]).fastARef(context.runtime.newSymbol("recursion_limit"));
625       if (recursionLimit != null) {
626         input.setRecursionLimit(((RubyNumeric) recursionLimit).getIntValue());
627       }
628     }
629     return decodeBytes(context, ret, input, /*freeze*/ false);
630   }
631 
decodeBytes( ThreadContext context, RubyMessage ret, CodedInputStream input, boolean freeze)632   public static IRubyObject decodeBytes(
633       ThreadContext context, RubyMessage ret, CodedInputStream input, boolean freeze) {
634     try {
635       ret.builder.mergeFrom(input, RubyDescriptorPool.registry);
636     } catch (Exception e) {
637       throw RaiseException.from(
638           context.runtime,
639           (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::ParseError"),
640           e.getMessage());
641     }
642 
643     ret.builder
644         .getUnknownFields()
645         .asMap()
646         .forEach(
647             (i, values) -> {
648               FieldDescriptor fd = ret.builder.getDescriptorForType().findFieldByNumber(i);
649               if (fd != null && fd.isRepeated() && fd.getType() == FieldDescriptor.Type.ENUM) {
650                 // Need to reset unknown values in repeated enum fields
651                 if (fd.legacyEnumFieldTreatedAsClosed()) {
652                   EnumDescriptor ed = fd.getEnumType();
653                   values
654                       .getVarintList()
655                       .forEach(
656                           value -> {
657                             ret.builder.addRepeatedField(
658                                 fd, ed.findValueByNumberCreatingIfUnknown(value.intValue()));
659                           });
660                 }
661               }
662             });
663     if (freeze) {
664       ret.freeze(context);
665     }
666     return ret;
667   }
668 
669   /*
670    * call-seq:
671    *     MessageClass.encode_json(msg, options = {}) => json_string
672    *
673    * Encodes the given message object into its serialized JSON representation.
674    * @param options [Hash] options for the decoder
675    *  preserve_proto_fieldnames: set true to use original fieldnames (default is to camelCase)
676    *  emit_defaults: set true to emit 0/false values (default is to omit them)
677    *  format_enums_as_integers: set true to emit enum values as integer (default is string)
678    */
679   @JRubyMethod(name = "encode_json", required = 1, optional = 1, meta = true)
encodeJson( ThreadContext context, IRubyObject recv, IRubyObject[] args)680   public static IRubyObject encodeJson(
681       ThreadContext context, IRubyObject recv, IRubyObject[] args) {
682     Ruby runtime = context.runtime;
683     RubyMessage message = (RubyMessage) args[0];
684     JsonFormat.Printer printer = JsonFormat.printer().omittingInsignificantWhitespace();
685     String result;
686 
687     if (args.length > 1) {
688       RubyHash options;
689       if (args[1] instanceof RubyHash) {
690         options = (RubyHash) args[1];
691       } else if (args[1].respondsTo("to_h")) {
692         options = (RubyHash) args[1].callMethod(context, "to_h");
693       } else {
694         throw runtime.newArgumentError("Expected hash arguments.");
695       }
696 
697       IRubyObject emitDefaults = options.fastARef(runtime.newSymbol("emit_defaults"));
698       IRubyObject preserveNames = options.fastARef(runtime.newSymbol("preserve_proto_fieldnames"));
699       IRubyObject printingEnumsAsInts =
700           options.fastARef(runtime.newSymbol("format_enums_as_integers"));
701 
702       if (emitDefaults != null && emitDefaults.isTrue()) {
703         printer = printer.alwaysPrintFieldsWithNoPresence();
704       }
705 
706       if (preserveNames != null && preserveNames.isTrue()) {
707         printer = printer.preservingProtoFieldNames();
708       }
709 
710       if (printingEnumsAsInts != null && printingEnumsAsInts.isTrue()) {
711         printer = printer.printingEnumsAsInts();
712       }
713     }
714     printer =
715         printer.usingTypeRegistry(
716             JsonFormat.TypeRegistry.newBuilder().add(message.descriptor).build());
717 
718     try {
719       result = printer.print(message.build(context, 0, SINK_MAXIMUM_NESTING));
720     } catch (InvalidProtocolBufferException e) {
721       throw runtime.newRuntimeError(e.getMessage());
722     } catch (IllegalArgumentException e) {
723       throw createParseError(context, e.getMessage());
724     }
725 
726     return runtime.newString(result);
727   }
728 
729   /*
730    * call-seq:
731    *     MessageClass.decode_json(data, options = {}) => message
732    *
733    * Decodes the given data (as a string containing bytes in protocol buffers wire
734    * format) under the interpretation given by this message class's definition
735    * and returns a message object with the corresponding field values.
736    *
737    *  @param options [Hash] options for the decoder
738    *   ignore_unknown_fields: set true to ignore unknown fields (default is to
739    *   raise an error)
740    */
741   @JRubyMethod(name = "decode_json", required = 1, optional = 1, meta = true)
decodeJson( ThreadContext context, IRubyObject recv, IRubyObject[] args)742   public static IRubyObject decodeJson(
743       ThreadContext context, IRubyObject recv, IRubyObject[] args) {
744     Ruby runtime = context.runtime;
745     boolean ignoreUnknownFields = false;
746     IRubyObject data = args[0];
747     JsonFormat.Parser parser = JsonFormat.parser();
748 
749     if (args.length == 2) {
750       if (!(args[1] instanceof RubyHash)) {
751         throw runtime.newArgumentError("Expected hash arguments.");
752       }
753 
754       IRubyObject ignoreSetting =
755           ((RubyHash) args[1]).fastARef(runtime.newSymbol("ignore_unknown_fields"));
756       if (ignoreSetting != null && ignoreSetting.isTrue()) {
757         parser = parser.ignoringUnknownFields();
758       }
759     }
760 
761     if (!(data instanceof RubyString)) {
762       throw runtime.newArgumentError("Expected string for JSON data.");
763     }
764 
765     RubyMessage ret = (RubyMessage) ((RubyClass) recv).newInstance(context, Block.NULL_BLOCK);
766     parser =
767         parser.usingTypeRegistry(JsonFormat.TypeRegistry.newBuilder().add(ret.descriptor).build());
768 
769     try {
770       parser.merge(data.asJavaString(), ret.builder);
771     } catch (InvalidProtocolBufferException e) {
772       throw createParseError(context, e.getMessage().replace("Cannot find", "No such"));
773     }
774 
775     if (isWrapper(ret.descriptor)) {
776       throw runtime.newRuntimeError(
777           "Parsing a wrapper type from JSON at the top level does not work.");
778     }
779 
780     return ret;
781   }
782 
783   @JRubyMethod(name = "to_h")
toHash(ThreadContext context)784   public IRubyObject toHash(ThreadContext context) {
785     Ruby runtime = context.runtime;
786     RubyHash ret = RubyHash.newHash(runtime);
787     build(context, 0, SINK_MAXIMUM_NESTING); // Sync Ruby data to the Builder object.
788     for (Map.Entry<FieldDescriptor, Object> field : builder.getAllFields().entrySet()) {
789       FieldDescriptor fdef = field.getKey();
790       IRubyObject value = getFieldInternal(context, fdef, !fdef.hasPresence());
791 
792       if (fdef.isRepeated() && !fdef.isMapField()) {
793         if (fdef.getType() != FieldDescriptor.Type.MESSAGE) {
794           value = Helpers.invoke(context, value, "to_a");
795         } else {
796           RubyArray ary = value.convertToArray();
797           for (int i = 0; i < ary.size(); i++) {
798             IRubyObject submsg = Helpers.invoke(context, ary.eltInternal(i), "to_h");
799             ary.eltInternalSet(i, submsg);
800           }
801 
802           value = ary.to_ary();
803         }
804       } else if (value.respondsTo("to_h")) {
805         value = Helpers.invoke(context, value, "to_h");
806       } else if (value.respondsTo("to_a")) {
807         value = Helpers.invoke(context, value, "to_a");
808       }
809       ret.fastASet(runtime.newSymbol(fdef.getName()), value);
810     }
811     return ret;
812   }
813 
814   @JRubyMethod
freeze(ThreadContext context)815   public IRubyObject freeze(ThreadContext context) {
816     if (isFrozen()) {
817       return this;
818     }
819     setFrozen(true);
820     for (FieldDescriptor fdef : descriptor.getFields()) {
821       if (fdef.isMapField()) {
822         ((RubyMap) fields.get(fdef)).freeze(context);
823       } else if (fdef.isRepeated()) {
824         this.getRepeatedField(context, fdef).freeze(context);
825       } else if (fields.containsKey(fdef)) {
826         if (fdef.getType() == FieldDescriptor.Type.MESSAGE) {
827           ((RubyMessage) fields.get(fdef)).freeze(context);
828         }
829       }
830     }
831     return this;
832   }
833 
build(ThreadContext context, int depth, int recursionLimit)834   protected DynamicMessage build(ThreadContext context, int depth, int recursionLimit) {
835     if (depth >= recursionLimit) {
836       throw context.runtime.newRuntimeError("Recursion limit exceeded during encoding.");
837     }
838 
839     RubySymbol typeBytesSymbol = RubySymbol.newSymbol(context.runtime, "TYPE_BYTES");
840 
841     // Handle the typical case where the fields.keySet contain the fieldDescriptors
842     for (FieldDescriptor fieldDescriptor : fields.keySet()) {
843       IRubyObject value = fields.get(fieldDescriptor);
844 
845       if (value instanceof RubyMap) {
846         builder.clearField(fieldDescriptor);
847         RubyDescriptor mapDescriptor =
848             (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
849         for (DynamicMessage kv :
850             ((RubyMap) value).build(context, mapDescriptor, depth, recursionLimit)) {
851           builder.addRepeatedField(fieldDescriptor, kv);
852         }
853 
854       } else if (value instanceof RubyRepeatedField) {
855         RubyRepeatedField repeatedField = (RubyRepeatedField) value;
856 
857         builder.clearField(fieldDescriptor);
858         for (int i = 0; i < repeatedField.size(); i++) {
859           Object item =
860               convert(
861                   context,
862                   fieldDescriptor,
863                   repeatedField.get(i),
864                   depth,
865                   recursionLimit,
866                   /*isDefaultValueForBytes*/ false);
867           builder.addRepeatedField(fieldDescriptor, item);
868         }
869 
870       } else if (!value.isNil()) {
871         /**
872          * Detect the special case where default_value strings are provided for byte fields. If so,
873          * disable normal string encoding behavior within convert. For a more detailed explanation
874          * of other possible workarounds, see the comments above {@code
875          * com.google.protobuf.Internal#stringDefaultValue() stringDefaultValue}.
876          */
877         boolean isDefaultStringForBytes = false;
878         if (DEFAULT_VALUE.equals(fieldDescriptor.getFullName())) {
879           FieldDescriptor enumFieldDescriptorForType =
880               this.builder.getDescriptorForType().findFieldByName(TYPE);
881           if (typeBytesSymbol.equals(fields.get(enumFieldDescriptorForType))) {
882             isDefaultStringForBytes = true;
883           }
884         }
885         builder.setField(
886             fieldDescriptor,
887             convert(
888                 context, fieldDescriptor, value, depth, recursionLimit, isDefaultStringForBytes));
889       }
890     }
891 
892     // Handle cases where {@code fields} doesn't contain the value until after getFieldInternal
893     // is called - typical of a deserialized message. Skip non-maps and descriptors that already
894     // have an entry in {@code fields}.
895     for (FieldDescriptor fieldDescriptor : descriptor.getFields()) {
896       if (!fieldDescriptor.isMapField()) {
897         continue;
898       }
899       IRubyObject value = fields.get(fieldDescriptor);
900       if (value != null) {
901         continue;
902       }
903       value = getFieldInternal(context, fieldDescriptor);
904       if (value instanceof RubyMap) {
905         builder.clearField(fieldDescriptor);
906         RubyDescriptor mapDescriptor =
907             (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
908         for (DynamicMessage kv :
909             ((RubyMap) value).build(context, mapDescriptor, depth, recursionLimit)) {
910           builder.addRepeatedField(fieldDescriptor, kv);
911         }
912       }
913     }
914     return builder.build();
915   }
916 
917   // Internal use only, called by Google::Protobuf.deep_copy
deepCopy(ThreadContext context)918   protected IRubyObject deepCopy(ThreadContext context) {
919     RubyMessage copy = (RubyMessage) metaClass.newInstance(context, Block.NULL_BLOCK);
920     for (FieldDescriptor fdef : descriptor.getFields()) {
921       if (fdef.isRepeated()) {
922         copy.fields.put(fdef, this.getRepeatedField(context, fdef).deepCopy(context));
923       } else if (fields.containsKey(fdef)) {
924         if (fdef.getType() == FieldDescriptor.Type.MESSAGE) {
925           copy.setFieldInternal(context, fdef, ((RubyMessage) fields.get(fdef)).deepCopy(context));
926         } else {
927           copy.setFieldInternal(context, fdef, fields.get(fdef));
928         }
929       } else if (builder.hasField(fdef)) {
930         copy.fields.put(fdef, wrapField(context, fdef, builder.getField(fdef)));
931       }
932     }
933     return copy;
934   }
935 
clearField(ThreadContext context, FieldDescriptor fieldDescriptor)936   protected IRubyObject clearField(ThreadContext context, FieldDescriptor fieldDescriptor) {
937     validateMessageType(context, fieldDescriptor, "clear");
938     return clearFieldInternal(context, fieldDescriptor);
939   }
940 
discardUnknownFields(ThreadContext context)941   protected void discardUnknownFields(ThreadContext context) {
942     discardUnknownFields(context, builder);
943   }
944 
getField(ThreadContext context, FieldDescriptor fieldDescriptor)945   protected IRubyObject getField(ThreadContext context, FieldDescriptor fieldDescriptor) {
946     validateMessageType(context, fieldDescriptor, "get");
947     return getFieldInternal(context, fieldDescriptor);
948   }
949 
hasField(ThreadContext context, FieldDescriptor fieldDescriptor)950   protected IRubyObject hasField(ThreadContext context, FieldDescriptor fieldDescriptor) {
951     validateMessageType(context, fieldDescriptor, "has?");
952     if (!fieldDescriptor.hasPresence()) {
953       throw context.runtime.newArgumentError("does not track presence");
954     }
955     return fields.containsKey(fieldDescriptor)
956         ? context.runtime.getTrue()
957         : context.runtime.getFalse();
958   }
959 
setField( ThreadContext context, FieldDescriptor fieldDescriptor, IRubyObject value)960   protected IRubyObject setField(
961       ThreadContext context, FieldDescriptor fieldDescriptor, IRubyObject value) {
962     validateMessageType(context, fieldDescriptor, "set");
963     return setFieldInternal(context, fieldDescriptor, value);
964   }
965 
setField( ThreadContext context, RubyFieldDescriptor fieldDescriptor, IRubyObject value)966   protected IRubyObject setField(
967       ThreadContext context, RubyFieldDescriptor fieldDescriptor, IRubyObject value) {
968     validateMessageType(context, fieldDescriptor.getDescriptor(), "set");
969     return setFieldInternal(context, fieldDescriptor.getDescriptor(), fieldDescriptor, value);
970   }
971 
getRepeatedField( ThreadContext context, FieldDescriptor fieldDescriptor)972   private RubyRepeatedField getRepeatedField(
973       ThreadContext context, FieldDescriptor fieldDescriptor) {
974     if (fields.containsKey(fieldDescriptor)) {
975       return (RubyRepeatedField) fields.get(fieldDescriptor);
976     }
977     int count = this.builder.getRepeatedFieldCount(fieldDescriptor);
978     RubyRepeatedField ret = repeatedFieldForFieldDescriptor(context, fieldDescriptor);
979     for (int i = 0; i < count; i++) {
980       ret.push(
981           context,
982           new IRubyObject[] {
983             wrapField(context, fieldDescriptor, this.builder.getRepeatedField(fieldDescriptor, i))
984           });
985     }
986     fields.put(fieldDescriptor, ret);
987     return ret;
988   }
989 
buildFrom(ThreadContext context, DynamicMessage dynamicMessage)990   private IRubyObject buildFrom(ThreadContext context, DynamicMessage dynamicMessage) {
991     this.builder.mergeFrom(dynamicMessage);
992     return this;
993   }
994 
clearFieldInternal(ThreadContext context, FieldDescriptor fieldDescriptor)995   private IRubyObject clearFieldInternal(ThreadContext context, FieldDescriptor fieldDescriptor) {
996     OneofDescriptor ood = fieldDescriptor.getContainingOneof();
997     if (ood != null) oneofCases.remove(ood);
998     fields.remove(fieldDescriptor);
999     builder.clearField(fieldDescriptor);
1000     return context.nil;
1001   }
1002 
discardUnknownFields(ThreadContext context, Message.Builder messageBuilder)1003   private void discardUnknownFields(ThreadContext context, Message.Builder messageBuilder) {
1004     messageBuilder.setUnknownFields(UnknownFieldSet.getDefaultInstance());
1005     messageBuilder
1006         .getAllFields()
1007         .forEach(
1008             (fd, value) -> {
1009               if (fd.getType() == FieldDescriptor.Type.MESSAGE) {
1010                 if (fd.isRepeated()) {
1011                   messageBuilder.clearField(fd);
1012                   ((List) value)
1013                       .forEach(
1014                           (val) -> {
1015                             Message.Builder submessageBuilder = ((DynamicMessage) val).toBuilder();
1016                             discardUnknownFields(context, submessageBuilder);
1017                             messageBuilder.addRepeatedField(fd, submessageBuilder.build());
1018                           });
1019                 } else {
1020                   Message.Builder submessageBuilder = ((DynamicMessage) value).toBuilder();
1021                   discardUnknownFields(context, submessageBuilder);
1022                   messageBuilder.setField(fd, submessageBuilder.build());
1023                 }
1024               }
1025             });
1026   }
1027 
findField(ThreadContext context, IRubyObject fieldName)1028   private FieldDescriptor findField(ThreadContext context, IRubyObject fieldName) {
1029     return findField(context, fieldName, false);
1030   }
1031 
findField( ThreadContext context, IRubyObject fieldName, boolean ignoreUnknownField)1032   private FieldDescriptor findField(
1033       ThreadContext context, IRubyObject fieldName, boolean ignoreUnknownField) {
1034     String nameStr = fieldName.asJavaString();
1035     FieldDescriptor ret = this.descriptor.findFieldByName(nameStr);
1036     if (ret == null && !ignoreUnknownField) {
1037       throw context.runtime.newArgumentError("field " + fieldName.asJavaString() + " is not found");
1038     }
1039     return ret;
1040   }
1041 
1042   // convert a ruby object to protobuf type, skip type check since it is checked on the way in
convert( ThreadContext context, FieldDescriptor fieldDescriptor, IRubyObject value, int depth, int recursionLimit, boolean isDefaultStringForBytes)1043   private Object convert(
1044       ThreadContext context,
1045       FieldDescriptor fieldDescriptor,
1046       IRubyObject value,
1047       int depth,
1048       int recursionLimit,
1049       boolean isDefaultStringForBytes) {
1050     Object val = null;
1051     switch (fieldDescriptor.getType()) {
1052       case INT32:
1053       case SFIXED32:
1054       case SINT32:
1055         val = RubyNumeric.num2int(value);
1056         break;
1057       case INT64:
1058       case SFIXED64:
1059       case SINT64:
1060         val = RubyNumeric.num2long(value);
1061         break;
1062       case FIXED32:
1063       case UINT32:
1064         val = Utils.num2uint(value);
1065         break;
1066       case FIXED64:
1067       case UINT64:
1068         val = Utils.num2ulong(context.runtime, value);
1069         break;
1070       case FLOAT:
1071         val = (float) RubyNumeric.num2dbl(value);
1072         break;
1073       case DOUBLE:
1074         val = (double) RubyNumeric.num2dbl(value);
1075         break;
1076       case BOOL:
1077         val = value.isTrue();
1078         break;
1079       case BYTES:
1080         val = ByteString.copyFrom(((RubyString) value).getBytes());
1081         break;
1082       case STRING:
1083         if (isDefaultStringForBytes) {
1084           val = ((RubyString) value).getByteList().toString();
1085         } else {
1086           val = value.asJavaString();
1087         }
1088         break;
1089       case MESSAGE:
1090         val = ((RubyMessage) value).build(context, depth + 1, recursionLimit);
1091         break;
1092       case ENUM:
1093         EnumDescriptor enumDescriptor = fieldDescriptor.getEnumType();
1094         if (Utils.isRubyNum(value)) {
1095           val = enumDescriptor.findValueByNumberCreatingIfUnknown(RubyNumeric.num2int(value));
1096         } else {
1097           val = enumDescriptor.findValueByName(value.asJavaString());
1098         }
1099         break;
1100       default:
1101         break;
1102     }
1103 
1104     return val;
1105   }
1106 
createParseError(ThreadContext context, String message)1107   private static RaiseException createParseError(ThreadContext context, String message) {
1108     if (parseErrorClass == null) {
1109       parseErrorClass =
1110           (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::ParseError");
1111     }
1112     return RaiseException.from(context.runtime, parseErrorClass, message);
1113   }
1114 
wrapField( ThreadContext context, FieldDescriptor fieldDescriptor, Object value)1115   private IRubyObject wrapField(
1116       ThreadContext context, FieldDescriptor fieldDescriptor, Object value) {
1117     return wrapField(context, fieldDescriptor, value, false);
1118   }
1119 
wrapField( ThreadContext context, FieldDescriptor fieldDescriptor, Object value, boolean encodeBytes)1120   private IRubyObject wrapField(
1121       ThreadContext context, FieldDescriptor fieldDescriptor, Object value, boolean encodeBytes) {
1122     if (value == null) {
1123       return context.runtime.getNil();
1124     }
1125     Ruby runtime = context.runtime;
1126 
1127     switch (fieldDescriptor.getType()) {
1128       case INT32:
1129       case INT64:
1130       case FIXED32:
1131       case SINT32:
1132       case FIXED64:
1133       case SINT64:
1134       case SFIXED64:
1135       case SFIXED32:
1136       case UINT32:
1137       case UINT64:
1138       case FLOAT:
1139       case DOUBLE:
1140       case BOOL:
1141       case BYTES:
1142       case STRING:
1143         return Utils.wrapPrimaryValue(context, fieldDescriptor.getType(), value, encodeBytes);
1144       case MESSAGE:
1145         RubyClass typeClass =
1146             (RubyClass)
1147                 ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor))
1148                     .msgclass(context);
1149         RubyMessage msg = (RubyMessage) typeClass.newInstance(context, Block.NULL_BLOCK);
1150         return msg.buildFrom(context, (DynamicMessage) value);
1151       case ENUM:
1152         EnumValueDescriptor enumValueDescriptor = (EnumValueDescriptor) value;
1153         if (enumValueDescriptor.getIndex() == -1) { // UNKNOWN ENUM VALUE
1154           return runtime.newFixnum(enumValueDescriptor.getNumber());
1155         }
1156         return runtime.newSymbol(enumValueDescriptor.getName());
1157       default:
1158         return runtime.newString(value.toString());
1159     }
1160   }
1161 
repeatedFieldForFieldDescriptor( ThreadContext context, FieldDescriptor fieldDescriptor)1162   private RubyRepeatedField repeatedFieldForFieldDescriptor(
1163       ThreadContext context, FieldDescriptor fieldDescriptor) {
1164     IRubyObject typeClass = context.runtime.getNilClass();
1165     IRubyObject descriptor = getDescriptorForField(context, fieldDescriptor);
1166     FieldDescriptor.Type type = fieldDescriptor.getType();
1167 
1168     if (type == FieldDescriptor.Type.MESSAGE) {
1169       typeClass = ((RubyDescriptor) descriptor).msgclass(context);
1170 
1171     } else if (type == FieldDescriptor.Type.ENUM) {
1172       typeClass = ((RubyEnumDescriptor) descriptor).enummodule(context);
1173     }
1174 
1175     RubyRepeatedField field =
1176         new RubyRepeatedField(context.runtime, cRepeatedField, type, typeClass);
1177     field.setName(fieldDescriptor.getName());
1178 
1179     return field;
1180   }
1181 
getFieldInternal(ThreadContext context, FieldDescriptor fieldDescriptor)1182   private IRubyObject getFieldInternal(ThreadContext context, FieldDescriptor fieldDescriptor) {
1183     return getFieldInternal(context, fieldDescriptor, true);
1184   }
1185 
getFieldInternal( ThreadContext context, FieldDescriptor fieldDescriptor, boolean returnDefaults)1186   private IRubyObject getFieldInternal(
1187       ThreadContext context, FieldDescriptor fieldDescriptor, boolean returnDefaults) {
1188     OneofDescriptor oneofDescriptor = fieldDescriptor.getContainingOneof();
1189     if (oneofDescriptor != null) {
1190       if (oneofCases.get(oneofDescriptor) == fieldDescriptor) {
1191         IRubyObject value = fields.get(fieldDescriptor);
1192         if (value == null) {
1193           FieldDescriptor oneofCase = builder.getOneofFieldDescriptor(oneofDescriptor);
1194           if (oneofCase != null) {
1195             Object builderValue = builder.getField(oneofCase);
1196             if (builderValue != null) {
1197               boolean encodeBytes =
1198                   oneofCase.hasDefaultValue() && builderValue.equals(oneofCase.getDefaultValue());
1199               value = wrapField(context, oneofCase, builderValue, encodeBytes);
1200             }
1201           }
1202           if (value == null) {
1203             return context.nil;
1204           } else {
1205             return value;
1206           }
1207         } else {
1208           return value;
1209         }
1210       } else {
1211         FieldDescriptor oneofCase = builder.getOneofFieldDescriptor(oneofDescriptor);
1212         if (oneofCase != fieldDescriptor) {
1213           if (fieldDescriptor.getType() == FieldDescriptor.Type.MESSAGE || !returnDefaults) {
1214             return context.nil;
1215           } else {
1216             return wrapField(context, fieldDescriptor, fieldDescriptor.getDefaultValue(), true);
1217           }
1218         }
1219         if (returnDefaults || builder.hasField(fieldDescriptor)) {
1220           Object rawValue = builder.getField(oneofCase);
1221           boolean encodeBytes =
1222               oneofCase.hasDefaultValue() && rawValue.equals(oneofCase.getDefaultValue());
1223           IRubyObject value = wrapField(context, oneofCase, rawValue, encodeBytes);
1224           fields.put(fieldDescriptor, value);
1225           return value;
1226         } else {
1227           return context.nil;
1228         }
1229       }
1230     }
1231 
1232     if (fieldDescriptor.isMapField()) {
1233       RubyMap map = (RubyMap) fields.get(fieldDescriptor);
1234       if (map == null) {
1235         map = newMapForField(context, fieldDescriptor);
1236         int mapSize = this.builder.getRepeatedFieldCount(fieldDescriptor);
1237         FieldDescriptor keyField = fieldDescriptor.getMessageType().findFieldByNumber(1);
1238         FieldDescriptor valueField = fieldDescriptor.getMessageType().findFieldByNumber(2);
1239         RubyDescriptor kvDescriptor =
1240             (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
1241         RubyClass kvClass = (RubyClass) kvDescriptor.msgclass(context);
1242         for (int i = 0; i < mapSize; i++) {
1243           RubyMessage kvMessage = (RubyMessage) kvClass.newInstance(context, Block.NULL_BLOCK);
1244           DynamicMessage message =
1245               (DynamicMessage) this.builder.getRepeatedField(fieldDescriptor, i);
1246           kvMessage.buildFrom(context, message);
1247           map.indexSet(
1248               context,
1249               kvMessage.getField(context, keyField),
1250               kvMessage.getField(context, valueField));
1251         }
1252         fields.put(fieldDescriptor, map);
1253       }
1254       return map;
1255     }
1256 
1257     if (fieldDescriptor.isRepeated()) {
1258       return getRepeatedField(context, fieldDescriptor);
1259     }
1260 
1261     if (fieldDescriptor.getType() != FieldDescriptor.Type.MESSAGE
1262         || builder.hasField(fieldDescriptor)
1263         || fields.containsKey(fieldDescriptor)) {
1264       if (fields.containsKey(fieldDescriptor)) {
1265         return fields.get(fieldDescriptor);
1266       } else if (returnDefaults || builder.hasField(fieldDescriptor)) {
1267         Object rawValue = builder.getField(fieldDescriptor);
1268         boolean encodeBytes =
1269             fieldDescriptor.hasDefaultValue() && rawValue.equals(fieldDescriptor.getDefaultValue());
1270         IRubyObject value = wrapField(context, fieldDescriptor, rawValue, encodeBytes);
1271         if (builder.hasField(fieldDescriptor)) {
1272           fields.put(fieldDescriptor, value);
1273         }
1274         return value;
1275       }
1276     }
1277     return context.nil;
1278   }
1279 
setFieldInternal( ThreadContext context, FieldDescriptor fieldDescriptor, IRubyObject value)1280   private IRubyObject setFieldInternal(
1281       ThreadContext context, FieldDescriptor fieldDescriptor, IRubyObject value) {
1282     return setFieldInternal(context, fieldDescriptor, null, value);
1283   }
1284 
setFieldInternal( ThreadContext context, FieldDescriptor fieldDescriptor, RubyFieldDescriptor rubyFieldDescriptor, IRubyObject value)1285   private IRubyObject setFieldInternal(
1286       ThreadContext context,
1287       FieldDescriptor fieldDescriptor,
1288       RubyFieldDescriptor rubyFieldDescriptor,
1289       IRubyObject value) {
1290     testFrozen("can't modify frozen " + getMetaClass());
1291 
1292     if (fieldDescriptor.isMapField()) {
1293       if (!(value instanceof RubyMap)) {
1294         throw Utils.createTypeError(context, "Expected Map instance");
1295       }
1296       RubyMap thisMap = (RubyMap) getFieldInternal(context, fieldDescriptor);
1297       thisMap.mergeIntoSelf(context, value);
1298 
1299     } else if (fieldDescriptor.isRepeated()) {
1300       if (value instanceof RubyRepeatedField) {
1301         fields.put(fieldDescriptor, value);
1302       } else {
1303         throw Utils.createTypeError(context, "Expected repeated field array");
1304       }
1305 
1306     } else {
1307       boolean addValue = true;
1308       FieldDescriptor.Type fieldType = fieldDescriptor.getType();
1309       OneofDescriptor oneofDescriptor = fieldDescriptor.getContainingOneof();
1310 
1311       // Determine the typeclass, if any
1312       IRubyObject typeClass = context.runtime.getObject();
1313       if (fieldType == FieldDescriptor.Type.MESSAGE) {
1314         if (rubyFieldDescriptor != null) {
1315           typeClass = ((RubyDescriptor) rubyFieldDescriptor.getSubtype(context)).msgclass(context);
1316         } else {
1317           typeClass =
1318               ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor)).msgclass(context);
1319         }
1320         if (value.isNil()) {
1321           addValue = false;
1322         }
1323       } else if (fieldType == FieldDescriptor.Type.ENUM) {
1324         typeClass =
1325             ((RubyEnumDescriptor) getDescriptorForField(context, fieldDescriptor))
1326                 .enummodule(context);
1327         value = enumToSymbol(context, fieldDescriptor.getEnumType(), value);
1328       }
1329 
1330       if (oneofDescriptor != null) {
1331         FieldDescriptor oneofCase = oneofCases.get(oneofDescriptor);
1332 
1333         // Remove the existing field if we are setting a different field in the Oneof
1334         if (oneofCase != null && oneofCase != fieldDescriptor) {
1335           fields.remove(oneofCase);
1336         }
1337 
1338         // Keep track of what Oneofs are set
1339         if (value.isNil()) {
1340           oneofCases.remove(oneofDescriptor);
1341           if (fieldDescriptor.getRealContainingOneof() != null) {
1342             addValue = false;
1343           }
1344         } else {
1345           oneofCases.put(oneofDescriptor, fieldDescriptor);
1346         }
1347       }
1348 
1349       if (addValue) {
1350         value =
1351             Utils.checkType(
1352                 context, fieldType, fieldDescriptor.getName(), value, (RubyModule) typeClass);
1353         fields.put(fieldDescriptor, value);
1354       } else {
1355         fields.remove(fieldDescriptor);
1356       }
1357     }
1358     return context.nil;
1359   }
1360 
getDescriptorForField( ThreadContext context, FieldDescriptor fieldDescriptor)1361   private IRubyObject getDescriptorForField(
1362       ThreadContext context, FieldDescriptor fieldDescriptor) {
1363     RubyDescriptor thisRbDescriptor = (RubyDescriptor) getDescriptor(context, metaClass);
1364     RubyFieldDescriptor fd =
1365         (RubyFieldDescriptor)
1366             thisRbDescriptor.lookup(context, context.runtime.newString(fieldDescriptor.getName()));
1367     return fd.getSubtype(context);
1368   }
1369 
enumToSymbol( ThreadContext context, EnumDescriptor enumDescriptor, IRubyObject value)1370   private IRubyObject enumToSymbol(
1371       ThreadContext context, EnumDescriptor enumDescriptor, IRubyObject value) {
1372     if (value instanceof RubySymbol) {
1373       return (RubySymbol) value;
1374     } else if (Utils.isRubyNum(value)) {
1375       EnumValueDescriptor enumValue =
1376           enumDescriptor.findValueByNumberCreatingIfUnknown(RubyNumeric.num2int(value));
1377       if (enumValue.getIndex() != -1) {
1378         return context.runtime.newSymbol(enumValue.getName());
1379       } else {
1380         return value;
1381       }
1382     } else if (value instanceof RubyString) {
1383       return ((RubyString) value).intern();
1384     }
1385 
1386     return context.runtime.newSymbol("UNKNOWN");
1387   }
1388 
rubyToRepeatedField( ThreadContext context, FieldDescriptor fieldDescriptor, IRubyObject value)1389   private RubyRepeatedField rubyToRepeatedField(
1390       ThreadContext context, FieldDescriptor fieldDescriptor, IRubyObject value) {
1391     RubyArray arr = value.convertToArray();
1392     RubyRepeatedField repeatedField = repeatedFieldForFieldDescriptor(context, fieldDescriptor);
1393     IRubyObject[] values = new IRubyObject[arr.size()];
1394     FieldDescriptor.Type fieldType = fieldDescriptor.getType();
1395 
1396     RubyModule typeClass = null;
1397     if (fieldType == FieldDescriptor.Type.MESSAGE) {
1398       RubyDescriptor descriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
1399       typeClass = (RubyModule) descriptor.msgclass(context);
1400     } else if (fieldType == FieldDescriptor.Type.ENUM) {
1401       RubyEnumDescriptor enumDescriptor =
1402           (RubyEnumDescriptor) getDescriptorForField(context, fieldDescriptor);
1403       typeClass = (RubyModule) enumDescriptor.enummodule(context);
1404     }
1405 
1406     for (int i = 0; i < arr.size(); i++) {
1407       IRubyObject item = arr.eltInternal(i);
1408       if (item.isNil()) {
1409         throw Utils.createTypeError(context, "nil message not allowed here.");
1410       }
1411       if (item instanceof RubyHash && typeClass != null) {
1412         values[i] = ((RubyClass) typeClass).newInstance(context, item, Block.NULL_BLOCK);
1413       } else {
1414         if (fieldType == FieldDescriptor.Type.ENUM) {
1415           item = enumToSymbol(context, fieldDescriptor.getEnumType(), item);
1416         }
1417 
1418         values[i] = item;
1419       }
1420     }
1421     repeatedField.push(context, values);
1422 
1423     return repeatedField;
1424   }
1425 
newMapForField(ThreadContext context, FieldDescriptor fieldDescriptor)1426   private RubyMap newMapForField(ThreadContext context, FieldDescriptor fieldDescriptor) {
1427     RubyDescriptor mapDescriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
1428     FieldDescriptor keyField = fieldDescriptor.getMessageType().findFieldByNumber(1);
1429     FieldDescriptor valueField = fieldDescriptor.getMessageType().findFieldByNumber(2);
1430     IRubyObject keyType = RubySymbol.newSymbol(context.runtime, keyField.getType().name());
1431     IRubyObject valueType = RubySymbol.newSymbol(context.runtime, valueField.getType().name());
1432 
1433     if (valueField.getType() == FieldDescriptor.Type.MESSAGE) {
1434       RubyFieldDescriptor rubyFieldDescriptor =
1435           (RubyFieldDescriptor) mapDescriptor.lookup(context, context.runtime.newString("value"));
1436       RubyDescriptor rubyDescriptor = (RubyDescriptor) rubyFieldDescriptor.getSubtype(context);
1437       return (RubyMap)
1438           cMap.newInstance(
1439               context, keyType, valueType, rubyDescriptor.msgclass(context), Block.NULL_BLOCK);
1440 
1441     } else if (valueField.getType() == FieldDescriptor.Type.ENUM) {
1442       RubyFieldDescriptor rubyFieldDescriptor =
1443           (RubyFieldDescriptor) mapDescriptor.lookup(context, context.runtime.newString("value"));
1444       RubyEnumDescriptor rubyEnumDescriptor =
1445           (RubyEnumDescriptor) rubyFieldDescriptor.getSubtype(context);
1446       return (RubyMap)
1447           cMap.newInstance(
1448               context,
1449               keyType,
1450               valueType,
1451               rubyEnumDescriptor.enummodule(context),
1452               Block.NULL_BLOCK);
1453 
1454     } else {
1455       return (RubyMap) cMap.newInstance(context, keyType, valueType, Block.NULL_BLOCK);
1456     }
1457   }
1458 
isWrappable(FieldDescriptor fieldDescriptor)1459   private boolean isWrappable(FieldDescriptor fieldDescriptor) {
1460     if (fieldDescriptor.getType() != FieldDescriptor.Type.MESSAGE) return false;
1461 
1462     return isWrapper(fieldDescriptor.getMessageType());
1463   }
1464 
isWrapper(Descriptor messageDescriptor)1465   private static boolean isWrapper(Descriptor messageDescriptor) {
1466     switch (messageDescriptor.getFullName()) {
1467       case "google.protobuf.DoubleValue":
1468       case "google.protobuf.FloatValue":
1469       case "google.protobuf.Int64Value":
1470       case "google.protobuf.UInt64Value":
1471       case "google.protobuf.Int32Value":
1472       case "google.protobuf.UInt32Value":
1473       case "google.protobuf.BoolValue":
1474       case "google.protobuf.StringValue":
1475       case "google.protobuf.BytesValue":
1476         return true;
1477       default:
1478         return false;
1479     }
1480   }
1481 
validateMessageType( ThreadContext context, FieldDescriptor fieldDescriptor, String methodName)1482   private void validateMessageType(
1483       ThreadContext context, FieldDescriptor fieldDescriptor, String methodName) {
1484     if (descriptor != fieldDescriptor.getContainingType()) {
1485       throw Utils.createTypeError(context, methodName + " method called on wrong message type");
1486     }
1487   }
1488 
1489   private static RubyClass parseErrorClass;
1490 
1491   private static final String AS_VALUE_SUFFIX = "_as_value";
1492   private static final String CLEAR_PREFIX = "clear_";
1493   private static final String CONST_SUFFIX = "_const";
1494   private static final String HAS_PREFIX = "has_";
1495   private static final String QUESTION_MARK = "?";
1496   private static final int SINK_MAXIMUM_NESTING = 64;
1497 
1498   private Descriptor descriptor;
1499   private DynamicMessage.Builder builder;
1500   private Map<FieldDescriptor, IRubyObject> fields;
1501   private Map<OneofDescriptor, FieldDescriptor> oneofCases;
1502   private RubyClass cRepeatedField;
1503   private RubyClass cMap;
1504   private boolean ignoreUnknownFieldsOnInit = false;
1505 }
1506