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.DescriptorProtos;
     36 import com.google.protobuf.Descriptors;
     37 import org.jruby.*;
     38 import org.jruby.anno.JRubyClass;
     39 import org.jruby.anno.JRubyMethod;
     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 import java.util.HashMap;
     46 import java.util.Map;
     47 
     48 
     49 @JRubyClass(name = "Descriptor", include = "Enumerable")
     50 public class RubyDescriptor extends RubyObject {
     51     public static void createRubyDescriptor(Ruby runtime) {
     52         RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf");
     53         RubyClass cDescriptor = protobuf.defineClassUnder("Descriptor", runtime.getObject(), new ObjectAllocator() {
     54             @Override
     55             public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
     56                 return new RubyDescriptor(runtime, klazz);
     57             }
     58         });
     59         cDescriptor.includeModule(runtime.getEnumerable());
     60         cDescriptor.defineAnnotatedMethods(RubyDescriptor.class);
     61     }
     62 
     63     public RubyDescriptor(Ruby runtime, RubyClass klazz) {
     64         super(runtime, klazz);
     65     }
     66 
     67     /*
     68      * call-seq:
     69      *     Descriptor.new => descriptor
     70      *
     71      * Creates a new, empty, message type descriptor. At a minimum, its name must be
     72      * set before it is added to a pool. It cannot be used to create messages until
     73      * it is added to a pool, after which it becomes immutable (as part of a
     74      * finalization process).
     75      */
     76     @JRubyMethod
     77     public IRubyObject initialize(ThreadContext context) {
     78         this.builder = DescriptorProtos.DescriptorProto.newBuilder();
     79         this.fieldDefMap = new HashMap<String, RubyFieldDescriptor>();
     80         this.oneofDefs = new HashMap<IRubyObject, RubyOneofDescriptor>();
     81         return this;
     82     }
     83 
     84     /*
     85      * call-seq:
     86      *     Descriptor.name => name
     87      *
     88      * Returns the name of this message type as a fully-qualfied string (e.g.,
     89      * My.Package.MessageType).
     90      */
     91     @JRubyMethod(name = "name")
     92     public IRubyObject getName(ThreadContext context) {
     93         return this.name;
     94     }
     95 
     96     /*
     97      * call-seq:
     98      *    Descriptor.name = name
     99      *
    100      * Assigns a name to this message type. The descriptor must not have been added
    101      * to a pool yet.
    102      */
    103     @JRubyMethod(name = "name=")
    104     public IRubyObject setName(ThreadContext context, IRubyObject name) {
    105         this.name = name;
    106         this.builder.setName(Utils.escapeIdentifier(this.name.asJavaString()));
    107         return context.runtime.getNil();
    108     }
    109 
    110     /*
    111      * call-seq:
    112      *     Descriptor.add_field(field) => nil
    113      *
    114      * Adds the given FieldDescriptor to this message type. The descriptor must not
    115      * have been added to a pool yet. Raises an exception if a field with the same
    116      * name or number already exists. Sub-type references (e.g. for fields of type
    117      * message) are not resolved at this point.
    118      */
    119     @JRubyMethod(name = "add_field")
    120     public IRubyObject addField(ThreadContext context, IRubyObject obj) {
    121         RubyFieldDescriptor fieldDef = (RubyFieldDescriptor) obj;
    122         this.fieldDefMap.put(fieldDef.getName(context).asJavaString(), fieldDef);
    123         this.builder.addField(fieldDef.build());
    124         return context.runtime.getNil();
    125     }
    126 
    127     /*
    128      * call-seq:
    129      *     Descriptor.lookup(name) => FieldDescriptor
    130      *
    131      * Returns the field descriptor for the field with the given name, if present,
    132      * or nil if none.
    133      */
    134     @JRubyMethod
    135     public IRubyObject lookup(ThreadContext context, IRubyObject fieldName) {
    136         return this.fieldDefMap.get(fieldName.asJavaString());
    137     }
    138 
    139     /*
    140      * call-seq:
    141      *     Descriptor.msgclass => message_klass
    142      *
    143      * Returns the Ruby class created for this message type. Valid only once the
    144      * message type has been added to a pool.
    145      */
    146     @JRubyMethod
    147     public IRubyObject msgclass(ThreadContext context) {
    148         if (this.klazz == null) {
    149             this.klazz = buildClassFromDescriptor(context);
    150         }
    151         return this.klazz;
    152     }
    153 
    154     /*
    155      * call-seq:
    156      *     Descriptor.each(&block)
    157      *
    158      * Iterates over fields in this message type, yielding to the block on each one.
    159      */
    160     @JRubyMethod
    161     public IRubyObject each(ThreadContext context, Block block) {
    162         for (Map.Entry<String, RubyFieldDescriptor> entry : fieldDefMap.entrySet()) {
    163             block.yield(context, entry.getValue());
    164         }
    165         return context.runtime.getNil();
    166     }
    167 
    168     /*
    169      * call-seq:
    170      *     Descriptor.add_oneof(oneof) => nil
    171      *
    172      * Adds the given OneofDescriptor to this message type. This descriptor must not
    173      * have been added to a pool yet. Raises an exception if a oneof with the same
    174      * name already exists, or if any of the oneof's fields' names or numbers
    175      * conflict with an existing field in this message type. All fields in the oneof
    176      * are added to the message descriptor. Sub-type references (e.g. for fields of
    177      * type message) are not resolved at this point.
    178      */
    179     @JRubyMethod(name = "add_oneof")
    180     public IRubyObject addOneof(ThreadContext context, IRubyObject obj) {
    181         RubyOneofDescriptor def = (RubyOneofDescriptor) obj;
    182         builder.addOneofDecl(def.build(builder.getOneofDeclCount()));
    183         for (RubyFieldDescriptor fieldDescriptor : def.getFields()) {
    184             addField(context, fieldDescriptor);
    185         }
    186         oneofDefs.put(def.getName(context), def);
    187         return context.runtime.getNil();
    188     }
    189 
    190     /*
    191      * call-seq:
    192      *     Descriptor.each_oneof(&block) => nil
    193      *
    194      * Invokes the given block for each oneof in this message type, passing the
    195      * corresponding OneofDescriptor.
    196      */
    197     @JRubyMethod(name = "each_oneof")
    198     public IRubyObject eachOneof(ThreadContext context, Block block) {
    199         for (RubyOneofDescriptor oneofDescriptor : oneofDefs.values()) {
    200             block.yieldSpecific(context, oneofDescriptor);
    201         }
    202         return context.runtime.getNil();
    203     }
    204 
    205     /*
    206      * call-seq:
    207      *     Descriptor.lookup_oneof(name) => OneofDescriptor
    208      *
    209      * Returns the oneof descriptor for the oneof with the given name, if present,
    210      * or nil if none.
    211      */
    212     @JRubyMethod(name = "lookup_oneof")
    213     public IRubyObject lookupOneof(ThreadContext context, IRubyObject name) {
    214         if (name instanceof RubySymbol) {
    215             name = ((RubySymbol) name).id2name();
    216         }
    217         return oneofDefs.containsKey(name) ? oneofDefs.get(name) : context.runtime.getNil();
    218     }
    219 
    220     public void setDescriptor(Descriptors.Descriptor descriptor) {
    221         this.descriptor = descriptor;
    222     }
    223 
    224     public Descriptors.Descriptor getDescriptor() {
    225         return this.descriptor;
    226     }
    227 
    228     public DescriptorProtos.DescriptorProto.Builder getBuilder() {
    229         return builder;
    230     }
    231 
    232     public void setMapEntry(boolean isMapEntry) {
    233         this.builder.setOptions(DescriptorProtos.MessageOptions.newBuilder().setMapEntry(isMapEntry));
    234     }
    235 
    236     private RubyModule buildClassFromDescriptor(ThreadContext context) {
    237         Ruby runtime = context.runtime;
    238 
    239         ObjectAllocator allocator = new ObjectAllocator() {
    240             @Override
    241             public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
    242                 return new RubyMessage(runtime, klazz, descriptor);
    243             }
    244         };
    245 
    246         // rb_define_class_id
    247         RubyClass klass = RubyClass.newClass(runtime, runtime.getObject());
    248         klass.setAllocator(allocator);
    249         klass.makeMetaClass(runtime.getObject().getMetaClass());
    250         klass.inherit(runtime.getObject());
    251         RubyModule messageExts = runtime.getClassFromPath("Google::Protobuf::MessageExts");
    252         klass.include(new IRubyObject[] {messageExts});
    253         klass.instance_variable_set(runtime.newString(Utils.DESCRIPTOR_INSTANCE_VAR), this);
    254         klass.defineAnnotatedMethods(RubyMessage.class);
    255         return klass;
    256     }
    257 
    258     protected RubyFieldDescriptor lookup(String fieldName) {
    259         return fieldDefMap.get(Utils.unescapeIdentifier(fieldName));
    260     }
    261 
    262     private IRubyObject name;
    263     private RubyModule klazz;
    264 
    265     private DescriptorProtos.DescriptorProto.Builder builder;
    266     private Descriptors.Descriptor descriptor;
    267     private Map<String, RubyFieldDescriptor> fieldDefMap;
    268     private Map<IRubyObject, RubyOneofDescriptor> oneofDefs;
    269 }
    270