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 void 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 validateStringEncoding(context.runtime, 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 } 131 wrapPrimaryValue(ThreadContext context, Descriptors.FieldDescriptor.Type fieldType, Object value)132 public static IRubyObject wrapPrimaryValue(ThreadContext context, Descriptors.FieldDescriptor.Type fieldType, Object value) { 133 Ruby runtime = context.runtime; 134 switch (fieldType) { 135 case INT32: 136 return runtime.newFixnum((Integer) value); 137 case INT64: 138 return runtime.newFixnum((Long) value); 139 case UINT32: 140 return runtime.newFixnum(((Integer) value) & (-1l >>> 32)); 141 case UINT64: 142 long ret = (Long) value; 143 return ret >= 0 ? runtime.newFixnum(ret) : 144 RubyBignum.newBignum(runtime, UINT64_COMPLEMENTARY.add(new BigInteger(ret + ""))); 145 case FLOAT: 146 return runtime.newFloat((Float) value); 147 case DOUBLE: 148 return runtime.newFloat((Double) value); 149 case BOOL: 150 return (Boolean) value ? runtime.getTrue() : runtime.getFalse(); 151 case BYTES: 152 return runtime.newString(((ByteString) value).toStringUtf8()); 153 case STRING: 154 return runtime.newString(value.toString()); 155 default: 156 return runtime.getNil(); 157 } 158 } 159 num2uint(IRubyObject value)160 public static int num2uint(IRubyObject value) { 161 long longVal = RubyNumeric.num2long(value); 162 if (longVal > UINT_MAX) 163 throw value.getRuntime().newRangeError("Integer " + longVal + " too big to convert to 'unsigned int'"); 164 long num = longVal; 165 if (num > Integer.MAX_VALUE || num < Integer.MIN_VALUE) 166 // encode to UINT32 167 num = (-longVal ^ (-1l >>> 32) ) + 1; 168 RubyNumeric.checkInt(value, num); 169 return (int) num; 170 } 171 num2ulong(Ruby runtime, IRubyObject value)172 public static long num2ulong(Ruby runtime, IRubyObject value) { 173 if (value instanceof RubyFloat) { 174 RubyBignum bignum = RubyBignum.newBignum(runtime, ((RubyFloat) value).getDoubleValue()); 175 return RubyBignum.big2ulong(bignum); 176 } else if (value instanceof RubyBignum) { 177 return RubyBignum.big2ulong((RubyBignum) value); 178 } else { 179 return RubyNumeric.num2long(value); 180 } 181 } 182 validateStringEncoding(Ruby runtime, Descriptors.FieldDescriptor.Type type, IRubyObject value)183 public static void validateStringEncoding(Ruby runtime, Descriptors.FieldDescriptor.Type type, IRubyObject value) { 184 if (!(value instanceof RubyString)) 185 throw runtime.newTypeError("Invalid argument for string field."); 186 Encoding encoding = ((RubyString) value).getEncoding(); 187 switch(type) { 188 case BYTES: 189 if (encoding != ASCIIEncoding.INSTANCE) 190 throw runtime.newTypeError("Encoding for bytes fields" + 191 " must be \"ASCII-8BIT\", but was " + encoding); 192 break; 193 case STRING: 194 if (encoding != UTF8Encoding.INSTANCE 195 && encoding != USASCIIEncoding.INSTANCE) 196 throw runtime.newTypeError("Encoding for string fields" + 197 " must be \"UTF-8\" or \"ASCII\", but was " + encoding); 198 break; 199 default: 200 break; 201 } 202 } 203 checkNameAvailability(ThreadContext context, String name)204 public static void checkNameAvailability(ThreadContext context, String name) { 205 if (context.runtime.getObject().getConstantAt(name) != null) 206 throw context.runtime.newNameError(name + " is already defined", name); 207 } 208 209 /** 210 * Replace invalid "." in descriptor with __DOT__ 211 * @param name 212 * @return 213 */ escapeIdentifier(String name)214 public static String escapeIdentifier(String name) { 215 return name.replace(".", BADNAME_REPLACEMENT); 216 } 217 218 /** 219 * Replace __DOT__ in descriptor name with "." 220 * @param name 221 * @return 222 */ unescapeIdentifier(String name)223 public static String unescapeIdentifier(String name) { 224 return name.replace(BADNAME_REPLACEMENT, "."); 225 } 226 isMapEntry(Descriptors.FieldDescriptor fieldDescriptor)227 public static boolean isMapEntry(Descriptors.FieldDescriptor fieldDescriptor) { 228 return fieldDescriptor.getType() == Descriptors.FieldDescriptor.Type.MESSAGE && 229 fieldDescriptor.isRepeated() && 230 fieldDescriptor.getMessageType().getOptions().getMapEntry(); 231 } 232 msgdefCreateField(ThreadContext context, String label, IRubyObject name, IRubyObject type, IRubyObject number, IRubyObject typeClass, RubyClass cFieldDescriptor)233 public static RubyFieldDescriptor msgdefCreateField(ThreadContext context, String label, IRubyObject name, 234 IRubyObject type, IRubyObject number, IRubyObject typeClass, RubyClass cFieldDescriptor) { 235 Ruby runtime = context.runtime; 236 RubyFieldDescriptor fieldDef = (RubyFieldDescriptor) cFieldDescriptor.newInstance(context, Block.NULL_BLOCK); 237 fieldDef.setLabel(context, runtime.newString(label)); 238 fieldDef.setName(context, name); 239 fieldDef.setType(context, type); 240 fieldDef.setNumber(context, number); 241 242 if (!typeClass.isNil()) { 243 if (!(typeClass instanceof RubyString)) { 244 throw runtime.newArgumentError("expected string for type class"); 245 } 246 fieldDef.setSubmsgName(context, typeClass); 247 } 248 return fieldDef; 249 } 250 checkIntTypePrecision(ThreadContext context, Descriptors.FieldDescriptor.Type type, IRubyObject value)251 protected static void checkIntTypePrecision(ThreadContext context, Descriptors.FieldDescriptor.Type type, IRubyObject value) { 252 if (value instanceof RubyFloat) { 253 double doubleVal = RubyNumeric.num2dbl(value); 254 if (Math.floor(doubleVal) != doubleVal) { 255 throw context.runtime.newRangeError("Non-integral floating point value assigned to integer field."); 256 } 257 } 258 if (type == Descriptors.FieldDescriptor.Type.UINT32 || type == Descriptors.FieldDescriptor.Type.UINT64) { 259 if (RubyNumeric.num2dbl(value) < 0) { 260 throw context.runtime.newRangeError("Assigning negative value to unsigned integer field."); 261 } 262 } 263 } 264 isRubyNum(Object value)265 protected static boolean isRubyNum(Object value) { 266 return value instanceof RubyFixnum || value instanceof RubyFloat || value instanceof RubyBignum; 267 } 268 validateTypeClass(ThreadContext context, Descriptors.FieldDescriptor.Type type, IRubyObject value)269 protected static void validateTypeClass(ThreadContext context, Descriptors.FieldDescriptor.Type type, IRubyObject value) { 270 Ruby runtime = context.runtime; 271 if (!(value instanceof RubyModule)) { 272 throw runtime.newArgumentError("TypeClass has incorrect type"); 273 } 274 RubyModule klass = (RubyModule) value; 275 IRubyObject descriptor = klass.getInstanceVariable(DESCRIPTOR_INSTANCE_VAR); 276 if (descriptor.isNil()) { 277 throw runtime.newArgumentError("Type class has no descriptor. Please pass a " + 278 "class or enum as returned by the DescriptorPool."); 279 } 280 if (type == Descriptors.FieldDescriptor.Type.MESSAGE) { 281 if (! (descriptor instanceof RubyDescriptor)) { 282 throw runtime.newArgumentError("Descriptor has an incorrect type"); 283 } 284 } else if (type == Descriptors.FieldDescriptor.Type.ENUM) { 285 if (! (descriptor instanceof RubyEnumDescriptor)) { 286 throw runtime.newArgumentError("Descriptor has an incorrect type"); 287 } 288 } 289 } 290 291 public static String BADNAME_REPLACEMENT = "__DOT__"; 292 293 public static String DESCRIPTOR_INSTANCE_VAR = "@descriptor"; 294 295 public static String EQUAL_SIGN = "="; 296 297 private static BigInteger UINT64_COMPLEMENTARY = new BigInteger("18446744073709551616"); //Math.pow(2, 64) 298 299 private static long UINT_MAX = 0xffffffffl; 300 } 301