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