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.ByteString;
  import com.google.protobuf.DescriptorProtos;
  import com.google.protobuf.Descriptors;
  import org.jcodings.Encoding;
  import org.jcodings.specific.ASCIIEncoding;
  import org.jcodings.specific.USASCIIEncoding;
  import org.jcodings.specific.UTF8Encoding;
  import org.jruby.*;
  import org.jruby.runtime.Block;
  import org.jruby.runtime.ThreadContext;
  import org.jruby.runtime.builtin.IRubyObject;
  
  import java.math.BigInteger;
  
  public class Utils {
      public static Descriptors.FieldDescriptor.Type rubyToFieldType(IRubyObject typeClass) {
          return Descriptors.FieldDescriptor.Type.valueOf(typeClass.asJavaString().toUpperCase());
      }
  
      public static IRubyObject fieldTypeToRuby(ThreadContext context, Descriptors.FieldDescriptor.Type type) {
          return fieldTypeToRuby(context, type.name());
      }
  
      public static IRubyObject fieldTypeToRuby(ThreadContext context, DescriptorProtos.FieldDescriptorProto.Type type) {
          return fieldTypeToRuby(context, type.name());
      }
  
      private static IRubyObject fieldTypeToRuby(ThreadContext context, String typeName) {
  
          return context.runtime.newSymbol(typeName.replace("TYPE_", "").toLowerCase());
      }
  
      public static IRubyObject checkType(ThreadContext context, Descriptors.FieldDescriptor.Type fieldType,
                                          IRubyObject value, RubyModule typeClass) {
          Ruby runtime = context.runtime;
          Object val;
          switch(fieldType) {
              case INT32:
              case INT64:
              case UINT32:
              case UINT64:
                  if (!isRubyNum(value)) {
                      throw runtime.newTypeError("Expected number type for integral field.");
                  }
                  switch(fieldType) {
                      case INT32:
                          RubyNumeric.num2int(value);
                          break;
                      case INT64:
                          RubyNumeric.num2long(value);
                          break;
                      case UINT32:
                          num2uint(value);
                          break;
                      default:
                          num2ulong(context.runtime, value);
                          break;
                  }
                  checkIntTypePrecision(context, fieldType, value);
                  break;
              case FLOAT:
                  if (!isRubyNum(value))
                      throw runtime.newTypeError("Expected number type for float field.");
                  break;
              case DOUBLE:
                  if (!isRubyNum(value))
                      throw runtime.newTypeError("Expected number type for double field.");
                  break;
              case BOOL:
                  if (!(value instanceof RubyBoolean))
                      throw runtime.newTypeError("Invalid argument for boolean field.");
                  break;
              case BYTES:
              case STRING:
                  value = validateStringEncoding(context, fieldType, value);
                  break;
              case MESSAGE:
                  if (value.getMetaClass() != typeClass) {
                      throw runtime.newTypeError(value, typeClass);
                  }
                  break;
              case ENUM:
                  if (value instanceof RubySymbol) {
                      Descriptors.EnumDescriptor enumDescriptor =
                              ((RubyEnumDescriptor) typeClass.getInstanceVariable(DESCRIPTOR_INSTANCE_VAR)).getDescriptor();
                      val = enumDescriptor.findValueByName(value.asJavaString());
                      if (val == null)
                          throw runtime.newRangeError("Enum value " + value + " is not found.");
                  } else if(!isRubyNum(value)) {
                      throw runtime.newTypeError("Expected number or symbol type for enum field.");
                  }
                  break;
              default:
                  break;
          }
          return value;
      }
  
      public static IRubyObject wrapPrimaryValue(ThreadContext context, Descriptors.FieldDescriptor.Type fieldType, Object value) {
          Ruby runtime = context.runtime;
          switch (fieldType) {
              case INT32:
                  return runtime.newFixnum((Integer) value);
              case INT64:
                  return runtime.newFixnum((Long) value);
              case UINT32:
                  return runtime.newFixnum(((Integer) value) & (-1l >>> 32));
              case UINT64:
                  long ret = (Long) value;
                  return ret >= 0 ? runtime.newFixnum(ret) :
                          RubyBignum.newBignum(runtime, UINT64_COMPLEMENTARY.add(new BigInteger(ret + "")));
              case FLOAT:
                  return runtime.newFloat((Float) value);
              case DOUBLE:
                  return runtime.newFloat((Double) value);
              case BOOL:
                  return (Boolean) value ? runtime.getTrue() : runtime.getFalse();
              case BYTES: {
                  IRubyObject wrapped = runtime.newString(((ByteString) value).toStringUtf8());
                  wrapped.setFrozen(true);
                  return wrapped;
              }
              case STRING: {
                  IRubyObject wrapped = runtime.newString(value.toString());
                  wrapped.setFrozen(true);
                  return wrapped;
              }
              default:
                  return runtime.getNil();
          }
      }
  
      public static int num2uint(IRubyObject value) {
          long longVal = RubyNumeric.num2long(value);
          if (longVal > UINT_MAX)
              throw value.getRuntime().newRangeError("Integer " + longVal + " too big to convert to 'unsigned int'");
          long num = longVal;
          if (num > Integer.MAX_VALUE || num < Integer.MIN_VALUE)
              // encode to UINT32
              num = (-longVal ^ (-1l >>> 32) ) + 1;
          RubyNumeric.checkInt(value, num);
          return (int) num;
      }
  
      public static long num2ulong(Ruby runtime, IRubyObject value) {
          if (value instanceof RubyFloat) {
              RubyBignum bignum = RubyBignum.newBignum(runtime, ((RubyFloat) value).getDoubleValue());
              return RubyBignum.big2ulong(bignum);
          } else if (value instanceof RubyBignum) {
              return RubyBignum.big2ulong((RubyBignum) value);
          } else {
              return RubyNumeric.num2long(value);
          }
      }
  
      public static IRubyObject validateStringEncoding(ThreadContext context, Descriptors.FieldDescriptor.Type type, IRubyObject value) {
          if (!(value instanceof RubyString))
              throw context.runtime.newTypeError("Invalid argument for string field.");
          switch(type) {
              case BYTES:
                  value = ((RubyString)value).encode(context, context.runtime.evalScriptlet("Encoding::ASCII_8BIT"));
                  break;
              case STRING:
                  value = ((RubyString)value).encode(context, context.runtime.evalScriptlet("Encoding::UTF_8"));
                  break;
              default:
                  break;
          }
          value.setFrozen(true);
          return value;
      }
  
      public static void checkNameAvailability(ThreadContext context, String name) {
          if (context.runtime.getObject().getConstantAt(name) != null)
              throw context.runtime.newNameError(name + " is already defined", name);
      }
  
      /**
       * Replace invalid "." in descriptor with __DOT__
       * @param name
       * @return
       */
      public static String escapeIdentifier(String name) {
          return name.replace(".", BADNAME_REPLACEMENT);
      }
  
      /**
       * Replace __DOT__ in descriptor name with "."
       * @param name
       * @return
       */
      public static String unescapeIdentifier(String name) {
          return name.replace(BADNAME_REPLACEMENT, ".");
      }
  
      public static boolean isMapEntry(Descriptors.FieldDescriptor fieldDescriptor) {
          return fieldDescriptor.getType() == Descriptors.FieldDescriptor.Type.MESSAGE &&
                  fieldDescriptor.isRepeated() &&
                  fieldDescriptor.getMessageType().getOptions().getMapEntry();
      }
  
      public static RubyFieldDescriptor msgdefCreateField(ThreadContext context, String label, IRubyObject name,
                                        IRubyObject type, IRubyObject number, IRubyObject typeClass, RubyClass cFieldDescriptor) {
          Ruby runtime = context.runtime;
          RubyFieldDescriptor fieldDef = (RubyFieldDescriptor) cFieldDescriptor.newInstance(context, Block.NULL_BLOCK);
          fieldDef.setLabel(context, runtime.newString(label));
          fieldDef.setName(context, name);
          fieldDef.setType(context, type);
          fieldDef.setNumber(context, number);
  
          if (!typeClass.isNil()) {
              if (!(typeClass instanceof RubyString)) {
                  throw runtime.newArgumentError("expected string for type class");
              }
              fieldDef.setSubmsgName(context, typeClass);
          }
          return fieldDef;
      }
  
      protected static void checkIntTypePrecision(ThreadContext context, Descriptors.FieldDescriptor.Type type, IRubyObject value) {
          if (value instanceof RubyFloat) {
              double doubleVal = RubyNumeric.num2dbl(value);
              if (Math.floor(doubleVal) != doubleVal) {
                  throw context.runtime.newRangeError("Non-integral floating point value assigned to integer field.");
              }
          }
          if (type == Descriptors.FieldDescriptor.Type.UINT32 || type == Descriptors.FieldDescriptor.Type.UINT64) {
              if (RubyNumeric.num2dbl(value) < 0) {
                  throw context.runtime.newRangeError("Assigning negative value to unsigned integer field.");
              }
          }
      }
  
      protected static boolean isRubyNum(Object value) {
          return value instanceof RubyFixnum || value instanceof RubyFloat || value instanceof RubyBignum;
      }
  
      protected static void validateTypeClass(ThreadContext context, Descriptors.FieldDescriptor.Type type, IRubyObject value) {
          Ruby runtime = context.runtime;
          if (!(value instanceof RubyModule)) {
              throw runtime.newArgumentError("TypeClass has incorrect type");
          }
          RubyModule klass = (RubyModule) value;
          IRubyObject descriptor = klass.getInstanceVariable(DESCRIPTOR_INSTANCE_VAR);
          if (descriptor.isNil()) {
              throw runtime.newArgumentError("Type class has no descriptor. Please pass a " +
                      "class or enum as returned by the DescriptorPool.");
          }
          if (type == Descriptors.FieldDescriptor.Type.MESSAGE) {
              if (! (descriptor instanceof RubyDescriptor)) {
                  throw runtime.newArgumentError("Descriptor has an incorrect type");
              }
          } else if (type == Descriptors.FieldDescriptor.Type.ENUM) {
              if (! (descriptor instanceof RubyEnumDescriptor)) {
                  throw runtime.newArgumentError("Descriptor has an incorrect type");
              }
          }
      }
  
      public static String BADNAME_REPLACEMENT = "__DOT__";
  
      public static String DESCRIPTOR_INSTANCE_VAR = "@descriptor";
  
      public static String EQUAL_SIGN = "=";
  
      private static BigInteger UINT64_COMPLEMENTARY = new BigInteger("18446744073709551616"); //Math.pow(2, 64)
  
      private static long UINT_MAX = 0xffffffffl;
  }
  