blob: 9ddadfcdd3c35562cfc71e450de0f80a6179c2e1 [file] [log] [blame]
Isaiah Peng27e2b572014-12-24 15:48:41 +01001/*
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
33package com.google.protobuf.jruby;
34
35import com.google.protobuf.*;
36import org.jruby.*;
37import org.jruby.anno.JRubyMethod;
38import org.jruby.runtime.Block;
39import org.jruby.runtime.Helpers;
40import org.jruby.runtime.ThreadContext;
41import org.jruby.runtime.builtin.IRubyObject;
42import org.jruby.util.ByteList;
43
44import java.util.HashMap;
45import java.util.Map;
46
47public class RubyMessage extends RubyObject {
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)
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.");
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.newTypeError("Expected array as initializer var for repeated field.");
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 = "[]=")
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 = "[]")
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
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
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 = "==")
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)
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 return index(context, args[0]);
221 }
222 RubyOneofDescriptor rubyOneofDescriptor = (RubyOneofDescriptor) oneofDescriptor;
223 Descriptors.FieldDescriptor fieldDescriptor =
224 oneofCases.get(rubyOneofDescriptor.getOneofDescriptor());
225 if (fieldDescriptor == null)
226 return context.runtime.getNil();
227
228 return context.runtime.newSymbol(fieldDescriptor.getName());
229 } else {
230 // fieldName is RubySymbol
231 RubyString field = args[0].asString();
232 RubyString equalSign = context.runtime.newString(Utils.EQUAL_SIGN);
233 if (field.end_with_p(context, equalSign).isTrue()) {
234 field.chomp_bang(context, equalSign);
235 }
236 return indexSet(context, field, args[1]);
237 }
238 }
239
240 /**
241 * call-seq:
242 * Message.dup => new_message
243 * Performs a shallow copy of this message and returns the new copy.
244 */
245 @JRubyMethod
246 public IRubyObject dup(ThreadContext context) {
247 RubyMessage dup = (RubyMessage) metaClass.newInstance(context, Block.NULL_BLOCK);
248 IRubyObject value;
249 for (Descriptors.FieldDescriptor fieldDescriptor : builder.getAllFields().keySet()) {
250 if (fieldDescriptor.isRepeated()) {
251 dup.repeatedFields.put(fieldDescriptor, getRepeatedField(context, fieldDescriptor));
252 } else if (builder.hasField(fieldDescriptor)) {
253 dup.fields.put(fieldDescriptor, wrapField(context, fieldDescriptor, builder.getField(fieldDescriptor)));
254 }
255 }
256 for (Descriptors.FieldDescriptor fieldDescriptor : fields.keySet()) {
257 dup.fields.put(fieldDescriptor, fields.get(fieldDescriptor));
258 }
259 for (Descriptors.FieldDescriptor fieldDescriptor : maps.keySet()) {
260 dup.maps.put(fieldDescriptor, maps.get(fieldDescriptor));
261 }
262 return dup;
263 }
264
265 /*
266 * call-seq:
267 * Message.descriptor => descriptor
268 *
269 * Class method that returns the Descriptor instance corresponding to this
270 * message class's type.
271 */
272 @JRubyMethod(name = "descriptor", meta = true)
273 public static IRubyObject getDescriptor(ThreadContext context, IRubyObject recv) {
274 return ((RubyClass) recv).getInstanceVariable(Utils.DESCRIPTOR_INSTANCE_VAR);
275 }
276
277 /*
278 * call-seq:
279 * MessageClass.encode(msg) => bytes
280 *
281 * Encodes the given message object to its serialized form in protocol buffers
282 * wire format.
283 */
284 @JRubyMethod(meta = true)
285 public static IRubyObject encode(ThreadContext context, IRubyObject recv, IRubyObject value) {
286 RubyMessage message = (RubyMessage) value;
287 return context.runtime.newString(new ByteList(message.build(context).toByteArray()));
288 }
289
290 /*
291 * call-seq:
292 * MessageClass.decode(data) => message
293 *
294 * Decodes the given data (as a string containing bytes in protocol buffers wire
295 * format) under the interpretration given by this message class's definition
296 * and returns a message object with the corresponding field values.
297 */
298 @JRubyMethod(meta = true)
299 public static IRubyObject decode(ThreadContext context, IRubyObject recv, IRubyObject data) {
300 byte[] bin = data.convertToString().getBytes();
301 RubyMessage ret = (RubyMessage) ((RubyClass) recv).newInstance(context, Block.NULL_BLOCK);
302 try {
303 ret.builder.mergeFrom(bin);
304 } catch (InvalidProtocolBufferException e) {
305 throw context.runtime.newRuntimeError(e.getMessage());
306 }
307 return ret;
308 }
309
310 /*
311 * call-seq:
312 * MessageClass.encode_json(msg) => json_string
313 *
314 * Encodes the given message object into its serialized JSON representation.
315 */
316 @JRubyMethod(name = "encode_json", meta = true)
317 public static IRubyObject encodeJson(ThreadContext context, IRubyObject recv, IRubyObject msgRb) {
318 RubyMessage message = (RubyMessage) msgRb;
319 return Helpers.invoke(context, message.toHash(context), "to_json");
320 }
321
322 /*
323 * call-seq:
324 * MessageClass.decode_json(data) => message
325 *
326 * Decodes the given data (as a string containing bytes in protocol buffers wire
327 * format) under the interpretration given by this message class's definition
328 * and returns a message object with the corresponding field values.
329 */
330 @JRubyMethod(name = "decode_json", meta = true)
331 public static IRubyObject decodeJson(ThreadContext context, IRubyObject recv, IRubyObject json) {
332 Ruby runtime = context.runtime;
333 RubyMessage ret = (RubyMessage) ((RubyClass) recv).newInstance(context, Block.NULL_BLOCK);
334 RubyModule jsonModule = runtime.getClassFromPath("JSON");
335 RubyHash opts = RubyHash.newHash(runtime);
336 opts.fastASet(runtime.newSymbol("symbolize_names"), runtime.getTrue());
337 IRubyObject[] args = new IRubyObject[] { Helpers.invoke(context, jsonModule, "parse", json, opts) };
338 ret.initialize(context, args);
339 return ret;
340 }
341
342 @JRubyMethod(name = "to_h")
343 public IRubyObject toHash(ThreadContext context) {
344 Ruby runtime = context.runtime;
345 RubyHash ret = RubyHash.newHash(runtime);
346 for (Descriptors.FieldDescriptor fdef : this.descriptor.getFields()) {
347 IRubyObject value = getField(context, fdef);
348 if (value.respondsTo("to_h")) {
349 value = Helpers.invoke(context, value, "to_h");
350 }
351 ret.fastASet(runtime.newString(fdef.getName()), value);
352 }
353 return ret;
354 }
355
356 protected DynamicMessage build(ThreadContext context) {
357 return build(context, 0);
358 }
359
360 protected DynamicMessage build(ThreadContext context, int depth) {
361 if (depth > SINK_MAXIMUM_NESTING) {
362 throw context.runtime.newRuntimeError("Maximum recursion depth exceeded during encoding.");
363 }
364 for (Descriptors.FieldDescriptor fieldDescriptor : maps.keySet()) {
365 this.builder.clearField(fieldDescriptor);
366 RubyDescriptor mapDescriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
367 for (DynamicMessage kv : maps.get(fieldDescriptor).build(context, mapDescriptor)) {
368 this.builder.addRepeatedField(fieldDescriptor, kv);
369 }
370 }
371 for (Descriptors.FieldDescriptor fieldDescriptor : repeatedFields.keySet()) {
372 RubyRepeatedField repeatedField = repeatedFields.get(fieldDescriptor);
373 this.builder.clearField(fieldDescriptor);
374 for (int i = 0; i < repeatedField.size(); i++) {
375 Object item = convert(context, fieldDescriptor, repeatedField.get(i), depth);
376 this.builder.addRepeatedField(fieldDescriptor, item);
377 }
378 }
379 for (Descriptors.FieldDescriptor fieldDescriptor : fields.keySet()) {
380 IRubyObject value = fields.get(fieldDescriptor);
381 this.builder.setField(fieldDescriptor, convert(context, fieldDescriptor, value, depth));
382 }
383 return this.builder.build();
384 }
385
386 protected Descriptors.Descriptor getDescriptor() {
387 return this.descriptor;
388 }
389
390 // Internal use only, called by Google::Protobuf.deep_copy
391 protected IRubyObject deepCopy(ThreadContext context) {
392 RubyMessage copy = (RubyMessage) metaClass.newInstance(context, Block.NULL_BLOCK);
393 for (Descriptors.FieldDescriptor fdef : this.descriptor.getFields()) {
394 if (fdef.isRepeated()) {
395 copy.addRepeatedField(fdef, this.getRepeatedField(context, fdef).deepCopy(context));
396 } else if (fields.containsKey(fdef)) {
397 copy.fields.put(fdef, fields.get(fdef));
398 } else if (this.builder.hasField(fdef)) {
399 copy.fields.put(fdef, wrapField(context, fdef, this.builder.getField(fdef)));
400 }
401 }
402 return copy;
403 }
404
405 private RubyRepeatedField getRepeatedField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor) {
406 if (this.repeatedFields.containsKey(fieldDescriptor)) {
407 return this.repeatedFields.get(fieldDescriptor);
408 }
409 int count = this.builder.getRepeatedFieldCount(fieldDescriptor);
410 RubyRepeatedField ret = repeatedFieldForFieldDescriptor(context, fieldDescriptor);
411 for (int i = 0; i < count; i++) {
412 ret.push(context, wrapField(context, fieldDescriptor, this.builder.getRepeatedField(fieldDescriptor, i)));
413 }
414 return ret;
415 }
416
417 private void addRepeatedField(Descriptors.FieldDescriptor fieldDescriptor, RubyRepeatedField repeatedField) {
418 this.repeatedFields.put(fieldDescriptor, repeatedField);
419 }
420
421 private IRubyObject buildFrom(ThreadContext context, DynamicMessage dynamicMessage) {
422 this.builder.mergeFrom(dynamicMessage);
423 return this;
424 }
425
426 private Descriptors.FieldDescriptor findField(ThreadContext context, IRubyObject fieldName) {
427 String nameStr = fieldName.asJavaString();
428 Descriptors.FieldDescriptor ret = this.descriptor.findFieldByName(Utils.escapeIdentifier(nameStr));
429 if (ret == null)
430 throw context.runtime.newArgumentError("field " + fieldName.asJavaString() + " is not found");
431 return ret;
432 }
433
434 private void checkRepeatedFieldType(ThreadContext context, IRubyObject value,
435 Descriptors.FieldDescriptor fieldDescriptor) {
436 Ruby runtime = context.runtime;
437 if (!(value instanceof RubyRepeatedField)) {
438 throw runtime.newTypeError("Expected repeated field array");
439 }
440 }
441
442 // convert a ruby object to protobuf type, with type check
443 private Object convert(ThreadContext context,
444 Descriptors.FieldDescriptor fieldDescriptor,
445 IRubyObject value, int depth) {
446 Ruby runtime = context.runtime;
447 Object val = null;
448 switch (fieldDescriptor.getType()) {
449 case INT32:
450 case INT64:
451 case UINT32:
452 case UINT64:
453 if (!Utils.isRubyNum(value)) {
454 throw runtime.newTypeError("Expected number type for integral field.");
455 }
456 Utils.checkIntTypePrecision(context, fieldDescriptor.getType(), value);
457 switch (fieldDescriptor.getType()) {
458 case INT32:
459 val = RubyNumeric.num2int(value);
460 break;
461 case INT64:
462 val = RubyNumeric.num2long(value);
463 break;
464 case UINT32:
465 val = Utils.num2uint(value);
466 break;
467 case UINT64:
468 val = Utils.num2ulong(context.runtime, value);
469 break;
470 default:
471 break;
472 }
473 break;
474 case FLOAT:
475 if (!Utils.isRubyNum(value))
476 throw runtime.newTypeError("Expected number type for float field.");
477 val = (float) RubyNumeric.num2dbl(value);
478 break;
479 case DOUBLE:
480 if (!Utils.isRubyNum(value))
481 throw runtime.newTypeError("Expected number type for double field.");
482 val = RubyNumeric.num2dbl(value);
483 break;
484 case BOOL:
485 if (!(value instanceof RubyBoolean))
486 throw runtime.newTypeError("Invalid argument for boolean field.");
487 val = value.isTrue();
488 break;
489 case BYTES:
490 case STRING:
491 Utils.validateStringEncoding(context.runtime, fieldDescriptor.getType(), value);
492 RubyString str = (RubyString) value;
493 switch (fieldDescriptor.getType()) {
494 case BYTES:
495 val = ByteString.copyFrom(str.getBytes());
496 break;
497 case STRING:
498 val = str.asJavaString();
499 break;
500 default:
501 break;
502 }
503 break;
504 case MESSAGE:
505 RubyClass typeClass = (RubyClass) ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor)).msgclass(context);
506 if (!value.getMetaClass().equals(typeClass))
507 throw runtime.newTypeError(value, "Invalid type to assign to submessage field.");
508 val = ((RubyMessage) value).build(context, depth + 1);
509 break;
510 case ENUM:
511 Descriptors.EnumDescriptor enumDescriptor = fieldDescriptor.getEnumType();
512
513 if (Utils.isRubyNum(value)) {
514 val = enumDescriptor.findValueByNumberCreatingIfUnknown(RubyNumeric.num2int(value));
515 } else if (value instanceof RubySymbol) {
516 val = enumDescriptor.findValueByName(value.asJavaString());
517 } else {
518 throw runtime.newTypeError("Expected number or symbol type for enum field.");
519 }
520 if (val == null) {
521 throw runtime.newRangeError("Enum value " + value + " is not found.");
522 }
523 break;
524 default:
525 break;
526 }
527 return val;
528 }
529
530 private IRubyObject wrapField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor, Object value) {
531 if (value == null) {
532 return context.runtime.getNil();
533 }
534 Ruby runtime = context.runtime;
535 switch (fieldDescriptor.getType()) {
536 case INT32:
537 case INT64:
538 case UINT32:
539 case UINT64:
540 case FLOAT:
541 case DOUBLE:
542 case BOOL:
543 case BYTES:
544 case STRING:
545 return Utils.wrapPrimaryValue(context, fieldDescriptor.getType(), value);
546 case MESSAGE:
547 RubyClass typeClass = (RubyClass) ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor)).msgclass(context);
548 RubyMessage msg = (RubyMessage) typeClass.newInstance(context, Block.NULL_BLOCK);
549 return msg.buildFrom(context, (DynamicMessage) value);
550 case ENUM:
551 Descriptors.EnumValueDescriptor enumValueDescriptor = (Descriptors.EnumValueDescriptor) value;
552 if (enumValueDescriptor.getIndex() == -1) { // UNKNOWN ENUM VALUE
553 return runtime.newFixnum(enumValueDescriptor.getNumber());
554 }
555 return runtime.newSymbol(enumValueDescriptor.getName());
556 default:
557 return runtime.newString(value.toString());
558 }
559 }
560
561 private RubyRepeatedField repeatedFieldForFieldDescriptor(ThreadContext context,
562 Descriptors.FieldDescriptor fieldDescriptor) {
563 IRubyObject typeClass = context.runtime.getNilClass();
564
565 IRubyObject descriptor = getDescriptorForField(context, fieldDescriptor);
566 Descriptors.FieldDescriptor.Type type = fieldDescriptor.getType();
567 if (type == Descriptors.FieldDescriptor.Type.MESSAGE) {
568 typeClass = ((RubyDescriptor) descriptor).msgclass(context);
569
570 } else if (type == Descriptors.FieldDescriptor.Type.ENUM) {
571 typeClass = ((RubyEnumDescriptor) descriptor).enummodule(context);
572 }
573 return new RubyRepeatedField(context.runtime, cRepeatedField, type, typeClass);
574 }
575
576 protected IRubyObject getField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor) {
577 Descriptors.OneofDescriptor oneofDescriptor = fieldDescriptor.getContainingOneof();
578 if (oneofDescriptor != null) {
579 if (oneofCases.containsKey(oneofDescriptor)) {
580 if (oneofCases.get(oneofDescriptor) != fieldDescriptor)
581 return context.runtime.getNil();
582 return fields.get(fieldDescriptor);
583 } else {
584 Descriptors.FieldDescriptor oneofCase = builder.getOneofFieldDescriptor(oneofDescriptor);
585 if (oneofCase != fieldDescriptor) return context.runtime.getNil();
586 IRubyObject value = wrapField(context, oneofCase, builder.getField(oneofCase));
587 fields.put(fieldDescriptor, value);
588 return value;
589 }
590 }
591
592 if (Utils.isMapEntry(fieldDescriptor)) {
593 RubyMap map = maps.get(fieldDescriptor);
594 if (map == null) {
595 map = newMapForField(context, fieldDescriptor);
596 int mapSize = this.builder.getRepeatedFieldCount(fieldDescriptor);
597 Descriptors.FieldDescriptor keyField = fieldDescriptor.getMessageType().findFieldByNumber(1);
598 Descriptors.FieldDescriptor valueField = fieldDescriptor.getMessageType().findFieldByNumber(2);
599 RubyDescriptor kvDescriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
600 RubyClass kvClass = (RubyClass) kvDescriptor.msgclass(context);
601 for (int i = 0; i < mapSize; i++) {
602 RubyMessage kvMessage = (RubyMessage) kvClass.newInstance(context, Block.NULL_BLOCK);
603 DynamicMessage message = (DynamicMessage) this.builder.getRepeatedField(fieldDescriptor, i);
604 kvMessage.buildFrom(context, message);
605 map.indexSet(context, kvMessage.getField(context, keyField), kvMessage.getField(context, valueField));
606 }
607 maps.put(fieldDescriptor, map);
608 }
609 return map;
610 }
611 if (fieldDescriptor.isRepeated()) {
612 return getRepeatedField(context, fieldDescriptor);
613 }
614 if (fieldDescriptor.getType() != Descriptors.FieldDescriptor.Type.MESSAGE ||
615 this.builder.hasField(fieldDescriptor) || fields.containsKey(fieldDescriptor)) {
616 if (fields.containsKey(fieldDescriptor)) {
617 return fields.get(fieldDescriptor);
618 } else {
619 IRubyObject value = wrapField(context, fieldDescriptor, this.builder.getField(fieldDescriptor));
620 if (this.builder.hasField(fieldDescriptor)) {
621 fields.put(fieldDescriptor, value);
622 }
623 return value;
624 }
625 }
626 return context.runtime.getNil();
627 }
628
629 protected IRubyObject setField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor, IRubyObject value) {
630 if (Utils.isMapEntry(fieldDescriptor)) {
631 if (!(value instanceof RubyMap)) {
632 throw context.runtime.newTypeError("Expected Map instance");
633 }
634 RubyMap thisMap = (RubyMap) getField(context, fieldDescriptor);
635 thisMap.mergeIntoSelf(context, value);
636 } else if (fieldDescriptor.isRepeated()) {
637 checkRepeatedFieldType(context, value, fieldDescriptor);
638 if (value instanceof RubyRepeatedField) {
639 addRepeatedField(fieldDescriptor, (RubyRepeatedField) value);
640 } else {
641 RubyArray ary = value.convertToArray();
642 RubyRepeatedField repeatedField = rubyToRepeatedField(context, fieldDescriptor, ary);
643 addRepeatedField(fieldDescriptor, repeatedField);
644 }
645 } else {
646 Descriptors.OneofDescriptor oneofDescriptor = fieldDescriptor.getContainingOneof();
647 if (oneofDescriptor != null) {
648 Descriptors.FieldDescriptor oneofCase = oneofCases.get(oneofDescriptor);
649 if (oneofCase != null && oneofCase != fieldDescriptor) {
650 fields.remove(oneofCase);
651 }
652 if (value.isNil()) {
653 oneofCases.remove(oneofDescriptor);
654 fields.remove(fieldDescriptor);
655 } else {
656 oneofCases.put(oneofDescriptor, fieldDescriptor);
657 fields.put(fieldDescriptor, value);
658 }
659 } else {
660 Descriptors.FieldDescriptor.Type fieldType = fieldDescriptor.getType();
661 IRubyObject typeClass = context.runtime.getObject();
Adam Greene64678262015-05-02 13:48:23 -0700662 boolean addValue = true;
Isaiah Peng27e2b572014-12-24 15:48:41 +0100663 if (fieldType == Descriptors.FieldDescriptor.Type.MESSAGE) {
664 typeClass = ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor)).msgclass(context);
Adam Greene64678262015-05-02 13:48:23 -0700665 if (value.isNil()){
666 addValue = false;
667 }
Isaiah Peng27e2b572014-12-24 15:48:41 +0100668 } else if (fieldType == Descriptors.FieldDescriptor.Type.ENUM) {
669 typeClass = ((RubyEnumDescriptor) getDescriptorForField(context, fieldDescriptor)).enummodule(context);
Isaiah Peng27e2b572014-12-24 15:48:41 +0100670 Descriptors.EnumDescriptor enumDescriptor = fieldDescriptor.getEnumType();
671 if (Utils.isRubyNum(value)) {
672 Descriptors.EnumValueDescriptor val =
673 enumDescriptor.findValueByNumberCreatingIfUnknown(RubyNumeric.num2int(value));
674 if (val.getIndex() != -1) value = context.runtime.newSymbol(val.getName());
675 }
676 }
Adam Greene64678262015-05-02 13:48:23 -0700677 if (addValue) {
678 Utils.checkType(context, fieldType, value, (RubyModule) typeClass);
679 this.fields.put(fieldDescriptor, value);
680 } else {
681 this.fields.remove(fieldDescriptor);
682 }
Isaiah Peng27e2b572014-12-24 15:48:41 +0100683 }
684 }
685 return context.runtime.getNil();
686 }
687
688 private String layoutInspect() {
689 ThreadContext context = getRuntime().getCurrentContext();
690 StringBuilder sb = new StringBuilder();
691 for (Descriptors.FieldDescriptor fdef : descriptor.getFields()) {
692 sb.append(Utils.unescapeIdentifier(fdef.getName()));
693 sb.append(": ");
694 sb.append(getField(context, fdef).inspect());
695 sb.append(", ");
696 }
697 return sb.substring(0, sb.length() - 2);
698 }
699
700 private IRubyObject getDescriptorForField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor) {
701 RubyDescriptor thisRbDescriptor = (RubyDescriptor) getDescriptor(context, metaClass);
702 return thisRbDescriptor.lookup(fieldDescriptor.getName()).getSubType(context);
703 }
704
705 private RubyRepeatedField rubyToRepeatedField(ThreadContext context,
706 Descriptors.FieldDescriptor fieldDescriptor, IRubyObject value) {
707 RubyArray arr = value.convertToArray();
708 RubyRepeatedField repeatedField = repeatedFieldForFieldDescriptor(context, fieldDescriptor);
709 for (int i = 0; i < arr.size(); i++) {
710 repeatedField.push(context, arr.eltInternal(i));
711 }
712 return repeatedField;
713 }
714
715 private RubyMap newMapForField(ThreadContext context, Descriptors.FieldDescriptor fieldDescriptor) {
716 RubyDescriptor mapDescriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
717 Descriptors.FieldDescriptor keyField = fieldDescriptor.getMessageType().findFieldByNumber(1);
718 Descriptors.FieldDescriptor valueField = fieldDescriptor.getMessageType().findFieldByNumber(2);
719 IRubyObject keyType = RubySymbol.newSymbol(context.runtime, keyField.getType().name());
720 IRubyObject valueType = RubySymbol.newSymbol(context.runtime, valueField.getType().name());
721 if (valueField.getType() == Descriptors.FieldDescriptor.Type.MESSAGE) {
722 RubyFieldDescriptor rubyFieldDescriptor = (RubyFieldDescriptor) mapDescriptor.lookup(context,
723 context.runtime.newString("value"));
724 RubyDescriptor rubyDescriptor = (RubyDescriptor) rubyFieldDescriptor.getSubType(context);
725 return (RubyMap) cMap.newInstance(context, keyType, valueType,
726 rubyDescriptor.msgclass(context), Block.NULL_BLOCK);
727 } else {
728 return (RubyMap) cMap.newInstance(context, keyType, valueType, Block.NULL_BLOCK);
729 }
730 }
731
732 private Descriptors.FieldDescriptor getOneofCase(Descriptors.OneofDescriptor oneof) {
733 if (oneofCases.containsKey(oneof)) {
734 return oneofCases.get(oneof);
735 }
736 return builder.getOneofFieldDescriptor(oneof);
737 }
738
739 private Descriptors.Descriptor descriptor;
740 private DynamicMessage.Builder builder;
741 private RubyClass cRepeatedField;
742 private RubyClass cMap;
743 private Map<Descriptors.FieldDescriptor, RubyRepeatedField> repeatedFields;
744 private Map<Descriptors.FieldDescriptor, RubyMap> maps;
745 private Map<Descriptors.FieldDescriptor, IRubyObject> fields;
746 private Map<Descriptors.OneofDescriptor, Descriptors.FieldDescriptor> oneofCases;
747
748 private static final int SINK_MAXIMUM_NESTING = 64;
749}