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