• 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.CodedInputStream;
36 import com.google.protobuf.Descriptors.Descriptor;
37 import com.google.protobuf.Descriptors.FieldDescriptor;
38 import com.google.protobuf.Descriptors.OneofDescriptor;
39 import java.util.HashMap;
40 import java.util.Map;
41 import org.jruby.*;
42 import org.jruby.anno.JRubyClass;
43 import org.jruby.anno.JRubyMethod;
44 import org.jruby.runtime.Block;
45 import org.jruby.runtime.Helpers;
46 import org.jruby.runtime.ObjectAllocator;
47 import org.jruby.runtime.ThreadContext;
48 import org.jruby.runtime.builtin.IRubyObject;
49 
50 @JRubyClass(name = "Descriptor", include = "Enumerable")
51 public class RubyDescriptor extends RubyObject {
createRubyDescriptor(Ruby runtime)52   public static void createRubyDescriptor(Ruby runtime) {
53     RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf");
54     RubyClass cDescriptor =
55         protobuf.defineClassUnder(
56             "Descriptor",
57             runtime.getObject(),
58             new ObjectAllocator() {
59               @Override
60               public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
61                 return new RubyDescriptor(runtime, klazz);
62               }
63             });
64     cDescriptor.includeModule(runtime.getEnumerable());
65     cDescriptor.defineAnnotatedMethods(RubyDescriptor.class);
66     cFieldDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::FieldDescriptor");
67     cOneofDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::OneofDescriptor");
68   }
69 
RubyDescriptor(Ruby runtime, RubyClass klazz)70   public RubyDescriptor(Ruby runtime, RubyClass klazz) {
71     super(runtime, klazz);
72   }
73 
74   /*
75    * call-seq:
76    *     Descriptor.name => name
77    *
78    * Returns the name of this message type as a fully-qualified string (e.g.,
79    * My.Package.MessageType).
80    */
81   @JRubyMethod(name = "name")
getName(ThreadContext context)82   public IRubyObject getName(ThreadContext context) {
83     return name;
84   }
85 
86   /*
87    * call-seq:
88    *     Descriptor.lookup(name) => FieldDescriptor
89    *
90    * Returns the field descriptor for the field with the given name, if present,
91    * or nil if none.
92    */
93   @JRubyMethod
lookup(ThreadContext context, IRubyObject fieldName)94   public IRubyObject lookup(ThreadContext context, IRubyObject fieldName) {
95     return Helpers.nullToNil(fieldDescriptors.get(fieldName), context.nil);
96   }
97 
98   /*
99    * call-seq:
100    *     Descriptor.msgclass => message_klass
101    *
102    * Returns the Ruby class created for this message type. Valid only once the
103    * message type has been added to a pool.
104    */
105   @JRubyMethod
msgclass(ThreadContext context)106   public IRubyObject msgclass(ThreadContext context) {
107     return klazz;
108   }
109 
110   /*
111    * call-seq:
112    *     Descriptor.each(&block)
113    *
114    * Iterates over fields in this message type, yielding to the block on each one.
115    */
116   @JRubyMethod
each(ThreadContext context, Block block)117   public IRubyObject each(ThreadContext context, Block block) {
118     for (Map.Entry<IRubyObject, RubyFieldDescriptor> entry : fieldDescriptors.entrySet()) {
119       block.yield(context, entry.getValue());
120     }
121     return context.nil;
122   }
123 
124   /*
125    * call-seq:
126    *    Descriptor.file_descriptor
127    *
128    * Returns the FileDescriptor object this message belongs to.
129    */
130   @JRubyMethod(name = "file_descriptor")
getFileDescriptor(ThreadContext context)131   public IRubyObject getFileDescriptor(ThreadContext context) {
132     return RubyFileDescriptor.getRubyFileDescriptor(context, descriptor);
133   }
134 
135   /*
136    * call-seq:
137    *     Descriptor.each_oneof(&block) => nil
138    *
139    * Invokes the given block for each oneof in this message type, passing the
140    * corresponding OneofDescriptor.
141    */
142   @JRubyMethod(name = "each_oneof")
eachOneof(ThreadContext context, Block block)143   public IRubyObject eachOneof(ThreadContext context, Block block) {
144     for (RubyOneofDescriptor oneofDescriptor : oneofDescriptors.values()) {
145       block.yieldSpecific(context, oneofDescriptor);
146     }
147     return context.nil;
148   }
149 
150   /*
151    * call-seq:
152    *     Descriptor.lookup_oneof(name) => OneofDescriptor
153    *
154    * Returns the oneof descriptor for the oneof with the given name, if present,
155    * or nil if none.
156    */
157   @JRubyMethod(name = "lookup_oneof")
lookupOneof(ThreadContext context, IRubyObject name)158   public IRubyObject lookupOneof(ThreadContext context, IRubyObject name) {
159     return Helpers.nullToNil(oneofDescriptors.get(Utils.symToString(name)), context.nil);
160   }
161 
162   @JRubyMethod
options(ThreadContext context)163   public IRubyObject options(ThreadContext context) {
164     RubyDescriptorPool pool = (RubyDescriptorPool) RubyDescriptorPool.generatedPool(null, null);
165     RubyDescriptor messageOptionsDescriptor =
166         (RubyDescriptor)
167             pool.lookup(context, context.runtime.newString("google.protobuf.MessageOptions"));
168     RubyClass messageOptionsClass = (RubyClass) messageOptionsDescriptor.msgclass(context);
169     RubyMessage msg = (RubyMessage) messageOptionsClass.newInstance(context, Block.NULL_BLOCK);
170     return msg.decodeBytes(
171         context,
172         msg,
173         CodedInputStream.newInstance(
174             descriptor.getOptions().toByteString().toByteArray()), /*freeze*/
175         true);
176   }
177 
getField(String name)178   protected FieldDescriptor getField(String name) {
179     return descriptor.findFieldByName(name);
180   }
181 
setDescriptor( ThreadContext context, Descriptor descriptor, RubyDescriptorPool pool)182   protected void setDescriptor(
183       ThreadContext context, Descriptor descriptor, RubyDescriptorPool pool) {
184     Ruby runtime = context.runtime;
185     Map<FieldDescriptor, RubyFieldDescriptor> cache = new HashMap();
186     this.descriptor = descriptor;
187 
188     // Populate the field caches
189     fieldDescriptors = new HashMap<IRubyObject, RubyFieldDescriptor>();
190     oneofDescriptors = new HashMap<IRubyObject, RubyOneofDescriptor>();
191 
192     for (FieldDescriptor fieldDescriptor : descriptor.getFields()) {
193       RubyFieldDescriptor fd =
194           (RubyFieldDescriptor) cFieldDescriptor.newInstance(context, Block.NULL_BLOCK);
195       fd.setDescriptor(context, fieldDescriptor, pool);
196       fieldDescriptors.put(runtime.newString(fieldDescriptor.getName()), fd);
197       cache.put(fieldDescriptor, fd);
198     }
199 
200     for (OneofDescriptor oneofDescriptor : descriptor.getRealOneofs()) {
201       RubyOneofDescriptor ood =
202           (RubyOneofDescriptor) cOneofDescriptor.newInstance(context, Block.NULL_BLOCK);
203       ood.setDescriptor(context, oneofDescriptor, cache);
204       oneofDescriptors.put(runtime.newString(oneofDescriptor.getName()), ood);
205     }
206 
207     // Make sure our class is built
208     this.klazz = buildClassFromDescriptor(context);
209   }
210 
setName(IRubyObject name)211   protected void setName(IRubyObject name) {
212     this.name = name;
213   }
214 
buildClassFromDescriptor(ThreadContext context)215   private RubyClass buildClassFromDescriptor(ThreadContext context) {
216     Ruby runtime = context.runtime;
217 
218     ObjectAllocator allocator =
219         new ObjectAllocator() {
220           @Override
221           public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
222             return new RubyMessage(runtime, klazz, descriptor);
223           }
224         };
225 
226     // rb_define_class_id
227     RubyClass klass = RubyClass.newClass(runtime, runtime.getObject());
228     klass.setAllocator(allocator);
229     klass.makeMetaClass(runtime.getObject().getMetaClass());
230     klass.inherit(runtime.getObject());
231     RubyModule messageExts = runtime.getClassFromPath("Google::Protobuf::MessageExts");
232     klass.include(new IRubyObject[] {messageExts});
233     klass.instance_variable_set(runtime.newString(Utils.DESCRIPTOR_INSTANCE_VAR), this);
234     klass.defineAnnotatedMethods(RubyMessage.class);
235     // Workaround for https://github.com/jruby/jruby/issues/7154
236     klass.searchMethod("respond_to?").setIsBuiltin(false);
237     return klass;
238   }
239 
240   private static RubyClass cFieldDescriptor;
241   private static RubyClass cOneofDescriptor;
242 
243   private Descriptor descriptor;
244   private IRubyObject name;
245   private Map<IRubyObject, RubyFieldDescriptor> fieldDescriptors;
246   private Map<IRubyObject, RubyOneofDescriptor> oneofDescriptors;
247   private RubyClass klazz;
248 }
249