Home | History | Annotate | Download | only in jruby
      1 /*
      2  * Protocol Buffers - Google's data interchange format
      3  * Copyright 2014 Google Inc.  All rights reserved.
      4  * https://developers.google.com/protocol-buffers/
      5  *
      6  * Redistribution and use in source and binary forms, with or without
      7  * modification, are permitted provided that the following conditions are
      8  * met:
      9  *
     10  *     * Redistributions of source code must retain the above copyright
     11  * notice, this list of conditions and the following disclaimer.
     12  *     * Redistributions in binary form must reproduce the above
     13  * copyright notice, this list of conditions and the following disclaimer
     14  * in the documentation and/or other materials provided with the
     15  * distribution.
     16  *     * Neither the name of Google Inc. nor the names of its
     17  * contributors may be used to endorse or promote products derived from
     18  * this software without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     33 package com.google.protobuf.jruby;
     34 
     35 import com.google.protobuf.Descriptors;
     36 import org.jruby.*;
     37 import org.jruby.anno.JRubyClass;
     38 import org.jruby.anno.JRubyMethod;
     39 import org.jruby.runtime.Binding;
     40 import org.jruby.runtime.Block;
     41 import org.jruby.runtime.ObjectAllocator;
     42 import org.jruby.runtime.ThreadContext;
     43 import org.jruby.runtime.builtin.IRubyObject;
     44 
     45 @JRubyClass(name = "MessageBuilderContext")
     46 public class RubyMessageBuilderContext extends RubyObject {
     47     public static void createRubyMessageBuilderContext(Ruby runtime) {
     48         RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf");
     49         RubyClass cMessageBuilderContext = protobuf.defineClassUnder("MessageBuilderContext", runtime.getObject(), new ObjectAllocator() {
     50             @Override
     51             public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
     52                 return new RubyMessageBuilderContext(runtime, klazz);
     53             }
     54         });
     55         cMessageBuilderContext.defineAnnotatedMethods(RubyMessageBuilderContext.class);
     56     }
     57 
     58     public RubyMessageBuilderContext(Ruby ruby, RubyClass klazz) {
     59         super(ruby, klazz);
     60     }
     61 
     62     @JRubyMethod
     63     public IRubyObject initialize(ThreadContext context, IRubyObject descriptor, IRubyObject rubyBuilder) {
     64         this.cFieldDescriptor = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::FieldDescriptor");
     65         this.cDescriptor = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::Descriptor");
     66         this.cOneofDescriptor = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::OneofDescriptor");
     67         this.cOneofBuilderContext = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::Internal::OneofBuilderContext");
     68         this.descriptor = (RubyDescriptor) descriptor;
     69         this.builder = (RubyBuilder) rubyBuilder;
     70         return this;
     71     }
     72 
     73     /*
     74      * call-seq:
     75      *     MessageBuilderContext.optional(name, type, number, type_class = nil)
     76      *
     77      * Defines a new optional field on this message type with the given type, tag
     78      * number, and type class (for message and enum fields). The type must be a Ruby
     79      * symbol (as accepted by FieldDescriptor#type=) and the type_class must be a
     80      * string, if present (as accepted by FieldDescriptor#submsg_name=).
     81      */
     82     @JRubyMethod(required = 3, optional = 1)
     83     public IRubyObject optional(ThreadContext context, IRubyObject[] args) {
     84         Ruby runtime = context.runtime;
     85         IRubyObject typeClass = runtime.getNil();
     86         if (args.length > 3) typeClass = args[3];
     87         msgdefAddField(context, "optional", args[0], args[1], args[2], typeClass);
     88         return context.runtime.getNil();
     89     }
     90 
     91     /*
     92      * call-seq:
     93      *     MessageBuilderContext.required(name, type, number, type_class = nil)
     94      *
     95      * Defines a new required field on this message type with the given type, tag
     96      * number, and type class (for message and enum fields). The type must be a Ruby
     97      * symbol (as accepted by FieldDescriptor#type=) and the type_class must be a
     98      * string, if present (as accepted by FieldDescriptor#submsg_name=).
     99      *
    100      * Proto3 does not have required fields, but this method exists for
    101      * completeness. Any attempt to add a message type with required fields to a
    102      * pool will currently result in an error.
    103      */
    104     @JRubyMethod(required = 3, optional = 1)
    105     public IRubyObject required(ThreadContext context, IRubyObject[] args) {
    106         IRubyObject typeClass = context.runtime.getNil();
    107         if (args.length > 3) typeClass = args[3];
    108         msgdefAddField(context, "required", args[0], args[1], args[2], typeClass);
    109         return context.runtime.getNil();
    110     }
    111 
    112     /*
    113      * call-seq:
    114      *     MessageBuilderContext.repeated(name, type, number, type_class = nil)
    115      *
    116      * Defines a new repeated field on this message type with the given type, tag
    117      * number, and type class (for message and enum fields). The type must be a Ruby
    118      * symbol (as accepted by FieldDescriptor#type=) and the type_class must be a
    119      * string, if present (as accepted by FieldDescriptor#submsg_name=).
    120      */
    121     @JRubyMethod(required = 3, optional = 1)
    122     public IRubyObject repeated(ThreadContext context, IRubyObject[] args) {
    123         IRubyObject typeClass = context.runtime.getNil();
    124         if (args.length > 3) typeClass = args[3];
    125         msgdefAddField(context, "repeated", args[0], args[1], args[2], typeClass);
    126         return context.runtime.getNil();
    127     }
    128 
    129     /*
    130      * call-seq:
    131      *     MessageBuilderContext.map(name, key_type, value_type, number,
    132      *                               value_type_class = nil)
    133      *
    134      * Defines a new map field on this message type with the given key and value
    135      * types, tag number, and type class (for message and enum value types). The key
    136      * type must be :int32/:uint32/:int64/:uint64, :bool, or :string. The value type
    137      * type must be a Ruby symbol (as accepted by FieldDescriptor#type=) and the
    138      * type_class must be a string, if present (as accepted by
    139      * FieldDescriptor#submsg_name=).
    140      */
    141     @JRubyMethod(required = 4, optional = 1)
    142     public IRubyObject map(ThreadContext context, IRubyObject[] args) {
    143         Ruby runtime = context.runtime;
    144         IRubyObject name = args[0];
    145         IRubyObject keyType = args[1];
    146         IRubyObject valueType = args[2];
    147         IRubyObject number = args[3];
    148         IRubyObject typeClass = args.length > 4 ? args[4] : context.runtime.getNil();
    149 
    150         // Validate the key type. We can't accept enums, messages, or floats/doubles
    151         // as map keys. (We exclude these explicitly, and the field-descriptor setter
    152         // below then ensures that the type is one of the remaining valid options.)
    153         if (keyType.equals(RubySymbol.newSymbol(runtime, "float")) ||
    154                 keyType.equals(RubySymbol.newSymbol(runtime, "double")) ||
    155                 keyType.equals(RubySymbol.newSymbol(runtime, "enum")) ||
    156                 keyType.equals(RubySymbol.newSymbol(runtime, "message")))
    157             throw runtime.newArgumentError("Cannot add a map field with a float, double, enum, or message type.");
    158 
    159         // Create a new message descriptor for the map entry message, and create a
    160         // repeated submessage field here with that type.
    161         RubyDescriptor mapentryDesc = (RubyDescriptor) cDescriptor.newInstance(context, Block.NULL_BLOCK);
    162         IRubyObject mapentryDescName = RubySymbol.newSymbol(runtime, name).id2name(context);
    163         mapentryDesc.setName(context, mapentryDescName);
    164         mapentryDesc.setMapEntry(true);
    165 
    166         //optional <type> key = 1;
    167         RubyFieldDescriptor keyField = (RubyFieldDescriptor) cFieldDescriptor.newInstance(context, Block.NULL_BLOCK);
    168         keyField.setName(context, runtime.newString("key"));
    169         keyField.setLabel(context, RubySymbol.newSymbol(runtime, "optional"));
    170         keyField.setNumber(context, runtime.newFixnum(1));
    171         keyField.setType(context, keyType);
    172         mapentryDesc.addField(context, keyField);
    173 
    174         //optional <type> value = 2;
    175         RubyFieldDescriptor valueField = (RubyFieldDescriptor) cFieldDescriptor.newInstance(context, Block.NULL_BLOCK);
    176         valueField.setName(context, runtime.newString("value"));
    177         valueField.setLabel(context, RubySymbol.newSymbol(runtime, "optional"));
    178         valueField.setNumber(context, runtime.newFixnum(2));
    179         valueField.setType(context, valueType);
    180         if (! typeClass.isNil()) valueField.setSubmsgName(context, typeClass);
    181         mapentryDesc.addField(context, valueField);
    182 
    183         // Add the map-entry message type to the current builder, and use the type to
    184         // create the map field itself.
    185         this.builder.pendingList.add(mapentryDesc);
    186 
    187         msgdefAddField(context, "repeated", name, runtime.newSymbol("message"), number, mapentryDescName);
    188         return runtime.getNil();
    189     }
    190 
    191     @JRubyMethod
    192     public IRubyObject oneof(ThreadContext context, IRubyObject name, Block block) {
    193         RubyOneofDescriptor oneofdef = (RubyOneofDescriptor)
    194                 cOneofDescriptor.newInstance(context, Block.NULL_BLOCK);
    195         RubyOneofBuilderContext ctx = (RubyOneofBuilderContext)
    196                 cOneofBuilderContext.newInstance(context, oneofdef, Block.NULL_BLOCK);
    197         oneofdef.setName(context, name);
    198         Binding binding = block.getBinding();
    199         binding.setSelf(ctx);
    200         block.yieldSpecific(context);
    201         descriptor.addOneof(context, oneofdef);
    202         return context.runtime.getNil();
    203     }
    204 
    205     private void msgdefAddField(ThreadContext context, String label, IRubyObject name,
    206                                 IRubyObject type, IRubyObject number, IRubyObject typeClass) {
    207         descriptor.addField(context,
    208                 Utils.msgdefCreateField(context, label, name, type, number, typeClass, cFieldDescriptor));
    209     }
    210 
    211     private RubyDescriptor descriptor;
    212     private RubyBuilder builder;
    213     private RubyClass cFieldDescriptor;
    214     private RubyClass cOneofDescriptor;
    215     private RubyClass cOneofBuilderContext;
    216     private RubyClass cDescriptor;
    217 }
    218