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