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