content: /*
   * 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.*;
  import org.jruby.runtime.builtin.IRubyObject;
  
  import java.util.HashMap;
  import java.util.Map;
  
  @JRubyClass(name = "DescriptorPool")
  public class RubyDescriptorPool extends RubyObject {
      public static void createRubyDescriptorPool(Ruby runtime) {
          RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf");
          RubyClass cDescriptorPool = protobuf.defineClassUnder("DescriptorPool", runtime.getObject(), new ObjectAllocator() {
              @Override
              public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
                  return new RubyDescriptorPool(runtime, klazz);
              }
          });
  
          cDescriptorPool.defineAnnotatedMethods(RubyDescriptorPool.class);
          descriptorPool = (RubyDescriptorPool) cDescriptorPool.newInstance(runtime.getCurrentContext(), Block.NULL_BLOCK);
      }
  
      public RubyDescriptorPool(Ruby ruby, RubyClass klazz) {
          super(ruby, klazz);
      }
  
      @JRubyMethod
      public IRubyObject initialize(ThreadContext context) {
          this.symtab = new HashMap<IRubyObject, IRubyObject>();
          this.cBuilder = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::Builder");
          this.builder = DescriptorProtos.FileDescriptorProto.newBuilder();
          return this;
      }
  
      @JRubyMethod
      public IRubyObject build(ThreadContext context, Block block) {
          RubyBuilder ctx = (RubyBuilder) cBuilder.newInstance(context, Block.NULL_BLOCK);
          if (block.arity() == Arity.ONE_ARGUMENT) {
              block.yield(context, ctx);
          } else {
              Binding binding = block.getBinding();
              binding.setSelf(ctx);
              block.yieldSpecific(context);
          }
          ctx.finalizeToPool(context, this);
          buildFileDescriptor(context);
          return context.runtime.getNil();
      }
  
      @JRubyMethod
      public IRubyObject lookup(ThreadContext context, IRubyObject name) {
          IRubyObject descriptor = this.symtab.get(name);
          if (descriptor == null) {
              return context.runtime.getNil();
          }
          return descriptor;
      }
  
      /*
       * call-seq:
       *     DescriptorPool.generated_pool => descriptor_pool
       *
       * Class method that returns the global DescriptorPool. This is a singleton into
       * which generated-code message and enum types are registered. The user may also
       * register types in this pool for convenience so that they do not have to hold
       * a reference to a private pool instance.
       */
      @JRubyMethod(meta = true, name = "generated_pool")
      public static IRubyObject generatedPool(ThreadContext context, IRubyObject recv) {
          return descriptorPool;
      }
  
      protected void addToSymtab(ThreadContext context, RubyDescriptor def) {
          symtab.put(def.getName(context), def);
          this.builder.addMessageType(def.getBuilder());
      }
  
      protected void addToSymtab(ThreadContext context, RubyEnumDescriptor def) {
          symtab.put(def.getName(context), def);
          this.builder.addEnumType(def.getBuilder());
      }
  
      private void buildFileDescriptor(ThreadContext context) {
          Ruby runtime = context.runtime;
          try {
              this.builder.setSyntax("proto3");
              final Descriptors.FileDescriptor fileDescriptor = Descriptors.FileDescriptor.buildFrom(
                      this.builder.build(), new Descriptors.FileDescriptor[]{});
  
              for (Descriptors.EnumDescriptor enumDescriptor : fileDescriptor.getEnumTypes()) {
                  String enumName = Utils.unescapeIdentifier(enumDescriptor.getName());
                  if (enumDescriptor.findValueByNumber(0) == null) {
                      throw runtime.newTypeError("Enum definition " + enumName
                              + " does not contain a value for '0'");
                  }
                  ((RubyEnumDescriptor) symtab.get(runtime.newString(enumName)))
                          .setDescriptor(enumDescriptor);
              }
              for (Descriptors.Descriptor descriptor : fileDescriptor.getMessageTypes()) {
                  RubyDescriptor rubyDescriptor = ((RubyDescriptor)
                          symtab.get(runtime.newString(Utils.unescapeIdentifier(descriptor.getName()))));
                  for (Descriptors.FieldDescriptor fieldDescriptor : descriptor.getFields()) {
                      if (fieldDescriptor.isRequired()) {
                          throw runtime.newTypeError("Required fields are unsupported in proto3");
                      }
                      RubyFieldDescriptor rubyFieldDescriptor = rubyDescriptor.lookup(fieldDescriptor.getName());
                      rubyFieldDescriptor.setFieldDef(fieldDescriptor);
                      if (fieldDescriptor.getType() == Descriptors.FieldDescriptor.Type.MESSAGE) {
                          RubyDescriptor subType = (RubyDescriptor) lookup(context,
                                  runtime.newString(Utils.unescapeIdentifier(fieldDescriptor.getMessageType().getName())));
                          rubyFieldDescriptor.setSubType(subType);
                      }
                      if (fieldDescriptor.getType() == Descriptors.FieldDescriptor.Type.ENUM) {
                          RubyEnumDescriptor subType = (RubyEnumDescriptor) lookup(context,
                                  runtime.newString(Utils.unescapeIdentifier(fieldDescriptor.getEnumType().getName())));
                          rubyFieldDescriptor.setSubType(subType);
                      }
                  }
                  rubyDescriptor.setDescriptor(descriptor);
              }
          } catch (Descriptors.DescriptorValidationException e) {
              throw runtime.newRuntimeError(e.getMessage());
          }
      }
  
      private static RubyDescriptorPool descriptorPool;
  
      private RubyClass cBuilder;
      private Map<IRubyObject, IRubyObject> symtab;
      private DescriptorProtos.FileDescriptorProto.Builder builder;
  }
  