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 { createRubyDescriptorPool(Ruby runtime)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 RubyDescriptorPool(Ruby ruby, RubyClass klazz)61 public RubyDescriptorPool(Ruby ruby, RubyClass klazz) { 62 super(ruby, klazz); 63 } 64 65 @JRubyMethod initialize(ThreadContext context)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 build(ThreadContext context, Block block)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 lookup(ThreadContext context, IRubyObject name)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") generatedPool(ThreadContext context, IRubyObject recv)107 public static IRubyObject generatedPool(ThreadContext context, IRubyObject recv) { 108 return descriptorPool; 109 } 110 addToSymtab(ThreadContext context, RubyDescriptor def)111 protected void addToSymtab(ThreadContext context, RubyDescriptor def) { 112 symtab.put(def.getName(context), def); 113 this.builder.addMessageType(def.getBuilder()); 114 } 115 addToSymtab(ThreadContext context, RubyEnumDescriptor def)116 protected void addToSymtab(ThreadContext context, RubyEnumDescriptor def) { 117 symtab.put(def.getName(context), def); 118 this.builder.addEnumType(def.getBuilder()); 119 } 120 buildFileDescriptor(ThreadContext context)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