Squashed 'libs/protobuf/' content from commit fcd3b9a85
git-subtree-dir: libs/protobuf git-subtree-split: fcd3b9a85ef36e46643dc30176cea1a7ad62e02b
This commit is contained in:
407
ruby/src/main/java/com/google/protobuf/jruby/Utils.java
Normal file
407
ruby/src/main/java/com/google/protobuf/jruby/Utils.java
Normal file
@@ -0,0 +1,407 @@
|
||||
/*
|
||||
* 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.FieldDescriptorProto;
|
||||
import com.google.protobuf.Descriptors.FieldDescriptor;
|
||||
import java.math.BigInteger;
|
||||
import org.jcodings.specific.ASCIIEncoding;
|
||||
import org.jruby.*;
|
||||
import org.jruby.exceptions.RaiseException;
|
||||
import org.jruby.ext.bigdecimal.RubyBigDecimal;
|
||||
import org.jruby.runtime.Block;
|
||||
import org.jruby.runtime.Helpers;
|
||||
import org.jruby.runtime.ThreadContext;
|
||||
import org.jruby.runtime.builtin.IRubyObject;
|
||||
|
||||
public class Utils {
|
||||
public static FieldDescriptor.Type rubyToFieldType(IRubyObject typeClass) {
|
||||
return FieldDescriptor.Type.valueOf(typeClass.asJavaString().toUpperCase());
|
||||
}
|
||||
|
||||
public static IRubyObject fieldTypeToRuby(ThreadContext context, FieldDescriptor.Type type) {
|
||||
return fieldTypeToRuby(context, type.name());
|
||||
}
|
||||
|
||||
public static IRubyObject fieldTypeToRuby(ThreadContext context, 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,
|
||||
FieldDescriptor.Type fieldType,
|
||||
String fieldName,
|
||||
IRubyObject value,
|
||||
RubyModule typeClass) {
|
||||
Ruby runtime = context.runtime;
|
||||
|
||||
switch (fieldType) {
|
||||
case SFIXED32:
|
||||
case SFIXED64:
|
||||
case FIXED64:
|
||||
case SINT64:
|
||||
case SINT32:
|
||||
case FIXED32:
|
||||
case INT32:
|
||||
case INT64:
|
||||
case UINT32:
|
||||
case UINT64:
|
||||
if (!isRubyNum(value))
|
||||
throw createExpectedTypeError(context, "number", "integral", fieldName, value);
|
||||
|
||||
if (value instanceof RubyFloat) {
|
||||
double doubleVal = RubyNumeric.num2dbl(value);
|
||||
if (Math.floor(doubleVal) != doubleVal) {
|
||||
throw runtime.newRangeError(
|
||||
"Non-integral floating point value assigned to integer field '"
|
||||
+ fieldName
|
||||
+ "' (given "
|
||||
+ value.getMetaClass()
|
||||
+ ").");
|
||||
}
|
||||
}
|
||||
if (fieldType == FieldDescriptor.Type.UINT32
|
||||
|| fieldType == FieldDescriptor.Type.UINT64
|
||||
|| fieldType == FieldDescriptor.Type.FIXED32
|
||||
|| fieldType == FieldDescriptor.Type.FIXED64) {
|
||||
if (((RubyNumeric) value).isNegative()) {
|
||||
throw runtime.newRangeError(
|
||||
"Assigning negative value to unsigned integer field '"
|
||||
+ fieldName
|
||||
+ "' (given "
|
||||
+ value.getMetaClass()
|
||||
+ ").");
|
||||
}
|
||||
}
|
||||
|
||||
switch (fieldType) {
|
||||
case INT32:
|
||||
RubyNumeric.num2int(value);
|
||||
break;
|
||||
case UINT32:
|
||||
case FIXED32:
|
||||
num2uint(value);
|
||||
break;
|
||||
case UINT64:
|
||||
case FIXED64:
|
||||
num2ulong(context.runtime, value);
|
||||
break;
|
||||
default:
|
||||
RubyNumeric.num2long(value);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case FLOAT:
|
||||
if (!isRubyNum(value))
|
||||
throw createExpectedTypeError(context, "number", "float", fieldName, value);
|
||||
break;
|
||||
case DOUBLE:
|
||||
if (!isRubyNum(value))
|
||||
throw createExpectedTypeError(context, "number", "double", fieldName, value);
|
||||
break;
|
||||
case BOOL:
|
||||
if (!(value instanceof RubyBoolean))
|
||||
throw createInvalidTypeError(context, "boolean", fieldName, value);
|
||||
break;
|
||||
case BYTES:
|
||||
value = validateAndEncodeString(context, "bytes", fieldName, value, "Encoding::ASCII_8BIT");
|
||||
break;
|
||||
case STRING:
|
||||
value =
|
||||
validateAndEncodeString(
|
||||
context, "string", fieldName, symToString(value), "Encoding::UTF_8");
|
||||
break;
|
||||
case MESSAGE:
|
||||
if (value.getMetaClass() != typeClass) {
|
||||
// See if we can convert the value before flagging it as invalid
|
||||
String className = typeClass.getName();
|
||||
|
||||
if (className.equals("Google::Protobuf::Timestamp") && value instanceof RubyTime) {
|
||||
RubyTime rt = (RubyTime) value;
|
||||
RubyHash timestampArgs =
|
||||
Helpers.constructHash(
|
||||
runtime,
|
||||
runtime.newString("nanos"),
|
||||
rt.nsec(),
|
||||
false,
|
||||
runtime.newString("seconds"),
|
||||
rt.to_i(),
|
||||
false);
|
||||
return ((RubyClass) typeClass).newInstance(context, timestampArgs, Block.NULL_BLOCK);
|
||||
|
||||
} else if (className.equals("Google::Protobuf::Duration")
|
||||
&& value instanceof RubyNumeric) {
|
||||
IRubyObject seconds;
|
||||
if (value instanceof RubyFloat) {
|
||||
seconds = ((RubyFloat) value).truncate(context);
|
||||
} else if (value instanceof RubyRational) {
|
||||
seconds = ((RubyRational) value).to_i(context);
|
||||
} else if (value instanceof RubyBigDecimal) {
|
||||
seconds = ((RubyBigDecimal) value).to_int(context);
|
||||
} else {
|
||||
seconds = ((RubyInteger) value).to_i();
|
||||
}
|
||||
|
||||
IRubyObject nanos = ((RubyNumeric) value).remainder(context, RubyFixnum.one(runtime));
|
||||
if (nanos instanceof RubyFloat) {
|
||||
nanos = ((RubyFloat) nanos).op_mul(context, 1000000000);
|
||||
} else if (nanos instanceof RubyRational) {
|
||||
nanos = ((RubyRational) nanos).op_mul(context, runtime.newFixnum(1000000000));
|
||||
} else if (nanos instanceof RubyBigDecimal) {
|
||||
nanos = ((RubyBigDecimal) nanos).op_mul(context, runtime.newFixnum(1000000000));
|
||||
} else {
|
||||
nanos = ((RubyInteger) nanos).op_mul(context, 1000000000);
|
||||
}
|
||||
|
||||
RubyHash durationArgs =
|
||||
Helpers.constructHash(
|
||||
runtime,
|
||||
runtime.newString("nanos"),
|
||||
((RubyNumeric) nanos).round(context),
|
||||
false,
|
||||
runtime.newString("seconds"),
|
||||
seconds,
|
||||
false);
|
||||
return ((RubyClass) typeClass).newInstance(context, durationArgs, Block.NULL_BLOCK);
|
||||
}
|
||||
|
||||
// Not able to convert so flag as invalid
|
||||
throw createTypeError(
|
||||
context,
|
||||
"Invalid type "
|
||||
+ value.getMetaClass()
|
||||
+ " to assign to submessage field '"
|
||||
+ fieldName
|
||||
+ "'.");
|
||||
}
|
||||
|
||||
break;
|
||||
case ENUM:
|
||||
boolean isValid =
|
||||
((RubyEnumDescriptor) typeClass.getInstanceVariable(DESCRIPTOR_INSTANCE_VAR))
|
||||
.isValidValue(context, value);
|
||||
if (!isValid) {
|
||||
throw runtime.newRangeError("Unknown symbol value for enum field '" + fieldName + "'.");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static IRubyObject wrapPrimaryValue(
|
||||
ThreadContext context, FieldDescriptor.Type fieldType, Object value) {
|
||||
return wrapPrimaryValue(context, fieldType, value, false);
|
||||
}
|
||||
|
||||
public static IRubyObject wrapPrimaryValue(
|
||||
ThreadContext context, FieldDescriptor.Type fieldType, Object value, boolean encodeBytes) {
|
||||
Ruby runtime = context.runtime;
|
||||
switch (fieldType) {
|
||||
case INT32:
|
||||
case SFIXED32:
|
||||
case SINT32:
|
||||
return runtime.newFixnum((Integer) value);
|
||||
case SFIXED64:
|
||||
case SINT64:
|
||||
case INT64:
|
||||
return runtime.newFixnum((Long) value);
|
||||
case FIXED32:
|
||||
case UINT32:
|
||||
return runtime.newFixnum(((Integer) value) & (-1l >>> 32));
|
||||
case FIXED64:
|
||||
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 =
|
||||
encodeBytes
|
||||
? RubyString.newString(
|
||||
runtime, ((ByteString) value).toStringUtf8(), ASCIIEncoding.INSTANCE)
|
||||
: RubyString.newString(runtime, ((ByteString) value).toByteArray());
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper to make it easier to support symbols being passed instead of strings
|
||||
*/
|
||||
public static IRubyObject symToString(IRubyObject sym) {
|
||||
if (sym instanceof RubySymbol) {
|
||||
return ((RubySymbol) sym).id2name();
|
||||
}
|
||||
return sym;
|
||||
}
|
||||
|
||||
public static void checkNameAvailability(ThreadContext context, String name) {
|
||||
if (context.runtime.getObject().getConstantAt(name) != null)
|
||||
throw context.runtime.newNameError(name + " is already defined", name);
|
||||
}
|
||||
|
||||
public static boolean isMapEntry(FieldDescriptor fieldDescriptor) {
|
||||
return fieldDescriptor.getType() == FieldDescriptor.Type.MESSAGE
|
||||
&& fieldDescriptor.isRepeated()
|
||||
&& fieldDescriptor.getMessageType().getOptions().getMapEntry();
|
||||
}
|
||||
|
||||
public static RaiseException createTypeError(ThreadContext context, String message) {
|
||||
if (cTypeError == null) {
|
||||
cTypeError = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::TypeError");
|
||||
}
|
||||
return RaiseException.from(context.runtime, cTypeError, message);
|
||||
}
|
||||
|
||||
public static RaiseException createExpectedTypeError(
|
||||
ThreadContext context, String type, String fieldType, String fieldName, IRubyObject value) {
|
||||
return createTypeError(
|
||||
context,
|
||||
String.format(
|
||||
EXPECTED_TYPE_ERROR_FORMAT, type, fieldType, fieldName, value.getMetaClass()));
|
||||
}
|
||||
|
||||
public static RaiseException createInvalidTypeError(
|
||||
ThreadContext context, String fieldType, String fieldName, IRubyObject value) {
|
||||
return createTypeError(
|
||||
context,
|
||||
String.format(INVALID_TYPE_ERROR_FORMAT, fieldType, fieldName, value.getMetaClass()));
|
||||
}
|
||||
|
||||
protected static boolean isRubyNum(Object value) {
|
||||
return value instanceof RubyFixnum || value instanceof RubyFloat || value instanceof RubyBignum;
|
||||
}
|
||||
|
||||
protected static void validateTypeClass(
|
||||
ThreadContext context, 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 == FieldDescriptor.Type.MESSAGE) {
|
||||
if (!(descriptor instanceof RubyDescriptor)) {
|
||||
throw runtime.newArgumentError("Descriptor has an incorrect type");
|
||||
}
|
||||
} else if (type == FieldDescriptor.Type.ENUM) {
|
||||
if (!(descriptor instanceof RubyEnumDescriptor)) {
|
||||
throw runtime.newArgumentError("Descriptor has an incorrect type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IRubyObject validateAndEncodeString(
|
||||
ThreadContext context,
|
||||
String fieldType,
|
||||
String fieldName,
|
||||
IRubyObject value,
|
||||
String encoding) {
|
||||
if (!(value instanceof RubyString))
|
||||
throw createInvalidTypeError(context, fieldType, fieldName, value);
|
||||
|
||||
value = ((RubyString) value).encode(context, context.runtime.evalScriptlet(encoding));
|
||||
value.setFrozen(true);
|
||||
return value;
|
||||
}
|
||||
|
||||
public static final String DESCRIPTOR_INSTANCE_VAR = "@descriptor";
|
||||
|
||||
public static final String EQUAL_SIGN = "=";
|
||||
|
||||
private static final BigInteger UINT64_COMPLEMENTARY =
|
||||
new BigInteger("18446744073709551616"); // Math.pow(2, 64)
|
||||
|
||||
private static final String EXPECTED_TYPE_ERROR_FORMAT =
|
||||
"Expected %s type for %s field '%s' (given %s).";
|
||||
private static final String INVALID_TYPE_ERROR_FORMAT =
|
||||
"Invalid argument for %s field '%s' (given %s).";
|
||||
|
||||
private static final long UINT_MAX = 0xffffffffl;
|
||||
|
||||
private static RubyClass cTypeError;
|
||||
}
|
||||
Reference in New Issue
Block a user