• 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;
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.Block;
41 import org.jruby.runtime.ObjectAllocator;
42 import org.jruby.runtime.ThreadContext;
43 import org.jruby.runtime.builtin.IRubyObject;
44 
45 import java.util.HashMap;
46 import java.util.Map;
47 
48 
49 @JRubyClass(name = "Descriptor", include = "Enumerable")
50 public class RubyDescriptor extends RubyObject {
createRubyDescriptor(Ruby runtime)51     public static void createRubyDescriptor(Ruby runtime) {
52         RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf");
53         RubyClass cDescriptor = protobuf.defineClassUnder("Descriptor", runtime.getObject(), new ObjectAllocator() {
54             @Override
55             public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
56                 return new RubyDescriptor(runtime, klazz);
57             }
58         });
59         cDescriptor.includeModule(runtime.getEnumerable());
60         cDescriptor.defineAnnotatedMethods(RubyDescriptor.class);
61     }
62 
RubyDescriptor(Ruby runtime, RubyClass klazz)63     public RubyDescriptor(Ruby runtime, RubyClass klazz) {
64         super(runtime, klazz);
65     }
66 
67     /*
68      * call-seq:
69      *     Descriptor.new => descriptor
70      *
71      * Creates a new, empty, message type descriptor. At a minimum, its name must be
72      * set before it is added to a pool. It cannot be used to create messages until
73      * it is added to a pool, after which it becomes immutable (as part of a
74      * finalization process).
75      */
76     @JRubyMethod
initialize(ThreadContext context)77     public IRubyObject initialize(ThreadContext context) {
78         this.builder = DescriptorProtos.DescriptorProto.newBuilder();
79         this.fieldDefMap = new HashMap<String, RubyFieldDescriptor>();
80         this.oneofDefs = new HashMap<IRubyObject, RubyOneofDescriptor>();
81         return this;
82     }
83 
84     /*
85      * call-seq:
86      *     Descriptor.name => name
87      *
88      * Returns the name of this message type as a fully-qualfied string (e.g.,
89      * My.Package.MessageType).
90      */
91     @JRubyMethod(name = "name")
getName(ThreadContext context)92     public IRubyObject getName(ThreadContext context) {
93         return this.name;
94     }
95 
96     /*
97      * call-seq:
98      *    Descriptor.name = name
99      *
100      * Assigns a name to this message type. The descriptor must not have been added
101      * to a pool yet.
102      */
103     @JRubyMethod(name = "name=")
setName(ThreadContext context, IRubyObject name)104     public IRubyObject setName(ThreadContext context, IRubyObject name) {
105         this.name = name;
106         this.builder.setName(Utils.escapeIdentifier(this.name.asJavaString()));
107         return context.runtime.getNil();
108     }
109 
110     /*
111      * call-seq:
112      *     Descriptor.add_field(field) => nil
113      *
114      * Adds the given FieldDescriptor to this message type. The descriptor must not
115      * have been added to a pool yet. Raises an exception if a field with the same
116      * name or number already exists. Sub-type references (e.g. for fields of type
117      * message) are not resolved at this point.
118      */
119     @JRubyMethod(name = "add_field")
addField(ThreadContext context, IRubyObject obj)120     public IRubyObject addField(ThreadContext context, IRubyObject obj) {
121         RubyFieldDescriptor fieldDef = (RubyFieldDescriptor) obj;
122         this.fieldDefMap.put(fieldDef.getName(context).asJavaString(), fieldDef);
123         this.builder.addField(fieldDef.build());
124         return context.runtime.getNil();
125     }
126 
127     /*
128      * call-seq:
129      *     Descriptor.lookup(name) => FieldDescriptor
130      *
131      * Returns the field descriptor for the field with the given name, if present,
132      * or nil if none.
133      */
134     @JRubyMethod
lookup(ThreadContext context, IRubyObject fieldName)135     public IRubyObject lookup(ThreadContext context, IRubyObject fieldName) {
136         return this.fieldDefMap.get(fieldName.asJavaString());
137     }
138 
139     /*
140      * call-seq:
141      *     Descriptor.msgclass => message_klass
142      *
143      * Returns the Ruby class created for this message type. Valid only once the
144      * message type has been added to a pool.
145      */
146     @JRubyMethod
msgclass(ThreadContext context)147     public IRubyObject msgclass(ThreadContext context) {
148         if (this.klazz == null) {
149             this.klazz = buildClassFromDescriptor(context);
150         }
151         return this.klazz;
152     }
153 
154     /*
155      * call-seq:
156      *     Descriptor.each(&block)
157      *
158      * Iterates over fields in this message type, yielding to the block on each one.
159      */
160     @JRubyMethod
each(ThreadContext context, Block block)161     public IRubyObject each(ThreadContext context, Block block) {
162         for (Map.Entry<String, RubyFieldDescriptor> entry : fieldDefMap.entrySet()) {
163             block.yield(context, entry.getValue());
164         }
165         return context.runtime.getNil();
166     }
167 
168     /*
169      * call-seq:
170      *     Descriptor.add_oneof(oneof) => nil
171      *
172      * Adds the given OneofDescriptor to this message type. This descriptor must not
173      * have been added to a pool yet. Raises an exception if a oneof with the same
174      * name already exists, or if any of the oneof's fields' names or numbers
175      * conflict with an existing field in this message type. All fields in the oneof
176      * are added to the message descriptor. Sub-type references (e.g. for fields of
177      * type message) are not resolved at this point.
178      */
179     @JRubyMethod(name = "add_oneof")
addOneof(ThreadContext context, IRubyObject obj)180     public IRubyObject addOneof(ThreadContext context, IRubyObject obj) {
181         RubyOneofDescriptor def = (RubyOneofDescriptor) obj;
182         builder.addOneofDecl(def.build(builder.getOneofDeclCount()));
183         for (RubyFieldDescriptor fieldDescriptor : def.getFields()) {
184             addField(context, fieldDescriptor);
185         }
186         oneofDefs.put(def.getName(context), def);
187         return context.runtime.getNil();
188     }
189 
190     /*
191      * call-seq:
192      *     Descriptor.each_oneof(&block) => nil
193      *
194      * Invokes the given block for each oneof in this message type, passing the
195      * corresponding OneofDescriptor.
196      */
197     @JRubyMethod(name = "each_oneof")
eachOneof(ThreadContext context, Block block)198     public IRubyObject eachOneof(ThreadContext context, Block block) {
199         for (RubyOneofDescriptor oneofDescriptor : oneofDefs.values()) {
200             block.yieldSpecific(context, oneofDescriptor);
201         }
202         return context.runtime.getNil();
203     }
204 
205     /*
206      * call-seq:
207      *     Descriptor.lookup_oneof(name) => OneofDescriptor
208      *
209      * Returns the oneof descriptor for the oneof with the given name, if present,
210      * or nil if none.
211      */
212     @JRubyMethod(name = "lookup_oneof")
lookupOneof(ThreadContext context, IRubyObject name)213     public IRubyObject lookupOneof(ThreadContext context, IRubyObject name) {
214         if (name instanceof RubySymbol) {
215             name = ((RubySymbol) name).id2name();
216         }
217         return oneofDefs.containsKey(name) ? oneofDefs.get(name) : context.runtime.getNil();
218     }
219 
setDescriptor(Descriptors.Descriptor descriptor)220     public void setDescriptor(Descriptors.Descriptor descriptor) {
221         this.descriptor = descriptor;
222     }
223 
getDescriptor()224     public Descriptors.Descriptor getDescriptor() {
225         return this.descriptor;
226     }
227 
getBuilder()228     public DescriptorProtos.DescriptorProto.Builder getBuilder() {
229         return builder;
230     }
231 
setMapEntry(boolean isMapEntry)232     public void setMapEntry(boolean isMapEntry) {
233         this.builder.setOptions(DescriptorProtos.MessageOptions.newBuilder().setMapEntry(isMapEntry));
234     }
235 
buildClassFromDescriptor(ThreadContext context)236     private RubyModule buildClassFromDescriptor(ThreadContext context) {
237         Ruby runtime = context.runtime;
238 
239         ObjectAllocator allocator = new ObjectAllocator() {
240             @Override
241             public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
242                 return new RubyMessage(runtime, klazz, descriptor);
243             }
244         };
245 
246         // rb_define_class_id
247         RubyClass klass = RubyClass.newClass(runtime, runtime.getObject());
248         klass.setAllocator(allocator);
249         klass.makeMetaClass(runtime.getObject().getMetaClass());
250         klass.inherit(runtime.getObject());
251         RubyModule messageExts = runtime.getClassFromPath("Google::Protobuf::MessageExts");
252         klass.include(new IRubyObject[] {messageExts});
253         klass.instance_variable_set(runtime.newString(Utils.DESCRIPTOR_INSTANCE_VAR), this);
254         klass.defineAnnotatedMethods(RubyMessage.class);
255         return klass;
256     }
257 
lookup(String fieldName)258     protected RubyFieldDescriptor lookup(String fieldName) {
259         return fieldDefMap.get(Utils.unescapeIdentifier(fieldName));
260     }
261 
262     private IRubyObject name;
263     private RubyModule klazz;
264 
265     private DescriptorProtos.DescriptorProto.Builder builder;
266     private Descriptors.Descriptor descriptor;
267     private Map<String, RubyFieldDescriptor> fieldDefMap;
268     private Map<IRubyObject, RubyOneofDescriptor> oneofDefs;
269 }
270