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.Descriptors; 36 import org.jruby.*; 37 import org.jruby.anno.JRubyClass; 38 import org.jruby.anno.JRubyMethod; 39 import org.jruby.runtime.Binding; 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 = "MessageBuilderContext") 46 public class RubyMessageBuilderContext extends RubyObject { createRubyMessageBuilderContext(Ruby runtime)47 public static void createRubyMessageBuilderContext(Ruby runtime) { 48 RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf"); 49 RubyClass cMessageBuilderContext = protobuf.defineClassUnder("MessageBuilderContext", runtime.getObject(), new ObjectAllocator() { 50 @Override 51 public IRubyObject allocate(Ruby runtime, RubyClass klazz) { 52 return new RubyMessageBuilderContext(runtime, klazz); 53 } 54 }); 55 cMessageBuilderContext.defineAnnotatedMethods(RubyMessageBuilderContext.class); 56 } 57 RubyMessageBuilderContext(Ruby ruby, RubyClass klazz)58 public RubyMessageBuilderContext(Ruby ruby, RubyClass klazz) { 59 super(ruby, klazz); 60 } 61 62 @JRubyMethod initialize(ThreadContext context, IRubyObject descriptor, IRubyObject rubyBuilder)63 public IRubyObject initialize(ThreadContext context, IRubyObject descriptor, IRubyObject rubyBuilder) { 64 this.cFieldDescriptor = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::FieldDescriptor"); 65 this.cDescriptor = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::Descriptor"); 66 this.cOneofDescriptor = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::OneofDescriptor"); 67 this.cOneofBuilderContext = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::Internal::OneofBuilderContext"); 68 this.descriptor = (RubyDescriptor) descriptor; 69 this.builder = (RubyBuilder) rubyBuilder; 70 return this; 71 } 72 73 /* 74 * call-seq: 75 * MessageBuilderContext.optional(name, type, number, type_class = nil) 76 * 77 * Defines a new optional field on this message type with the given type, tag 78 * number, and type class (for message and enum fields). The type must be a Ruby 79 * symbol (as accepted by FieldDescriptor#type=) and the type_class must be a 80 * string, if present (as accepted by FieldDescriptor#submsg_name=). 81 */ 82 @JRubyMethod(required = 3, optional = 1) optional(ThreadContext context, IRubyObject[] args)83 public IRubyObject optional(ThreadContext context, IRubyObject[] args) { 84 Ruby runtime = context.runtime; 85 IRubyObject typeClass = runtime.getNil(); 86 if (args.length > 3) typeClass = args[3]; 87 msgdefAddField(context, "optional", args[0], args[1], args[2], typeClass); 88 return context.runtime.getNil(); 89 } 90 91 /* 92 * call-seq: 93 * MessageBuilderContext.required(name, type, number, type_class = nil) 94 * 95 * Defines a new required field on this message type with the given type, tag 96 * number, and type class (for message and enum fields). The type must be a Ruby 97 * symbol (as accepted by FieldDescriptor#type=) and the type_class must be a 98 * string, if present (as accepted by FieldDescriptor#submsg_name=). 99 * 100 * Proto3 does not have required fields, but this method exists for 101 * completeness. Any attempt to add a message type with required fields to a 102 * pool will currently result in an error. 103 */ 104 @JRubyMethod(required = 3, optional = 1) required(ThreadContext context, IRubyObject[] args)105 public IRubyObject required(ThreadContext context, IRubyObject[] args) { 106 IRubyObject typeClass = context.runtime.getNil(); 107 if (args.length > 3) typeClass = args[3]; 108 msgdefAddField(context, "required", args[0], args[1], args[2], typeClass); 109 return context.runtime.getNil(); 110 } 111 112 /* 113 * call-seq: 114 * MessageBuilderContext.repeated(name, type, number, type_class = nil) 115 * 116 * Defines a new repeated field on this message type with the given type, tag 117 * number, and type class (for message and enum fields). The type must be a Ruby 118 * symbol (as accepted by FieldDescriptor#type=) and the type_class must be a 119 * string, if present (as accepted by FieldDescriptor#submsg_name=). 120 */ 121 @JRubyMethod(required = 3, optional = 1) repeated(ThreadContext context, IRubyObject[] args)122 public IRubyObject repeated(ThreadContext context, IRubyObject[] args) { 123 IRubyObject typeClass = context.runtime.getNil(); 124 if (args.length > 3) typeClass = args[3]; 125 msgdefAddField(context, "repeated", args[0], args[1], args[2], typeClass); 126 return context.runtime.getNil(); 127 } 128 129 /* 130 * call-seq: 131 * MessageBuilderContext.map(name, key_type, value_type, number, 132 * value_type_class = nil) 133 * 134 * Defines a new map field on this message type with the given key and value 135 * types, tag number, and type class (for message and enum value types). The key 136 * type must be :int32/:uint32/:int64/:uint64, :bool, or :string. The value type 137 * type must be a Ruby symbol (as accepted by FieldDescriptor#type=) and the 138 * type_class must be a string, if present (as accepted by 139 * FieldDescriptor#submsg_name=). 140 */ 141 @JRubyMethod(required = 4, optional = 1) map(ThreadContext context, IRubyObject[] args)142 public IRubyObject map(ThreadContext context, IRubyObject[] args) { 143 Ruby runtime = context.runtime; 144 IRubyObject name = args[0]; 145 IRubyObject keyType = args[1]; 146 IRubyObject valueType = args[2]; 147 IRubyObject number = args[3]; 148 IRubyObject typeClass = args.length > 4 ? args[4] : context.runtime.getNil(); 149 150 // Validate the key type. We can't accept enums, messages, or floats/doubles 151 // as map keys. (We exclude these explicitly, and the field-descriptor setter 152 // below then ensures that the type is one of the remaining valid options.) 153 if (keyType.equals(RubySymbol.newSymbol(runtime, "float")) || 154 keyType.equals(RubySymbol.newSymbol(runtime, "double")) || 155 keyType.equals(RubySymbol.newSymbol(runtime, "enum")) || 156 keyType.equals(RubySymbol.newSymbol(runtime, "message"))) 157 throw runtime.newArgumentError("Cannot add a map field with a float, double, enum, or message type."); 158 159 // Create a new message descriptor for the map entry message, and create a 160 // repeated submessage field here with that type. 161 RubyDescriptor mapentryDesc = (RubyDescriptor) cDescriptor.newInstance(context, Block.NULL_BLOCK); 162 IRubyObject mapentryDescName = RubySymbol.newSymbol(runtime, name).id2name(context); 163 mapentryDesc.setName(context, mapentryDescName); 164 mapentryDesc.setMapEntry(true); 165 166 //optional <type> key = 1; 167 RubyFieldDescriptor keyField = (RubyFieldDescriptor) cFieldDescriptor.newInstance(context, Block.NULL_BLOCK); 168 keyField.setName(context, runtime.newString("key")); 169 keyField.setLabel(context, RubySymbol.newSymbol(runtime, "optional")); 170 keyField.setNumber(context, runtime.newFixnum(1)); 171 keyField.setType(context, keyType); 172 mapentryDesc.addField(context, keyField); 173 174 //optional <type> value = 2; 175 RubyFieldDescriptor valueField = (RubyFieldDescriptor) cFieldDescriptor.newInstance(context, Block.NULL_BLOCK); 176 valueField.setName(context, runtime.newString("value")); 177 valueField.setLabel(context, RubySymbol.newSymbol(runtime, "optional")); 178 valueField.setNumber(context, runtime.newFixnum(2)); 179 valueField.setType(context, valueType); 180 if (! typeClass.isNil()) valueField.setSubmsgName(context, typeClass); 181 mapentryDesc.addField(context, valueField); 182 183 // Add the map-entry message type to the current builder, and use the type to 184 // create the map field itself. 185 this.builder.pendingList.add(mapentryDesc); 186 187 msgdefAddField(context, "repeated", name, runtime.newSymbol("message"), number, mapentryDescName); 188 return runtime.getNil(); 189 } 190 191 @JRubyMethod oneof(ThreadContext context, IRubyObject name, Block block)192 public IRubyObject oneof(ThreadContext context, IRubyObject name, Block block) { 193 RubyOneofDescriptor oneofdef = (RubyOneofDescriptor) 194 cOneofDescriptor.newInstance(context, Block.NULL_BLOCK); 195 RubyOneofBuilderContext ctx = (RubyOneofBuilderContext) 196 cOneofBuilderContext.newInstance(context, oneofdef, Block.NULL_BLOCK); 197 oneofdef.setName(context, name); 198 Binding binding = block.getBinding(); 199 binding.setSelf(ctx); 200 block.yieldSpecific(context); 201 descriptor.addOneof(context, oneofdef); 202 return context.runtime.getNil(); 203 } 204 msgdefAddField(ThreadContext context, String label, IRubyObject name, IRubyObject type, IRubyObject number, IRubyObject typeClass)205 private void msgdefAddField(ThreadContext context, String label, IRubyObject name, 206 IRubyObject type, IRubyObject number, IRubyObject typeClass) { 207 descriptor.addField(context, 208 Utils.msgdefCreateField(context, label, name, type, number, typeClass, cFieldDescriptor)); 209 } 210 211 private RubyDescriptor descriptor; 212 private RubyBuilder builder; 213 private RubyClass cFieldDescriptor; 214 private RubyClass cOneofDescriptor; 215 private RubyClass cOneofBuilderContext; 216 private RubyClass cDescriptor; 217 } 218