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.FileDescriptorProto; 36 import com.google.protobuf.Descriptors.Descriptor; 37 import com.google.protobuf.Descriptors.DescriptorValidationException; 38 import com.google.protobuf.Descriptors.EnumDescriptor; 39 import com.google.protobuf.Descriptors.FieldDescriptor; 40 import com.google.protobuf.Descriptors.FileDescriptor; 41 import com.google.protobuf.Descriptors.ServiceDescriptor; 42 import com.google.protobuf.Descriptors.MethodDescriptor; 43 import com.google.protobuf.ExtensionRegistry; 44 import com.google.protobuf.InvalidProtocolBufferException; 45 import java.util.ArrayList; 46 import java.util.HashMap; 47 import java.util.List; 48 import java.util.Map; 49 import org.jruby.*; 50 import org.jruby.anno.JRubyClass; 51 import org.jruby.anno.JRubyMethod; 52 import org.jruby.exceptions.RaiseException; 53 import org.jruby.runtime.*; 54 import org.jruby.runtime.builtin.IRubyObject; 55 56 @JRubyClass(name = "DescriptorPool") 57 public class RubyDescriptorPool extends RubyObject { createRubyDescriptorPool(Ruby runtime)58 public static void createRubyDescriptorPool(Ruby runtime) { 59 RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf"); 60 RubyClass cDescriptorPool = 61 protobuf.defineClassUnder( 62 "DescriptorPool", 63 runtime.getObject(), 64 new ObjectAllocator() { 65 @Override 66 public IRubyObject allocate(Ruby runtime, RubyClass klazz) { 67 return new RubyDescriptorPool(runtime, klazz); 68 } 69 }); 70 71 cDescriptorPool.defineAnnotatedMethods(RubyDescriptorPool.class); 72 descriptorPool = 73 (RubyDescriptorPool) 74 cDescriptorPool.newInstance(runtime.getCurrentContext(), Block.NULL_BLOCK); 75 cDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Descriptor"); 76 cEnumDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::EnumDescriptor"); 77 cFieldDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::FieldDescriptor"); 78 cServiceDescriptor = 79 (RubyClass) runtime.getClassFromPath("Google::Protobuf::ServiceDescriptor"); 80 cMethodDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::MethodDescriptor"); 81 } 82 RubyDescriptorPool(Ruby runtime, RubyClass klazz)83 public RubyDescriptorPool(Ruby runtime, RubyClass klazz) { 84 super(runtime, klazz); 85 this.fileDescriptors = new ArrayList<>(); 86 this.symtab = new HashMap<IRubyObject, IRubyObject>(); 87 } 88 89 @JRubyMethod build(ThreadContext context, Block block)90 public IRubyObject build(ThreadContext context, Block block) { 91 RubyClass cBuilder = 92 (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::Internal::Builder"); 93 RubyBasicObject ctx = (RubyBasicObject) cBuilder.newInstance(context, this, Block.NULL_BLOCK); 94 ctx.instance_eval(context, block); 95 ctx.callMethod(context, "build"); // Needs to be called to support the deprecated syntax 96 return context.nil; 97 } 98 99 /* 100 * call-seq: 101 * DescriptorPool.lookup(name) => descriptor 102 * 103 * Finds a Descriptor, EnumDescriptor or FieldDescriptor by name and returns it, or nil if none 104 * exists with the given name. 105 * 106 * This currently lazy loads the ruby descriptor objects as they are requested. 107 * This allows us to leave the heavy lifting to the java library 108 */ 109 @JRubyMethod lookup(ThreadContext context, IRubyObject name)110 public IRubyObject lookup(ThreadContext context, IRubyObject name) { 111 return Helpers.nullToNil(symtab.get(name), context.nil); 112 } 113 114 /* 115 * call-seq: 116 * DescriptorPool.generated_pool => descriptor_pool 117 * 118 * Class method that returns the global DescriptorPool. This is a singleton into 119 * which generated-code message and enum types are registered. The user may also 120 * register types in this pool for convenience so that they do not have to hold 121 * a reference to a private pool instance. 122 */ 123 @JRubyMethod(meta = true, name = "generated_pool") generatedPool(ThreadContext context, IRubyObject recv)124 public static IRubyObject generatedPool(ThreadContext context, IRubyObject recv) { 125 return descriptorPool; 126 } 127 128 @JRubyMethod(required = 1) add_serialized_file(ThreadContext context, IRubyObject data)129 public IRubyObject add_serialized_file(ThreadContext context, IRubyObject data) { 130 byte[] bin = data.convertToString().getBytes(); 131 try { 132 FileDescriptorProto.Builder builder = 133 FileDescriptorProto.newBuilder().mergeFrom(bin, registry); 134 registerFileDescriptor(context, builder); 135 } catch (InvalidProtocolBufferException e) { 136 throw RaiseException.from( 137 context.runtime, 138 (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::ParseError"), 139 e.getMessage()); 140 } 141 return context.nil; 142 } 143 registerFileDescriptor( ThreadContext context, FileDescriptorProto.Builder builder)144 protected void registerFileDescriptor( 145 ThreadContext context, FileDescriptorProto.Builder builder) { 146 final FileDescriptor fd; 147 try { 148 fd = FileDescriptor.buildFrom(builder.build(), existingFileDescriptors()); 149 } catch (DescriptorValidationException e) { 150 throw context.runtime.newRuntimeError(e.getMessage()); 151 } 152 153 String packageName = fd.getPackage(); 154 if (!packageName.isEmpty()) { 155 packageName = packageName + "."; 156 } 157 158 // Need to make sure enums are registered first in case anything references them 159 for (EnumDescriptor ed : fd.getEnumTypes()) registerEnumDescriptor(context, ed, packageName); 160 for (Descriptor message : fd.getMessageTypes()) 161 registerDescriptor(context, message, packageName); 162 for (FieldDescriptor fieldDescriptor : fd.getExtensions()) 163 registerExtension(context, fieldDescriptor, packageName); 164 for (ServiceDescriptor serviceDescriptor : fd.getServices()) 165 registerService(context, serviceDescriptor, packageName); 166 167 // Mark this as a loaded file 168 fileDescriptors.add(fd); 169 } 170 registerDescriptor(ThreadContext context, Descriptor descriptor, String parentPath)171 private void registerDescriptor(ThreadContext context, Descriptor descriptor, String parentPath) { 172 String fullName = parentPath + descriptor.getName(); 173 String fullPath = fullName + "."; 174 RubyString name = context.runtime.newString(fullName); 175 176 RubyDescriptor des = (RubyDescriptor) cDescriptor.newInstance(context, Block.NULL_BLOCK); 177 des.setName(name); 178 des.setDescriptor(context, descriptor, this); 179 symtab.put(name, des); 180 181 // Need to make sure enums are registered first in case anything references them 182 for (EnumDescriptor ed : descriptor.getEnumTypes()) 183 registerEnumDescriptor(context, ed, fullPath); 184 for (Descriptor message : descriptor.getNestedTypes()) 185 registerDescriptor(context, message, fullPath); 186 for (FieldDescriptor fieldDescriptor : descriptor.getExtensions()) 187 registerExtension(context, fieldDescriptor, fullPath); 188 } 189 registerExtension( ThreadContext context, FieldDescriptor descriptor, String parentPath)190 private void registerExtension( 191 ThreadContext context, FieldDescriptor descriptor, String parentPath) { 192 if (descriptor.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { 193 registry.add(descriptor, descriptor.toProto()); 194 } else { 195 registry.add(descriptor); 196 } 197 RubyString name = context.runtime.newString(parentPath + descriptor.getName()); 198 RubyFieldDescriptor des = 199 (RubyFieldDescriptor) cFieldDescriptor.newInstance(context, Block.NULL_BLOCK); 200 des.setName(name); 201 des.setDescriptor(context, descriptor, this); 202 // For MessageSet extensions, there is the possibility of a name conflict. Prefer the Message. 203 symtab.putIfAbsent(name, des); 204 } 205 registerEnumDescriptor( ThreadContext context, EnumDescriptor descriptor, String parentPath)206 private void registerEnumDescriptor( 207 ThreadContext context, EnumDescriptor descriptor, String parentPath) { 208 RubyString name = context.runtime.newString(parentPath + descriptor.getName()); 209 RubyEnumDescriptor des = 210 (RubyEnumDescriptor) cEnumDescriptor.newInstance(context, Block.NULL_BLOCK); 211 des.setName(name); 212 des.setDescriptor(context, descriptor); 213 symtab.put(name, des); 214 } 215 registerService( ThreadContext context, ServiceDescriptor descriptor, String parentPath)216 private void registerService( 217 ThreadContext context, ServiceDescriptor descriptor, String parentPath) { 218 String fullName = parentPath + descriptor.getName(); 219 RubyString name = context.runtime.newString(fullName); 220 RubyServiceDescriptor des = 221 (RubyServiceDescriptor) cServiceDescriptor.newInstance(context, Block.NULL_BLOCK); 222 des.setName(name); 223 // n.b. this will also construct the descriptors for the service's methods. 224 des.setDescriptor(context, descriptor, this); 225 symtab.putIfAbsent(name, des); 226 } 227 existingFileDescriptors()228 private FileDescriptor[] existingFileDescriptors() { 229 return fileDescriptors.toArray(new FileDescriptor[fileDescriptors.size()]); 230 } 231 232 private static RubyClass cDescriptor; 233 private static RubyClass cEnumDescriptor; 234 private static RubyClass cFieldDescriptor; 235 private static RubyClass cServiceDescriptor; 236 private static RubyClass cMethodDescriptor; 237 private static RubyDescriptorPool descriptorPool; 238 239 private List<FileDescriptor> fileDescriptors; 240 private Map<IRubyObject, IRubyObject> symtab; 241 protected static final ExtensionRegistry registry = ExtensionRegistry.newInstance(); 242 } 243