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 33package com.google.protobuf.jruby; 34 35import com.google.protobuf.ByteString; 36import com.google.protobuf.DescriptorProtos; 37import com.google.protobuf.Descriptors; 38import org.jcodings.Encoding; 39import org.jcodings.specific.ASCIIEncoding; 40import org.jcodings.specific.USASCIIEncoding; 41import org.jcodings.specific.UTF8Encoding; 42import org.jruby.*; 43import org.jruby.runtime.Block; 44import org.jruby.runtime.ThreadContext; 45import org.jruby.runtime.builtin.IRubyObject; 46 47import java.math.BigInteger; 48 49public class Utils { 50 public static Descriptors.FieldDescriptor.Type rubyToFieldType(IRubyObject typeClass) { 51 return Descriptors.FieldDescriptor.Type.valueOf(typeClass.asJavaString().toUpperCase()); 52 } 53 54 public static IRubyObject fieldTypeToRuby(ThreadContext context, Descriptors.FieldDescriptor.Type type) { 55 return fieldTypeToRuby(context, type.name()); 56 } 57 58 public static IRubyObject fieldTypeToRuby(ThreadContext context, DescriptorProtos.FieldDescriptorProto.Type type) { 59 return fieldTypeToRuby(context, type.name()); 60 } 61 62 private static IRubyObject fieldTypeToRuby(ThreadContext context, String typeName) { 63 64 return context.runtime.newSymbol(typeName.replace("TYPE_", "").toLowerCase()); 65 } 66 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 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 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 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 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 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 */ 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 */ 223 public static String unescapeIdentifier(String name) { 224 return name.replace(BADNAME_REPLACEMENT, "."); 225 } 226 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 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 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 265 protected static boolean isRubyNum(Object value) { 266 return value instanceof RubyFixnum || value instanceof RubyFloat || value instanceof RubyBignum; 267 } 268 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