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.*;
     41 import org.jruby.runtime.builtin.IRubyObject;
     42 
     43 import java.util.HashMap;
     44 import java.util.Map;
     45 
     46 @JRubyClass(name = "DescriptorPool")
     47 public class RubyDescriptorPool extends RubyObject {
     48     public static void createRubyDescriptorPool(Ruby runtime) {
     49         RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf");
     50         RubyClass cDescriptorPool = protobuf.defineClassUnder("DescriptorPool", runtime.getObject(), new ObjectAllocator() {
     51             @Override
     52             public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
     53                 return new RubyDescriptorPool(runtime, klazz);
     54             }
     55         });
     56 
     57         cDescriptorPool.defineAnnotatedMethods(RubyDescriptorPool.class);
     58         descriptorPool = (RubyDescriptorPool) cDescriptorPool.newInstance(runtime.getCurrentContext(), Block.NULL_BLOCK);
     59     }
     60 
     61     public RubyDescriptorPool(Ruby ruby, RubyClass klazz) {
     62         super(ruby, klazz);
     63     }
     64 
     65     @JRubyMethod
     66     public IRubyObject initialize(ThreadContext context) {
     67         this.symtab = new HashMap<IRubyObject, IRubyObject>();
     68         this.cBuilder = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::Builder");
     69         this.builder = DescriptorProtos.FileDescriptorProto.newBuilder();
     70         return this;
     71     }
     72 
     73     @JRubyMethod
     74     public IRubyObject build(ThreadContext context, Block block) {
     75         RubyBuilder ctx = (RubyBuilder) cBuilder.newInstance(context, Block.NULL_BLOCK);
     76         if (block.arity() == Arity.ONE_ARGUMENT) {
     77             block.yield(context, ctx);
     78         } else {
     79             Binding binding = block.getBinding();
     80             binding.setSelf(ctx);
     81             block.yieldSpecific(context);
     82         }
     83         ctx.finalizeToPool(context, this);
     84         buildFileDescriptor(context);
     85         return context.runtime.getNil();
     86     }
     87 
     88     @JRubyMethod
     89     public IRubyObject lookup(ThreadContext context, IRubyObject name) {
     90         IRubyObject descriptor = this.symtab.get(name);
     91         if (descriptor == null) {
     92             return context.runtime.getNil();
     93         }
     94         return descriptor;
     95     }
     96 
     97     /*
     98      * call-seq:
     99      *     DescriptorPool.generated_pool => descriptor_pool
    100      *
    101      * Class method that returns the global DescriptorPool. This is a singleton into
    102      * which generated-code message and enum types are registered. The user may also
    103      * register types in this pool for convenience so that they do not have to hold
    104      * a reference to a private pool instance.
    105      */
    106     @JRubyMethod(meta = true, name = "generated_pool")
    107     public static IRubyObject generatedPool(ThreadContext context, IRubyObject recv) {
    108         return descriptorPool;
    109     }
    110 
    111     protected void addToSymtab(ThreadContext context, RubyDescriptor def) {
    112         symtab.put(def.getName(context), def);
    113         this.builder.addMessageType(def.getBuilder());
    114     }
    115 
    116     protected void addToSymtab(ThreadContext context, RubyEnumDescriptor def) {
    117         symtab.put(def.getName(context), def);
    118         this.builder.addEnumType(def.getBuilder());
    119     }
    120 
    121     private void buildFileDescriptor(ThreadContext context) {
    122         Ruby runtime = context.runtime;
    123         try {
    124             this.builder.setSyntax("proto3");
    125             final Descriptors.FileDescriptor fileDescriptor = Descriptors.FileDescriptor.buildFrom(
    126                     this.builder.build(), new Descriptors.FileDescriptor[]{});
    127 
    128             for (Descriptors.EnumDescriptor enumDescriptor : fileDescriptor.getEnumTypes()) {
    129                 String enumName = Utils.unescapeIdentifier(enumDescriptor.getName());
    130                 if (enumDescriptor.findValueByNumber(0) == null) {
    131                     throw runtime.newTypeError("Enum definition " + enumName
    132                             + " does not contain a value for '0'");
    133                 }
    134                 ((RubyEnumDescriptor) symtab.get(runtime.newString(enumName)))
    135                         .setDescriptor(enumDescriptor);
    136             }
    137             for (Descriptors.Descriptor descriptor : fileDescriptor.getMessageTypes()) {
    138                 RubyDescriptor rubyDescriptor = ((RubyDescriptor)
    139                         symtab.get(runtime.newString(Utils.unescapeIdentifier(descriptor.getName()))));
    140                 for (Descriptors.FieldDescriptor fieldDescriptor : descriptor.getFields()) {
    141                     if (fieldDescriptor.isRequired()) {
    142                         throw runtime.newTypeError("Required fields are unsupported in proto3");
    143                     }
    144                     RubyFieldDescriptor rubyFieldDescriptor = rubyDescriptor.lookup(fieldDescriptor.getName());
    145                     rubyFieldDescriptor.setFieldDef(fieldDescriptor);
    146                     if (fieldDescriptor.getType() == Descriptors.FieldDescriptor.Type.MESSAGE) {
    147                         RubyDescriptor subType = (RubyDescriptor) lookup(context,
    148                                 runtime.newString(Utils.unescapeIdentifier(fieldDescriptor.getMessageType().getName())));
    149                         rubyFieldDescriptor.setSubType(subType);
    150                     }
    151                     if (fieldDescriptor.getType() == Descriptors.FieldDescriptor.Type.ENUM) {
    152                         RubyEnumDescriptor subType = (RubyEnumDescriptor) lookup(context,
    153                                 runtime.newString(Utils.unescapeIdentifier(fieldDescriptor.getEnumType().getName())));
    154                         rubyFieldDescriptor.setSubType(subType);
    155                     }
    156                 }
    157                 rubyDescriptor.setDescriptor(descriptor);
    158             }
    159         } catch (Descriptors.DescriptorValidationException e) {
    160             throw runtime.newRuntimeError(e.getMessage());
    161         }
    162     }
    163 
    164     private static RubyDescriptorPool descriptorPool;
    165 
    166     private RubyClass cBuilder;
    167     private Map<IRubyObject, IRubyObject> symtab;
    168     private DescriptorProtos.FileDescriptorProto.Builder builder;
    169 }
    170