| /* |
| * Protocol Buffers - Google's data interchange format |
| * Copyright 2014 Google Inc. All rights reserved. |
| * https://developers.google.com/protocol-buffers/ |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| package com.google.protobuf.jruby; |
| |
| import com.google.protobuf.DescriptorProtos; |
| import com.google.protobuf.Descriptors; |
| import org.jruby.*; |
| import org.jruby.anno.JRubyClass; |
| import org.jruby.anno.JRubyMethod; |
| import org.jruby.runtime.Block; |
| import org.jruby.runtime.ObjectAllocator; |
| import org.jruby.runtime.ThreadContext; |
| import org.jruby.runtime.builtin.IRubyObject; |
| |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| |
| @JRubyClass(name = "Descriptor", include = "Enumerable") |
| public class RubyDescriptor extends RubyObject { |
| public static void createRubyDescriptor(Ruby runtime) { |
| RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf"); |
| RubyClass cDescriptor = protobuf.defineClassUnder("Descriptor", runtime.getObject(), new ObjectAllocator() { |
| @Override |
| public IRubyObject allocate(Ruby runtime, RubyClass klazz) { |
| return new RubyDescriptor(runtime, klazz); |
| } |
| }); |
| cDescriptor.includeModule(runtime.getEnumerable()); |
| cDescriptor.defineAnnotatedMethods(RubyDescriptor.class); |
| } |
| |
| public RubyDescriptor(Ruby runtime, RubyClass klazz) { |
| super(runtime, klazz); |
| } |
| |
| /* |
| * call-seq: |
| * Descriptor.new => descriptor |
| * |
| * Creates a new, empty, message type descriptor. At a minimum, its name must be |
| * set before it is added to a pool. It cannot be used to create messages until |
| * it is added to a pool, after which it becomes immutable (as part of a |
| * finalization process). |
| */ |
| @JRubyMethod |
| public IRubyObject initialize(ThreadContext context) { |
| this.builder = DescriptorProtos.DescriptorProto.newBuilder(); |
| this.fieldDefMap = new HashMap<String, RubyFieldDescriptor>(); |
| this.oneofDefs = new HashMap<IRubyObject, RubyOneofDescriptor>(); |
| return this; |
| } |
| |
| /* |
| * call-seq: |
| * Descriptor.name => name |
| * |
| * Returns the name of this message type as a fully-qualfied string (e.g., |
| * My.Package.MessageType). |
| */ |
| @JRubyMethod(name = "name") |
| public IRubyObject getName(ThreadContext context) { |
| return this.name; |
| } |
| |
| /* |
| * call-seq: |
| * Descriptor.name = name |
| * |
| * Assigns a name to this message type. The descriptor must not have been added |
| * to a pool yet. |
| */ |
| @JRubyMethod(name = "name=") |
| public IRubyObject setName(ThreadContext context, IRubyObject name) { |
| this.name = name; |
| this.builder.setName(Utils.escapeIdentifier(this.name.asJavaString())); |
| return context.runtime.getNil(); |
| } |
| |
| /* |
| * call-seq: |
| * Descriptor.add_field(field) => nil |
| * |
| * Adds the given FieldDescriptor to this message type. The descriptor must not |
| * have been added to a pool yet. Raises an exception if a field with the same |
| * name or number already exists. Sub-type references (e.g. for fields of type |
| * message) are not resolved at this point. |
| */ |
| @JRubyMethod(name = "add_field") |
| public IRubyObject addField(ThreadContext context, IRubyObject obj) { |
| RubyFieldDescriptor fieldDef = (RubyFieldDescriptor) obj; |
| this.fieldDefMap.put(fieldDef.getName(context).asJavaString(), fieldDef); |
| this.builder.addField(fieldDef.build()); |
| return context.runtime.getNil(); |
| } |
| |
| /* |
| * call-seq: |
| * Descriptor.lookup(name) => FieldDescriptor |
| * |
| * Returns the field descriptor for the field with the given name, if present, |
| * or nil if none. |
| */ |
| @JRubyMethod |
| public IRubyObject lookup(ThreadContext context, IRubyObject fieldName) { |
| return this.fieldDefMap.get(fieldName.asJavaString()); |
| } |
| |
| /* |
| * call-seq: |
| * Descriptor.msgclass => message_klass |
| * |
| * Returns the Ruby class created for this message type. Valid only once the |
| * message type has been added to a pool. |
| */ |
| @JRubyMethod |
| public IRubyObject msgclass(ThreadContext context) { |
| if (this.klazz == null) { |
| this.klazz = buildClassFromDescriptor(context); |
| } |
| return this.klazz; |
| } |
| |
| /* |
| * call-seq: |
| * Descriptor.each(&block) |
| * |
| * Iterates over fields in this message type, yielding to the block on each one. |
| */ |
| @JRubyMethod |
| public IRubyObject each(ThreadContext context, Block block) { |
| for (Map.Entry<String, RubyFieldDescriptor> entry : fieldDefMap.entrySet()) { |
| block.yield(context, entry.getValue()); |
| } |
| return context.runtime.getNil(); |
| } |
| |
| /* |
| * call-seq: |
| * Descriptor.add_oneof(oneof) => nil |
| * |
| * Adds the given OneofDescriptor to this message type. This descriptor must not |
| * have been added to a pool yet. Raises an exception if a oneof with the same |
| * name already exists, or if any of the oneof's fields' names or numbers |
| * conflict with an existing field in this message type. All fields in the oneof |
| * are added to the message descriptor. Sub-type references (e.g. for fields of |
| * type message) are not resolved at this point. |
| */ |
| @JRubyMethod(name = "add_oneof") |
| public IRubyObject addOneof(ThreadContext context, IRubyObject obj) { |
| RubyOneofDescriptor def = (RubyOneofDescriptor) obj; |
| builder.addOneofDecl(def.build(builder.getOneofDeclCount())); |
| for (RubyFieldDescriptor fieldDescriptor : def.getFields()) { |
| addField(context, fieldDescriptor); |
| } |
| oneofDefs.put(def.getName(context), def); |
| return context.runtime.getNil(); |
| } |
| |
| /* |
| * call-seq: |
| * Descriptor.each_oneof(&block) => nil |
| * |
| * Invokes the given block for each oneof in this message type, passing the |
| * corresponding OneofDescriptor. |
| */ |
| @JRubyMethod(name = "each_oneof") |
| public IRubyObject eachOneof(ThreadContext context, Block block) { |
| for (RubyOneofDescriptor oneofDescriptor : oneofDefs.values()) { |
| block.yieldSpecific(context, oneofDescriptor); |
| } |
| return context.runtime.getNil(); |
| } |
| |
| /* |
| * call-seq: |
| * Descriptor.lookup_oneof(name) => OneofDescriptor |
| * |
| * Returns the oneof descriptor for the oneof with the given name, if present, |
| * or nil if none. |
| */ |
| @JRubyMethod(name = "lookup_oneof") |
| public IRubyObject lookupOneof(ThreadContext context, IRubyObject name) { |
| if (name instanceof RubySymbol) { |
| name = ((RubySymbol) name).id2name(); |
| } |
| return oneofDefs.containsKey(name) ? oneofDefs.get(name) : context.runtime.getNil(); |
| } |
| |
| public void setDescriptor(Descriptors.Descriptor descriptor) { |
| this.descriptor = descriptor; |
| } |
| |
| public Descriptors.Descriptor getDescriptor() { |
| return this.descriptor; |
| } |
| |
| public DescriptorProtos.DescriptorProto.Builder getBuilder() { |
| return builder; |
| } |
| |
| public void setMapEntry(boolean isMapEntry) { |
| this.builder.setOptions(DescriptorProtos.MessageOptions.newBuilder().setMapEntry(isMapEntry)); |
| } |
| |
| private RubyModule buildClassFromDescriptor(ThreadContext context) { |
| Ruby runtime = context.runtime; |
| |
| ObjectAllocator allocator = new ObjectAllocator() { |
| @Override |
| public IRubyObject allocate(Ruby runtime, RubyClass klazz) { |
| return new RubyMessage(runtime, klazz, descriptor); |
| } |
| }; |
| |
| // rb_define_class_id |
| RubyClass klass = RubyClass.newClass(runtime, runtime.getObject()); |
| klass.setAllocator(allocator); |
| klass.makeMetaClass(runtime.getObject().getMetaClass()); |
| klass.inherit(runtime.getObject()); |
| RubyModule messageExts = runtime.getClassFromPath("Google::Protobuf::MessageExts"); |
| klass.include(new IRubyObject[] {messageExts}); |
| klass.instance_variable_set(runtime.newString(Utils.DESCRIPTOR_INSTANCE_VAR), this); |
| klass.defineAnnotatedMethods(RubyMessage.class); |
| return klass; |
| } |
| |
| protected RubyFieldDescriptor lookup(String fieldName) { |
| return fieldDefMap.get(Utils.unescapeIdentifier(fieldName)); |
| } |
| |
| private IRubyObject name; |
| private RubyModule klazz; |
| |
| private DescriptorProtos.DescriptorProto.Builder builder; |
| private Descriptors.Descriptor descriptor; |
| private Map<String, RubyFieldDescriptor> fieldDefMap; |
| private Map<IRubyObject, RubyOneofDescriptor> oneofDefs; |
| } |