1 /* 2 * Protocol Buffers - Google's data interchange format 3 * Copyright 2014 Google Inc. All rights reserved. 4 * https://developers.google.com/protocol-buffers/ 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are 8 * met: 9 * 10 * * Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * * Redistributions in binary form must reproduce the above 13 * copyright notice, this list of conditions and the following disclaimer 14 * in the documentation and/or other materials provided with the 15 * distribution. 16 * * Neither the name of Google Inc. nor the names of its 17 * contributors may be used to endorse or promote products derived from 18 * this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 package com.google.protobuf.jruby; 34 35 import com.google.protobuf.ByteString; 36 import com.google.protobuf.DescriptorProtos; 37 import com.google.protobuf.Descriptors; 38 import org.jcodings.Encoding; 39 import org.jcodings.specific.ASCIIEncoding; 40 import org.jcodings.specific.USASCIIEncoding; 41 import org.jcodings.specific.UTF8Encoding; 42 import org.jruby.*; 43 import org.jruby.runtime.Block; 44 import org.jruby.runtime.ThreadContext; 45 import org.jruby.runtime.builtin.IRubyObject; 46 47 import java.math.BigInteger; 48 49 public class Utils { rubyToFieldType(IRubyObject typeClass)50 public static Descriptors.FieldDescriptor.Type rubyToFieldType(IRubyObject typeClass) { 51 return Descriptors.FieldDescriptor.Type.valueOf(typeClass.asJavaString().toUpperCase()); 52 } 53 fieldTypeToRuby(ThreadContext context, Descriptors.FieldDescriptor.Type type)54 public static IRubyObject fieldTypeToRuby(ThreadContext context, Descriptors.FieldDescriptor.Type type) { 55 return fieldTypeToRuby(context, type.name()); 56 } 57 fieldTypeToRuby(ThreadContext context, DescriptorProtos.FieldDescriptorProto.Type type)58 public static IRubyObject fieldTypeToRuby(ThreadContext context, DescriptorProtos.FieldDescriptorProto.Type type) { 59 return fieldTypeToRuby(context, type.name()); 60 } 61 fieldTypeToRuby(ThreadContext context, String typeName)62 private static IRubyObject fieldTypeToRuby(ThreadContext context, String typeName) { 63 64 return context.runtime.newSymbol(typeName.replace("TYPE_", "").toLowerCase()); 65 } 66 checkType(ThreadContext context, Descriptors.FieldDescriptor.Type fieldType, IRubyObject value, RubyModule typeClass)67 public static IRubyObject checkType(ThreadContext context, Descriptors.FieldDescriptor.Type fieldType, 68 IRubyObject value, RubyModule typeClass) { 69 Ruby runtime = context.runtime; 70 Object val; 71 switch(fieldType) { 72 case INT32: 73 case INT64: 74 case UINT32: 75 case UINT64: 76 if (!isRubyNum(value)) { 77 throw runtime.newTypeError("Expected number type for integral field."); 78 } 79 switch(fieldType) { 80 case INT32: 81 RubyNumeric.num2int(value); 82 break; 83 case INT64: 84 RubyNumeric.num2long(value); 85 break; 86 case UINT32: 87 num2uint(value); 88 break; 89 default: 90 num2ulong(context.runtime, value); 91 break; 92 } 93 checkIntTypePrecision(context, fieldType, value); 94 break; 95 case FLOAT: 96 if (!isRubyNum(value)) 97 throw runtime.newTypeError("Expected number type for float field."); 98 break; 99 case DOUBLE: 100 if (!isRubyNum(value)) 101 throw runtime.newTypeError("Expected number type for double field."); 102 break; 103 case BOOL: 104 if (!(value instanceof RubyBoolean)) 105 throw runtime.newTypeError("Invalid argument for boolean field."); 106 break; 107 case BYTES: 108 case STRING: 109 value = validateStringEncoding(context, fieldType, value); 110 break; 111 case MESSAGE: 112 if (value.getMetaClass() != typeClass) { 113 throw runtime.newTypeError(value, typeClass); 114 } 115 break; 116 case ENUM: 117 if (value instanceof RubySymbol) { 118 Descriptors.EnumDescriptor enumDescriptor = 119 ((RubyEnumDescriptor) typeClass.getInstanceVariable(DESCRIPTOR_INSTANCE_VAR)).getDescriptor(); 120 val = enumDescriptor.findValueByName(value.asJavaString()); 121 if (val == null) 122 throw runtime.newRangeError("Enum value " + value + " is not found."); 123 } else if(!isRubyNum(value)) { 124 throw runtime.newTypeError("Expected number or symbol type for enum field."); 125 } 126 break; 127 default: 128 break; 129 } 130 return value; 131 } 132 wrapPrimaryValue(ThreadContext context, Descriptors.FieldDescriptor.Type fieldType, Object value)133 public static IRubyObject wrapPrimaryValue(ThreadContext context, Descriptors.FieldDescriptor.Type fieldType, Object value) { 134 Ruby runtime = context.runtime; 135 switch (fieldType) { 136 case INT32: 137 return runtime.newFixnum((Integer) value); 138 case INT64: 139 return runtime.newFixnum((Long) value); 140 case UINT32: 141 return runtime.newFixnum(((Integer) value) & (-1l >>> 32)); 142 case UINT64: 143 long ret = (Long) value; 144 return ret >= 0 ? runtime.newFixnum(ret) : 145 RubyBignum.newBignum(runtime, UINT64_COMPLEMENTARY.add(new BigInteger(ret + ""))); 146 case FLOAT: 147 return runtime.newFloat((Float) value); 148 case DOUBLE: 149 return runtime.newFloat((Double) value); 150 case BOOL: 151 return (Boolean) value ? runtime.getTrue() : runtime.getFalse(); 152 case BYTES: { 153 IRubyObject wrapped = runtime.newString(((ByteString) value).toStringUtf8()); 154 wrapped.setFrozen(true); 155 return wrapped; 156 } 157 case STRING: { 158 IRubyObject wrapped = runtime.newString(value.toString()); 159 wrapped.setFrozen(true); 160 return wrapped; 161 } 162 default: 163 return runtime.getNil(); 164 } 165 } 166 num2uint(IRubyObject value)167 public static int num2uint(IRubyObject value) { 168 long longVal = RubyNumeric.num2long(value); 169 if (longVal > UINT_MAX) 170 throw value.getRuntime().newRangeError("Integer " + longVal + " too big to convert to 'unsigned int'"); 171 long num = longVal; 172 if (num > Integer.MAX_VALUE || num < Integer.MIN_VALUE) 173 // encode to UINT32 174 num = (-longVal ^ (-1l >>> 32) ) + 1; 175 RubyNumeric.checkInt(value, num); 176 return (int) num; 177 } 178 num2ulong(Ruby runtime, IRubyObject value)179 public static long num2ulong(Ruby runtime, IRubyObject value) { 180 if (value instanceof RubyFloat) { 181 RubyBignum bignum = RubyBignum.newBignum(runtime, ((RubyFloat) value).getDoubleValue()); 182 return RubyBignum.big2ulong(bignum); 183 } else if (value instanceof RubyBignum) { 184 return RubyBignum.big2ulong((RubyBignum) value); 185 } else { 186 return RubyNumeric.num2long(value); 187 } 188 } 189 validateStringEncoding(ThreadContext context, Descriptors.FieldDescriptor.Type type, IRubyObject value)190 public static IRubyObject validateStringEncoding(ThreadContext context, Descriptors.FieldDescriptor.Type type, IRubyObject value) { 191 if (!(value instanceof RubyString)) 192 throw context.runtime.newTypeError("Invalid argument for string field."); 193 switch(type) { 194 case BYTES: 195 value = ((RubyString)value).encode(context, context.runtime.evalScriptlet("Encoding::ASCII_8BIT")); 196 break; 197 case STRING: 198 value = ((RubyString)value).encode(context, context.runtime.evalScriptlet("Encoding::UTF_8")); 199 break; 200 default: 201 break; 202 } 203 value.setFrozen(true); 204 return value; 205 } 206 checkNameAvailability(ThreadContext context, String name)207 public static void checkNameAvailability(ThreadContext context, String name) { 208 if (context.runtime.getObject().getConstantAt(name) != null) 209 throw context.runtime.newNameError(name + " is already defined", name); 210 } 211 212 /** 213 * Replace invalid "." in descriptor with __DOT__ 214 * @param name 215 * @return 216 */ escapeIdentifier(String name)217 public static String escapeIdentifier(String name) { 218 return name.replace(".", BADNAME_REPLACEMENT); 219 } 220 221 /** 222 * Replace __DOT__ in descriptor name with "." 223 * @param name 224 * @return 225 */ unescapeIdentifier(String name)226 public static String unescapeIdentifier(String name) { 227 return name.replace(BADNAME_REPLACEMENT, "."); 228 } 229 isMapEntry(Descriptors.FieldDescriptor fieldDescriptor)230 public static boolean isMapEntry(Descriptors.FieldDescriptor fieldDescriptor) { 231 return fieldDescriptor.getType() == Descriptors.FieldDescriptor.Type.MESSAGE && 232 fieldDescriptor.isRepeated() && 233 fieldDescriptor.getMessageType().getOptions().getMapEntry(); 234 } 235 msgdefCreateField(ThreadContext context, String label, IRubyObject name, IRubyObject type, IRubyObject number, IRubyObject typeClass, RubyClass cFieldDescriptor)236 public static RubyFieldDescriptor msgdefCreateField(ThreadContext context, String label, IRubyObject name, 237 IRubyObject type, IRubyObject number, IRubyObject typeClass, RubyClass cFieldDescriptor) { 238 Ruby runtime = context.runtime; 239 RubyFieldDescriptor fieldDef = (RubyFieldDescriptor) cFieldDescriptor.newInstance(context, Block.NULL_BLOCK); 240 fieldDef.setLabel(context, runtime.newString(label)); 241 fieldDef.setName(context, name); 242 fieldDef.setType(context, type); 243 fieldDef.setNumber(context, number); 244 245 if (!typeClass.isNil()) { 246 if (!(typeClass instanceof RubyString)) { 247 throw runtime.newArgumentError("expected string for type class"); 248 } 249 fieldDef.setSubmsgName(context, typeClass); 250 } 251 return fieldDef; 252 } 253 checkIntTypePrecision(ThreadContext context, Descriptors.FieldDescriptor.Type type, IRubyObject value)254 protected static void checkIntTypePrecision(ThreadContext context, Descriptors.FieldDescriptor.Type type, IRubyObject value) { 255 if (value instanceof RubyFloat) { 256 double doubleVal = RubyNumeric.num2dbl(value); 257 if (Math.floor(doubleVal) != doubleVal) { 258 throw context.runtime.newRangeError("Non-integral floating point value assigned to integer field."); 259 } 260 } 261 if (type == Descriptors.FieldDescriptor.Type.UINT32 || type == Descriptors.FieldDescriptor.Type.UINT64) { 262 if (RubyNumeric.num2dbl(value) < 0) { 263 throw context.runtime.newRangeError("Assigning negative value to unsigned integer field."); 264 } 265 } 266 } 267 isRubyNum(Object value)268 protected static boolean isRubyNum(Object value) { 269 return value instanceof RubyFixnum || value instanceof RubyFloat || value instanceof RubyBignum; 270 } 271 validateTypeClass(ThreadContext context, Descriptors.FieldDescriptor.Type type, IRubyObject value)272 protected static void validateTypeClass(ThreadContext context, Descriptors.FieldDescriptor.Type type, IRubyObject value) { 273 Ruby runtime = context.runtime; 274 if (!(value instanceof RubyModule)) { 275 throw runtime.newArgumentError("TypeClass has incorrect type"); 276 } 277 RubyModule klass = (RubyModule) value; 278 IRubyObject descriptor = klass.getInstanceVariable(DESCRIPTOR_INSTANCE_VAR); 279 if (descriptor.isNil()) { 280 throw runtime.newArgumentError("Type class has no descriptor. Please pass a " + 281 "class or enum as returned by the DescriptorPool."); 282 } 283 if (type == Descriptors.FieldDescriptor.Type.MESSAGE) { 284 if (! (descriptor instanceof RubyDescriptor)) { 285 throw runtime.newArgumentError("Descriptor has an incorrect type"); 286 } 287 } else if (type == Descriptors.FieldDescriptor.Type.ENUM) { 288 if (! (descriptor instanceof RubyEnumDescriptor)) { 289 throw runtime.newArgumentError("Descriptor has an incorrect type"); 290 } 291 } 292 } 293 294 public static String BADNAME_REPLACEMENT = "__DOT__"; 295 296 public static String DESCRIPTOR_INSTANCE_VAR = "@descriptor"; 297 298 public static String EQUAL_SIGN = "="; 299 300 private static BigInteger UINT64_COMPLEMENTARY = new BigInteger("18446744073709551616"); //Math.pow(2, 64) 301 302 private static long UINT_MAX = 0xffffffffl; 303 } 304