From 27e2b57830c328b83286e055752bf92790587953 Mon Sep 17 00:00:00 2001 From: Isaiah Peng Date: Wed, 24 Dec 2014 15:48:41 +0100 Subject: add jruby support by protobuf-java reflection API --- .../main/java/com/google/protobuf/jruby/Utils.java | 300 +++++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 ruby/src/main/java/com/google/protobuf/jruby/Utils.java (limited to 'ruby/src/main/java/com/google/protobuf/jruby/Utils.java') diff --git a/ruby/src/main/java/com/google/protobuf/jruby/Utils.java b/ruby/src/main/java/com/google/protobuf/jruby/Utils.java new file mode 100644 index 00000000..596a0979 --- /dev/null +++ b/ruby/src/main/java/com/google/protobuf/jruby/Utils.java @@ -0,0 +1,300 @@ +/* + * 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 void 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: + validateStringEncoding(context.runtime, 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; + } + } + + 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: + return runtime.newString(((ByteString) value).toStringUtf8()); + case STRING: + return runtime.newString(value.toString()); + 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 void validateStringEncoding(Ruby runtime, Descriptors.FieldDescriptor.Type type, IRubyObject value) { + if (!(value instanceof RubyString)) + throw runtime.newTypeError("Invalid argument for string field."); + Encoding encoding = ((RubyString) value).getEncoding(); + switch(type) { + case BYTES: + if (encoding != ASCIIEncoding.INSTANCE) + throw runtime.newTypeError("Encoding for bytes fields" + + " must be \"ASCII-8BIT\", but was " + encoding); + break; + case STRING: + if (encoding != UTF8Encoding.INSTANCE + && encoding != USASCIIEncoding.INSTANCE) + throw runtime.newTypeError("Encoding for string fields" + + " must be \"UTF-8\" or \"ASCII\", but was " + encoding); + break; + default: + break; + } + } + + 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; +} -- cgit v1.2.3