• 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.FieldDescriptor;
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 @JRubyClass(name = "FieldDescriptor")
46 public class RubyFieldDescriptor extends RubyObject {
createRubyFieldDescriptor(Ruby runtime)47   public static void createRubyFieldDescriptor(Ruby runtime) {
48     RubyModule mProtobuf = runtime.getClassFromPath("Google::Protobuf");
49     RubyClass cFieldDescriptor =
50         mProtobuf.defineClassUnder(
51             "FieldDescriptor",
52             runtime.getObject(),
53             new ObjectAllocator() {
54               @Override
55               public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
56                 return new RubyFieldDescriptor(runtime, klazz);
57               }
58             });
59     cFieldDescriptor.defineAnnotatedMethods(RubyFieldDescriptor.class);
60   }
61 
RubyFieldDescriptor(Ruby runtime, RubyClass klazz)62   public RubyFieldDescriptor(Ruby runtime, RubyClass klazz) {
63     super(runtime, klazz);
64   }
65 
66   /*
67    * call-seq:
68    *     FieldDescriptor.default => default
69    *
70    * Returns this field's default, as a Ruby object, or nil if not yet set.
71    */
72   // VALUE FieldDescriptor_default(VALUE _self) {
73   //   DEFINE_SELF(FieldDescriptor, self, _self);
74   //   return layout_get_default(self->fielddef);
75   // }
76 
77   /*
78    * call-seq:
79    *     FieldDescriptor.label => label
80    *
81    * Returns this field's label (i.e., plurality), as a Ruby symbol.
82    *
83    * Valid field labels are:
84    *     :optional, :repeated
85    */
86   @JRubyMethod(name = "label")
getLabel(ThreadContext context)87   public IRubyObject getLabel(ThreadContext context) {
88     if (label == null) {
89       calculateLabel(context);
90     }
91     return label;
92   }
93 
94   /*
95    * call-seq:
96    *     FieldDescriptor.has_presence? => bool
97    *
98    * Returns whether this field tracks presence.
99    */
100   @JRubyMethod(name = "has_presence?")
hasPresence(ThreadContext context)101   public IRubyObject hasPresence(ThreadContext context) {
102     return this.descriptor.hasPresence() ? context.runtime.getTrue() : context.runtime.getFalse();
103   }
104 
105   /*
106    * call-seq:
107    *     FieldDescriptor.is_packed? => bool
108    *
109    * Returns whether this is a repeated field that uses packed encoding.
110    */
111   @JRubyMethod(name = "is_packed?")
isPacked(ThreadContext context)112   public IRubyObject isPacked(ThreadContext context) {
113     return this.descriptor.isPacked() ? context.runtime.getTrue() : context.runtime.getFalse();
114   }
115 
116   /*
117    * call-seq:
118    *     FieldDescriptor.name => name
119    *
120    * Returns the name of this field as a Ruby String, or nil if it is not set.
121    */
122   @JRubyMethod(name = "name")
getName(ThreadContext context)123   public IRubyObject getName(ThreadContext context) {
124     return this.name;
125   }
126 
setName(IRubyObject name)127   protected void setName(IRubyObject name) {
128     this.name = name;
129   }
130 
131   /*
132    * call-seq:
133    *     FieldDescriptor.subtype => message_or_enum_descriptor
134    *
135    * Returns the message or enum descriptor corresponding to this field's type if
136    * it is a message or enum field, respectively, or nil otherwise. Cannot be
137    * called *until* the containing message type is added to a pool (and thus
138    * resolved).
139    */
140   @JRubyMethod(name = "subtype")
getSubtype(ThreadContext context)141   public IRubyObject getSubtype(ThreadContext context) {
142     if (subtype == null) {
143       calculateSubtype(context);
144     }
145     return subtype;
146   }
147 
148   /*
149    * call-seq:
150    *     FieldDescriptor.type => type
151    *
152    * Returns this field's type, as a Ruby symbol, or nil if not yet set.
153    *
154    * Valid field types are:
155    *     :int32, :int64, :uint32, :uint64, :float, :double, :bool, :string,
156    *     :bytes, :message.
157    */
158   @JRubyMethod(name = "type")
getType(ThreadContext context)159   public IRubyObject getType(ThreadContext context) {
160     return Utils.fieldTypeToRuby(context, descriptor.getType());
161   }
162 
163   /*
164    * call-seq:
165    *     FieldDescriptor.number => number
166    *
167    * Returns the tag number for this field.
168    */
169   @JRubyMethod(name = "number")
getNumber(ThreadContext context)170   public IRubyObject getNumber(ThreadContext context) {
171     return this.number;
172   }
173 
174   /*
175    * call-seq:
176    *     FieldDescriptor.submsg_name => submsg_name
177    *
178    * Returns the name of the message or enum type corresponding to this field, if
179    * it is a message or enum field (respectively), or nil otherwise. This type
180    * name will be resolved within the context of the pool to which the containing
181    * message type is added.
182    */
183   // VALUE FieldDescriptor_submsg_name(VALUE _self) {
184   //   DEFINE_SELF(FieldDescriptor, self, _self);
185   //   switch (upb_fielddef_type(self->fielddef)) {
186   //     case UPB_TYPE_ENUM:
187   //       return rb_str_new2(
188   //           upb_enumdef_fullname(upb_fielddef_enumsubdef(self->fielddef)));
189   //     case UPB_TYPE_MESSAGE:
190   //       return rb_str_new2(
191   //           upb_msgdef_fullname(upb_fielddef_msgsubdef(self->fielddef)));
192   //     default:
193   //       return Qnil;
194   //   }
195   // }
196   /*
197    * call-seq:
198    *     FieldDescriptor.submsg_name = submsg_name
199    *
200    * Sets the name of the message or enum type corresponding to this field, if it
201    * is a message or enum field (respectively). This type name will be resolved
202    * within the context of the pool to which the containing message type is added.
203    * Cannot be called on field that are not of message or enum type, or on fields
204    * that are part of a message type already added to a pool.
205    */
206   // @JRubyMethod(name = "submsg_name=")
207   // public IRubyObject setSubmsgName(ThreadContext context, IRubyObject name) {
208   //     this.builder.setTypeName("." + Utils.escapeIdentifier(name.asJavaString()));
209   //     return context.runtime.getNil();
210   // }
211 
212   /*
213    * call-seq:
214    *     FieldDescriptor.clear(message)
215    *
216    * Clears the field from the message if it's set.
217    */
218   @JRubyMethod(name = "clear")
clearValue(ThreadContext context, IRubyObject message)219   public IRubyObject clearValue(ThreadContext context, IRubyObject message) {
220     return ((RubyMessage) message).clearField(context, descriptor);
221   }
222 
223   /*
224    * call-seq:
225    *     FieldDescriptor.get(message) => value
226    *
227    * Returns the value set for this field on the given message. Raises an
228    * exception if message is of the wrong type.
229    */
230   @JRubyMethod(name = "get")
getValue(ThreadContext context, IRubyObject message)231   public IRubyObject getValue(ThreadContext context, IRubyObject message) {
232     return ((RubyMessage) message).getField(context, descriptor);
233   }
234 
235   /*
236    * call-seq:
237    *     FieldDescriptor.has?(message) => boolean
238    *
239    * Returns whether the value is set on the given message. Raises an
240    * exception when calling for fields that do not have presence.
241    */
242   @JRubyMethod(name = "has?")
has(ThreadContext context, IRubyObject message)243   public IRubyObject has(ThreadContext context, IRubyObject message) {
244     return ((RubyMessage) message).hasField(context, descriptor);
245   }
246 
247   /*
248    * call-seq:
249    *     FieldDescriptor.set(message, value)
250    *
251    * Sets the value corresponding to this field to the given value on the given
252    * message. Raises an exception if message is of the wrong type. Performs the
253    * ordinary type-checks for field setting.
254    */
255   @JRubyMethod(name = "set")
setValue(ThreadContext context, IRubyObject message, IRubyObject value)256   public IRubyObject setValue(ThreadContext context, IRubyObject message, IRubyObject value) {
257     ((RubyMessage) message).setField(context, this, value);
258     return context.nil;
259   }
260 
261   @JRubyMethod
options(ThreadContext context)262   public IRubyObject options(ThreadContext context) {
263     RubyDescriptor fieldOptionsDescriptor =
264         (RubyDescriptor)
265             pool.lookup(context, context.runtime.newString("google.protobuf.FieldOptions"));
266     RubyClass fieldOptionsClass = (RubyClass) fieldOptionsDescriptor.msgclass(context);
267     RubyMessage msg = (RubyMessage) fieldOptionsClass.newInstance(context, Block.NULL_BLOCK);
268     return msg.decodeBytes(
269         context,
270         msg,
271         CodedInputStream.newInstance(
272             descriptor.getOptions().toByteString().toByteArray()), /*freeze*/
273         true);
274   }
275 
setDescriptor( ThreadContext context, FieldDescriptor descriptor, RubyDescriptorPool pool)276   protected void setDescriptor(
277       ThreadContext context, FieldDescriptor descriptor, RubyDescriptorPool pool) {
278     this.descriptor = descriptor;
279     this.name = context.runtime.newString(descriptor.getName());
280     this.pool = pool;
281   }
282 
getDescriptor()283   protected FieldDescriptor getDescriptor() {
284     return descriptor;
285   }
286 
calculateLabel(ThreadContext context)287   private void calculateLabel(ThreadContext context) {
288     if (descriptor.isRepeated()) {
289       this.label = context.runtime.newSymbol("repeated");
290     } else if (descriptor.isRequired()) {
291       this.label = context.runtime.newSymbol("required");
292     } else if (descriptor.isOptional()) {
293       this.label = context.runtime.newSymbol("optional");
294     } else {
295       this.label = context.nil;
296     }
297   }
298 
calculateSubtype(ThreadContext context)299   private void calculateSubtype(ThreadContext context) {
300     FieldDescriptor.Type fdType = descriptor.getType();
301     if (fdType == FieldDescriptor.Type.MESSAGE) {
302       RubyString messageName = context.runtime.newString(descriptor.getMessageType().getFullName());
303       this.subtype = pool.lookup(context, messageName);
304     } else if (fdType == FieldDescriptor.Type.ENUM) {
305       RubyString enumName = context.runtime.newString(descriptor.getEnumType().getFullName());
306       this.subtype = pool.lookup(context, enumName);
307     } else {
308       this.subtype = context.nil;
309     }
310   }
311 
312   private static final String DOT = ".";
313 
314   private FieldDescriptor descriptor;
315   private IRubyObject name;
316   private IRubyObject label;
317   private IRubyObject number;
318   private IRubyObject subtype;
319   private RubyDescriptorPool pool;
320 }
321