Home | History | Annotate | Download | only in jruby
      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 {
     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