• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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