• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.FieldDescriptorProto;
37 import com.google.protobuf.Descriptors.FieldDescriptor;
38 import java.math.BigInteger;
39 import org.jcodings.Encoding;
40 import org.jcodings.specific.ASCIIEncoding;
41 import org.jcodings.specific.UTF8Encoding;
42 import org.jruby.*;
43 import org.jruby.common.RubyWarnings;
44 import org.jruby.exceptions.RaiseException;
45 import org.jruby.ext.bigdecimal.RubyBigDecimal;
46 import org.jruby.runtime.Block;
47 import org.jruby.runtime.Helpers;
48 import org.jruby.runtime.ThreadContext;
49 import org.jruby.runtime.builtin.IRubyObject;
50 import org.jruby.util.ByteList;
51 
52 public class Utils {
rubyToFieldType(IRubyObject typeClass)53   public static FieldDescriptor.Type rubyToFieldType(IRubyObject typeClass) {
54     return FieldDescriptor.Type.valueOf(typeClass.asJavaString().toUpperCase());
55   }
56 
fieldTypeToRuby(ThreadContext context, FieldDescriptor.Type type)57   public static IRubyObject fieldTypeToRuby(ThreadContext context, FieldDescriptor.Type type) {
58     return fieldTypeToRuby(context, type.name());
59   }
60 
fieldTypeToRuby(ThreadContext context, FieldDescriptorProto.Type type)61   public static IRubyObject fieldTypeToRuby(ThreadContext context, FieldDescriptorProto.Type type) {
62     return fieldTypeToRuby(context, type.name());
63   }
64 
fieldTypeToRuby(ThreadContext context, String typeName)65   private static IRubyObject fieldTypeToRuby(ThreadContext context, String typeName) {
66 
67     return context.runtime.newSymbol(typeName.replace("TYPE_", "").toLowerCase());
68   }
69 
checkType( ThreadContext context, FieldDescriptor.Type fieldType, String fieldName, IRubyObject value, RubyModule typeClass)70   public static IRubyObject checkType(
71       ThreadContext context,
72       FieldDescriptor.Type fieldType,
73       String fieldName,
74       IRubyObject value,
75       RubyModule typeClass) {
76     Ruby runtime = context.runtime;
77 
78     switch (fieldType) {
79       case SFIXED32:
80       case SFIXED64:
81       case FIXED64:
82       case SINT64:
83       case SINT32:
84       case FIXED32:
85       case INT32:
86       case INT64:
87       case UINT32:
88       case UINT64:
89         if (!isRubyNum(value))
90           throw createExpectedTypeError(context, "number", "integral", fieldName, value);
91 
92         if (value instanceof RubyFloat) {
93           double doubleVal = RubyNumeric.num2dbl(value);
94           if (Math.floor(doubleVal) != doubleVal) {
95             throw runtime.newRangeError(
96                 "Non-integral floating point value assigned to integer field '"
97                     + fieldName
98                     + "' (given "
99                     + value.getMetaClass()
100                     + ").");
101           }
102         }
103         if (fieldType == FieldDescriptor.Type.UINT32
104             || fieldType == FieldDescriptor.Type.UINT64
105             || fieldType == FieldDescriptor.Type.FIXED32
106             || fieldType == FieldDescriptor.Type.FIXED64) {
107           if (((RubyNumeric) value).isNegative()) {
108             throw runtime.newRangeError(
109                 "Assigning negative value to unsigned integer field '"
110                     + fieldName
111                     + "' (given "
112                     + value.getMetaClass()
113                     + ").");
114           }
115         }
116 
117         switch (fieldType) {
118           case INT32:
119             RubyNumeric.num2int(value);
120             break;
121           case UINT32:
122           case FIXED32:
123             num2uint(value);
124             break;
125           case UINT64:
126           case FIXED64:
127             num2ulong(context.runtime, value);
128             break;
129           default:
130             RubyNumeric.num2long(value);
131             break;
132         }
133         break;
134       case FLOAT:
135         if (!isRubyNum(value))
136           throw createExpectedTypeError(context, "number", "float", fieldName, value);
137         break;
138       case DOUBLE:
139         if (!isRubyNum(value))
140           throw createExpectedTypeError(context, "number", "double", fieldName, value);
141         break;
142       case BOOL:
143         if (!(value instanceof RubyBoolean))
144           throw createInvalidTypeError(context, "boolean", fieldName, value);
145         break;
146       case BYTES:
147         value = validateAndEncodeString(context, "bytes", fieldName, value, ASCIIEncoding.INSTANCE);
148         break;
149       case STRING:
150         value =
151             validateAndEncodeString(
152                 context, "string", fieldName, symToString(value), UTF8Encoding.INSTANCE);
153         break;
154       case MESSAGE:
155         if (value.getMetaClass() != typeClass) {
156           // See if we can convert the value before flagging it as invalid
157           String className = typeClass.getName();
158 
159           if (className.equals("Google::Protobuf::Timestamp") && value instanceof RubyTime) {
160             RubyTime rt = (RubyTime) value;
161             RubyHash timestampArgs =
162                 Helpers.constructHash(
163                     runtime,
164                     runtime.newString("nanos"),
165                     rt.nsec(),
166                     false,
167                     runtime.newString("seconds"),
168                     rt.to_i(),
169                     false);
170             return ((RubyClass) typeClass).newInstance(context, timestampArgs, Block.NULL_BLOCK);
171 
172           } else if (className.equals("Google::Protobuf::Duration")
173               && value instanceof RubyNumeric) {
174             IRubyObject seconds;
175             if (value instanceof RubyFloat) {
176               seconds = ((RubyFloat) value).truncate(context);
177             } else if (value instanceof RubyRational) {
178               seconds = ((RubyRational) value).to_i(context);
179             } else if (value instanceof RubyBigDecimal) {
180               seconds = ((RubyBigDecimal) value).to_int(context);
181             } else {
182               seconds = ((RubyInteger) value).to_i();
183             }
184 
185             IRubyObject nanos = ((RubyNumeric) value).remainder(context, RubyFixnum.one(runtime));
186             if (nanos instanceof RubyFloat) {
187               nanos = ((RubyFloat) nanos).op_mul(context, 1000000000);
188             } else if (nanos instanceof RubyRational) {
189               nanos = ((RubyRational) nanos).op_mul(context, runtime.newFixnum(1000000000));
190             } else if (nanos instanceof RubyBigDecimal) {
191               nanos = ((RubyBigDecimal) nanos).op_mul(context, runtime.newFixnum(1000000000));
192             } else {
193               nanos = ((RubyInteger) nanos).op_mul(context, 1000000000);
194             }
195 
196             RubyHash durationArgs =
197                 Helpers.constructHash(
198                     runtime,
199                     runtime.newString("nanos"),
200                     ((RubyNumeric) nanos).round(context),
201                     false,
202                     runtime.newString("seconds"),
203                     seconds,
204                     false);
205             return ((RubyClass) typeClass).newInstance(context, durationArgs, Block.NULL_BLOCK);
206           }
207 
208           // Not able to convert so flag as invalid
209           throw createTypeError(
210               context,
211               "Invalid type "
212                   + value.getMetaClass()
213                   + " to assign to submessage field '"
214                   + fieldName
215                   + "'.");
216         }
217 
218         break;
219       case ENUM:
220         boolean isValid =
221             ((RubyEnumDescriptor) typeClass.getInstanceVariable(DESCRIPTOR_INSTANCE_VAR))
222                 .isValidValue(context, value);
223         if (!isValid) {
224           throw runtime.newRangeError("Unknown symbol value for enum field '" + fieldName + "'.");
225         }
226         break;
227       default:
228         break;
229     }
230     return value;
231   }
232 
wrapPrimaryValue( ThreadContext context, FieldDescriptor.Type fieldType, Object value)233   public static IRubyObject wrapPrimaryValue(
234       ThreadContext context, FieldDescriptor.Type fieldType, Object value) {
235     return wrapPrimaryValue(context, fieldType, value, false);
236   }
237 
wrapPrimaryValue( ThreadContext context, FieldDescriptor.Type fieldType, Object value, boolean encodeBytes)238   public static IRubyObject wrapPrimaryValue(
239       ThreadContext context, FieldDescriptor.Type fieldType, Object value, boolean encodeBytes) {
240     Ruby runtime = context.runtime;
241     switch (fieldType) {
242       case INT32:
243       case SFIXED32:
244       case SINT32:
245         return runtime.newFixnum((Integer) value);
246       case SFIXED64:
247       case SINT64:
248       case INT64:
249         return runtime.newFixnum((Long) value);
250       case FIXED32:
251       case UINT32:
252         return runtime.newFixnum(((Integer) value) & (-1l >>> 32));
253       case FIXED64:
254       case UINT64:
255         long ret = (Long) value;
256         return ret >= 0
257             ? runtime.newFixnum(ret)
258             : RubyBignum.newBignum(runtime, UINT64_COMPLEMENTARY.add(new BigInteger(ret + "")));
259       case FLOAT:
260         return runtime.newFloat((Float) value);
261       case DOUBLE:
262         return runtime.newFloat((Double) value);
263       case BOOL:
264         return (Boolean) value ? runtime.getTrue() : runtime.getFalse();
265       case BYTES:
266         {
267           IRubyObject wrapped =
268               encodeBytes
269                   ? RubyString.newString(
270                       runtime,
271                       new ByteList(((ByteString) value).toByteArray()),
272                       ASCIIEncoding.INSTANCE)
273                   : RubyString.newString(runtime, ((ByteString) value).toByteArray());
274           wrapped.setFrozen(true);
275           return wrapped;
276         }
277       case STRING:
278         {
279           IRubyObject wrapped = runtime.newString(value.toString());
280           wrapped.setFrozen(true);
281           return wrapped;
282         }
283       default:
284         return runtime.getNil();
285     }
286   }
287 
num2uint(IRubyObject value)288   public static int num2uint(IRubyObject value) {
289     long longVal = RubyNumeric.num2long(value);
290     if (longVal > UINT_MAX)
291       throw value
292           .getRuntime()
293           .newRangeError("Integer " + longVal + " too big to convert to 'unsigned int'");
294     long num = longVal;
295     if (num > Integer.MAX_VALUE || num < Integer.MIN_VALUE)
296       // encode to UINT32
297       num = (-longVal ^ (-1l >>> 32)) + 1;
298     RubyNumeric.checkInt(value, num);
299     return (int) num;
300   }
301 
num2ulong(Ruby runtime, IRubyObject value)302   public static long num2ulong(Ruby runtime, IRubyObject value) {
303     if (value instanceof RubyFloat) {
304       RubyBignum bignum = RubyBignum.newBignum(runtime, ((RubyFloat) value).getDoubleValue());
305       return RubyBignum.big2ulong(bignum);
306     } else if (value instanceof RubyBignum) {
307       return RubyBignum.big2ulong((RubyBignum) value);
308     } else {
309       return RubyNumeric.num2long(value);
310     }
311   }
312 
313   /*
314    * Helper to make it easier to support symbols being passed instead of strings
315    */
symToString(IRubyObject sym)316   public static IRubyObject symToString(IRubyObject sym) {
317     if (sym instanceof RubySymbol) {
318       return ((RubySymbol) sym).id2name();
319     }
320     return sym;
321   }
322 
checkNameAvailability(ThreadContext context, String name)323   public static void checkNameAvailability(ThreadContext context, String name) {
324     if (context.runtime.getObject().getConstantAt(name) != null)
325       throw context.runtime.newNameError(name + " is already defined", name);
326   }
327 
isMapEntry(FieldDescriptor fieldDescriptor)328   public static boolean isMapEntry(FieldDescriptor fieldDescriptor) {
329     return fieldDescriptor.getType() == FieldDescriptor.Type.MESSAGE
330         && fieldDescriptor.isRepeated()
331         && fieldDescriptor.getMessageType().getOptions().getMapEntry();
332   }
333 
createTypeError(ThreadContext context, String message)334   public static RaiseException createTypeError(ThreadContext context, String message) {
335     if (cTypeError == null) {
336       cTypeError = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::TypeError");
337     }
338     return RaiseException.from(context.runtime, cTypeError, message);
339   }
340 
createExpectedTypeError( ThreadContext context, String type, String fieldType, String fieldName, IRubyObject value)341   public static RaiseException createExpectedTypeError(
342       ThreadContext context, String type, String fieldType, String fieldName, IRubyObject value) {
343     return createTypeError(
344         context,
345         String.format(
346             EXPECTED_TYPE_ERROR_FORMAT, type, fieldType, fieldName, value.getMetaClass()));
347   }
348 
createInvalidTypeError( ThreadContext context, String fieldType, String fieldName, IRubyObject value)349   public static RaiseException createInvalidTypeError(
350       ThreadContext context, String fieldType, String fieldName, IRubyObject value) {
351     return createTypeError(
352         context,
353         String.format(INVALID_TYPE_ERROR_FORMAT, fieldType, fieldName, value.getMetaClass()));
354   }
355 
isRubyNum(Object value)356   protected static boolean isRubyNum(Object value) {
357     return value instanceof RubyFixnum || value instanceof RubyFloat || value instanceof RubyBignum;
358   }
359 
validateTypeClass( ThreadContext context, FieldDescriptor.Type type, IRubyObject value)360   protected static void validateTypeClass(
361       ThreadContext context, FieldDescriptor.Type type, IRubyObject value) {
362     Ruby runtime = context.runtime;
363     if (!(value instanceof RubyModule)) {
364       throw runtime.newArgumentError("TypeClass has incorrect type");
365     }
366     RubyModule klass = (RubyModule) value;
367     IRubyObject descriptor = klass.getInstanceVariable(DESCRIPTOR_INSTANCE_VAR);
368     if (descriptor.isNil()) {
369       throw runtime.newArgumentError(
370           "Type class has no descriptor. Please pass a "
371               + "class or enum as returned by the DescriptorPool.");
372     }
373     if (type == FieldDescriptor.Type.MESSAGE) {
374       if (!(descriptor instanceof RubyDescriptor)) {
375         throw runtime.newArgumentError("Descriptor has an incorrect type");
376       }
377     } else if (type == FieldDescriptor.Type.ENUM) {
378       if (!(descriptor instanceof RubyEnumDescriptor)) {
379         throw runtime.newArgumentError("Descriptor has an incorrect type");
380       }
381     }
382   }
383 
validateAndEncodeString( ThreadContext context, String fieldType, String fieldName, IRubyObject value, Encoding encoding)384   private static IRubyObject validateAndEncodeString(
385       ThreadContext context,
386       String fieldType,
387       String fieldName,
388       IRubyObject value,
389       Encoding encoding) {
390     if (!(value instanceof RubyString))
391       throw createInvalidTypeError(context, fieldType, fieldName, value);
392 
393     RubyString string = (RubyString) value;
394     if (encoding == UTF8Encoding.INSTANCE && string.getEncoding().isUTF8()) {
395       if (string.isCodeRangeBroken()) {
396         // TODO: For now we only warn for
397         // this case.  We will remove the warning and throw an exception in the 30.x release
398         context
399             .runtime
400             .getWarnings()
401             .warn("String is invalid UTF-8. This will be an error in a future version.");
402       }
403     }
404 
405     value =
406         string.encode(
407             context, context.runtime.getEncodingService().convertEncodingToRubyEncoding(encoding));
408     value.setFrozen(true);
409     return value;
410   }
411 
412   public static final String DESCRIPTOR_INSTANCE_VAR = "@descriptor";
413 
414   public static final String EQUAL_SIGN = "=";
415 
416   private static final BigInteger UINT64_COMPLEMENTARY =
417       new BigInteger("18446744073709551616"); // Math.pow(2, 64)
418 
419   private static final String EXPECTED_TYPE_ERROR_FORMAT =
420       "Expected %s type for %s field '%s' (given %s).";
421   private static final String INVALID_TYPE_ERROR_FORMAT =
422       "Invalid argument for %s field '%s' (given %s).";
423 
424   private static final long UINT_MAX = 0xffffffffl;
425 
426   private static RubyClass cTypeError;
427 }
428