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