001    /*
002     * Copyright 2010-2013 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.jet.descriptors.serialization;
018    
019    import com.google.protobuf.ExtensionRegistryLite;
020    import org.jetbrains.annotations.NotNull;
021    import org.jetbrains.annotations.Nullable;
022    import org.jetbrains.asm4.Type;
023    import org.jetbrains.asm4.commons.Method;
024    import org.jetbrains.jet.lang.resolve.name.FqName;
025    import org.jetbrains.jet.lang.resolve.name.Name;
026    
027    import java.util.ArrayList;
028    import java.util.Arrays;
029    import java.util.List;
030    
031    import static org.jetbrains.asm4.Type.*;
032    
033    public class JavaProtoBufUtil {
034        private JavaProtoBufUtil() {
035        }
036    
037        @Nullable
038        public static Method loadMethodSignature(@NotNull ProtoBuf.Callable proto, @NotNull NameResolver nameResolver) {
039            if (!proto.hasExtension(JavaProtoBuf.methodSignature)) return null;
040            JavaProtoBuf.JavaMethodSignature signature = proto.getExtension(JavaProtoBuf.methodSignature);
041            return new Deserializer(nameResolver).methodSignature(signature);
042        }
043    
044        @Nullable
045        public static Method loadPropertyGetterSignature(@NotNull ProtoBuf.Callable proto, @NotNull NameResolver nameResolver) {
046            if (!proto.hasExtension(JavaProtoBuf.propertySignature)) return null;
047            JavaProtoBuf.JavaPropertySignature propertySignature = proto.getExtension(JavaProtoBuf.propertySignature);
048            return new Deserializer(nameResolver).methodSignature(propertySignature.getGetter());
049        }
050    
051        @Nullable
052        public static Method loadPropertySetterSignature(@NotNull ProtoBuf.Callable proto, @NotNull NameResolver nameResolver) {
053            if (!proto.hasExtension(JavaProtoBuf.propertySignature)) return null;
054            JavaProtoBuf.JavaPropertySignature propertySignature = proto.getExtension(JavaProtoBuf.propertySignature);
055            return new Deserializer(nameResolver).methodSignature(propertySignature.getSetter());
056        }
057    
058        public static class PropertyData {
059            private final Type fieldType;
060            private final String fieldName;
061            private final String syntheticMethodName;
062    
063            public PropertyData(@Nullable Type fieldType, @Nullable String fieldName, @Nullable String syntheticMethodName) {
064                this.fieldType = fieldType;
065                this.fieldName = fieldName;
066                this.syntheticMethodName = syntheticMethodName;
067            }
068    
069            @Nullable
070            public Type getFieldType() {
071                return fieldType;
072            }
073    
074            @Nullable
075            public String getFieldName() {
076                return fieldName;
077            }
078    
079            @Nullable
080            public String getSyntheticMethodName() {
081                return syntheticMethodName;
082            }
083    
084            @Override
085            public String toString() {
086                return fieldName != null ? "Field " + fieldName + " " + fieldType : "Synthetic method " + syntheticMethodName;
087            }
088        }
089    
090        @Nullable
091        public static PropertyData loadPropertyData(@NotNull ProtoBuf.Callable proto, @NotNull NameResolver nameResolver) {
092            if (!proto.hasExtension(JavaProtoBuf.propertySignature)) return null;
093            JavaProtoBuf.JavaPropertySignature propertySignature = proto.getExtension(JavaProtoBuf.propertySignature);
094    
095            if (propertySignature.hasField()) {
096                JavaProtoBuf.JavaFieldSignature field = propertySignature.getField();
097                Type type = new Deserializer(nameResolver).type(field.getType());
098                Name name = nameResolver.getName(field.getName());
099                return new PropertyData(type, name.asString(), null);
100            }
101            else if (propertySignature.hasSyntheticMethodName()) {
102                Name name = nameResolver.getName(propertySignature.getSyntheticMethodName());
103                return new PropertyData(null, null, name.asString());
104            }
105            else {
106                return null;
107            }
108        }
109    
110        @Nullable
111        public static Name loadSrcClassName(@NotNull ProtoBuf.Callable proto, @NotNull NameResolver nameResolver) {
112            if (!proto.hasExtension(JavaProtoBuf.srcClassName)) return null;
113            return nameResolver.getName(proto.getExtension(JavaProtoBuf.srcClassName));
114        }
115    
116        public static boolean isStaticFieldInOuter(@NotNull ProtoBuf.Callable proto) {
117            if (!proto.hasExtension(JavaProtoBuf.propertySignature)) return false;
118            JavaProtoBuf.JavaPropertySignature propertySignature = proto.getExtension(JavaProtoBuf.propertySignature);
119            return propertySignature.hasField() && propertySignature.getField().getIsStaticInOuter();
120        }
121    
122        public static void saveMethodSignature(@NotNull ProtoBuf.Callable.Builder proto, @NotNull Method method, @NotNull NameTable nameTable) {
123            proto.setExtension(JavaProtoBuf.methodSignature, new Serializer(nameTable).methodSignature(method));
124        }
125    
126        public static void savePropertySignature(
127                @NotNull ProtoBuf.Callable.Builder proto,
128                @Nullable Type fieldType,
129                @Nullable String fieldName,
130                boolean isStaticInOuter,
131                @Nullable String syntheticMethodName,
132                @Nullable Method getter,
133                @Nullable Method setter,
134                @NotNull NameTable nameTable
135        ) {
136            proto.setExtension(JavaProtoBuf.propertySignature,
137                    new Serializer(nameTable).propertySignature(fieldType, fieldName, isStaticInOuter, syntheticMethodName, getter, setter));
138        }
139    
140        public static void saveSrcClassName(
141                @NotNull ProtoBuf.Callable.Builder proto,
142                @NotNull Name name,
143                @NotNull NameTable nameTable
144        ) {
145            proto.setExtension(JavaProtoBuf.srcClassName, nameTable.getSimpleNameIndex(name));
146        }
147    
148        private static class Serializer {
149            private final NameTable nameTable;
150    
151            public Serializer(@NotNull NameTable nameTable) {
152                this.nameTable = nameTable;
153            }
154    
155            @NotNull
156            public JavaProtoBuf.JavaMethodSignature methodSignature(@NotNull Method method) {
157                JavaProtoBuf.JavaMethodSignature.Builder signature = JavaProtoBuf.JavaMethodSignature.newBuilder();
158    
159                signature.setName(nameTable.getSimpleNameIndex(Name.guess(method.getName())));
160    
161                signature.setReturnType(type(method.getReturnType()));
162    
163                for (Type type : method.getArgumentTypes()) {
164                    signature.addParameterType(type(type));
165                }
166    
167                return signature.build();
168            }
169    
170            @NotNull
171            public JavaProtoBuf.JavaPropertySignature propertySignature(
172                    @Nullable Type fieldType,
173                    @Nullable String fieldName,
174                    boolean isStaticInOuter,
175                    @Nullable String syntheticMethodName,
176                    @Nullable Method getter,
177                    @Nullable Method setter
178            ) {
179                JavaProtoBuf.JavaPropertySignature.Builder signature = JavaProtoBuf.JavaPropertySignature.newBuilder();
180    
181                if (fieldType != null) {
182                    assert fieldName != null : "Field name shouldn't be null when there's a field type: " + fieldType;
183                    signature.setField(fieldSignature(fieldType, fieldName, isStaticInOuter));
184                }
185    
186                if (syntheticMethodName != null) {
187                    signature.setSyntheticMethodName(nameTable.getSimpleNameIndex(Name.guess(syntheticMethodName)));
188                }
189    
190                if (getter != null) {
191                    signature.setGetter(methodSignature(getter));
192                }
193                if (setter != null) {
194                    signature.setSetter(methodSignature(setter));
195                }
196    
197                return signature.build();
198            }
199    
200            @NotNull
201            public JavaProtoBuf.JavaFieldSignature fieldSignature(@NotNull Type type, @NotNull String name, boolean isStaticInOuter) {
202                JavaProtoBuf.JavaFieldSignature.Builder signature = JavaProtoBuf.JavaFieldSignature.newBuilder();
203                signature.setName(nameTable.getSimpleNameIndex(Name.guess(name)));
204                signature.setType(type(type));
205                if (isStaticInOuter) {
206                    signature.setIsStaticInOuter(true);
207                }
208                return signature.build();
209            }
210    
211            @NotNull
212            public JavaProtoBuf.JavaType type(@NotNull Type givenType) {
213                JavaProtoBuf.JavaType.Builder builder = JavaProtoBuf.JavaType.newBuilder();
214    
215                int arrayDimension = 0;
216                Type type = givenType;
217                while (type.getSort() == Type.ARRAY) {
218                    arrayDimension++;
219                    type = type.getElementType();
220                }
221                if (arrayDimension != 0) {
222                    builder.setArrayDimension(arrayDimension);
223                }
224    
225                if (type.getSort() == Type.OBJECT) {
226                    FqName fqName = internalNameToFqName(type.getInternalName());
227                    builder.setClassFqName(nameTable.getFqNameIndex(fqName));
228                }
229                else {
230                    builder.setPrimitiveType(JavaProtoBuf.JavaType.PrimitiveType.valueOf(type.getSort()));
231                }
232    
233                return builder.build();
234            }
235    
236            @NotNull
237            private static FqName internalNameToFqName(@NotNull String internalName) {
238                return FqName.fromSegments(Arrays.asList(internalName.split("/")));
239            }
240        }
241    
242        private static class Deserializer {
243            // These types are ordered according to their sorts, this is significant for deserialization
244            private static final Type[] PRIMITIVE_TYPES = new Type[]
245                    { VOID_TYPE, BOOLEAN_TYPE, CHAR_TYPE, BYTE_TYPE, SHORT_TYPE, INT_TYPE, FLOAT_TYPE, LONG_TYPE, DOUBLE_TYPE };
246    
247            private final NameResolver nameResolver;
248    
249            public Deserializer(@NotNull NameResolver nameResolver) {
250                this.nameResolver = nameResolver;
251            }
252    
253            @NotNull
254            public Method methodSignature(@NotNull JavaProtoBuf.JavaMethodSignature signature) {
255                String name = nameResolver.getName(signature.getName()).asString();
256    
257                Type returnType = type(signature.getReturnType());
258    
259                int parameters = signature.getParameterTypeCount();
260                Type[] parameterTypes = new Type[parameters];
261                for (int i = 0; i < parameters; i++) {
262                    parameterTypes[i] = type(signature.getParameterType(i));
263                }
264    
265                return new Method(name, returnType, parameterTypes);
266            }
267    
268            @NotNull
269            private Type type(@NotNull JavaProtoBuf.JavaType type) {
270                Type result;
271                if (type.hasPrimitiveType()) {
272                    result = PRIMITIVE_TYPES[type.getPrimitiveType().ordinal()];
273                }
274                else {
275                    result = Type.getObjectType(fqNameToInternalName(nameResolver.getFqName(type.getClassFqName())));
276                }
277    
278                StringBuilder brackets = new StringBuilder(type.getArrayDimension());
279                for (int i = 0; i < type.getArrayDimension(); i++) {
280                    brackets.append('[');
281                }
282    
283                return Type.getType(brackets + result.getDescriptor());
284            }
285    
286            @NotNull
287            private static String fqNameToInternalName(@NotNull FqName fqName) {
288                return fqName.asString().replace('.', '/');
289            }
290        }
291    
292    
293        @NotNull
294        public static ExtensionRegistryLite getExtensionRegistry() {
295            ExtensionRegistryLite registry = ExtensionRegistryLite.newInstance();
296            JavaProtoBuf.registerAllExtensions(registry);
297            return registry;
298        }
299    
300        @NotNull
301        public static ClassData readClassDataFrom(@NotNull String[] data) {
302            return ClassData.read(decodeBytes(data), getExtensionRegistry());
303        }
304    
305        @NotNull
306        public static PackageData readPackageDataFrom(@NotNull String[] data) {
307            return PackageData.read(decodeBytes(data), getExtensionRegistry());
308        }
309    
310        /**
311         * Converts a byte array of serialized data to an array of {@code String} satisfying JVM annotation value argument restrictions:
312         * <ol>
313         *     <li>Each string's length should be no more than 65535</li>
314         *     <li>UTF-8 representation of each string cannot contain bytes in the range 0xf0..0xff</li>
315         * </ol>
316         */
317        @NotNull
318        public static String[] encodeBytes(@NotNull byte[] data) {
319            byte[] bytes = encode8to7(data);
320            // Since 0x0 byte is encoded as two bytes in the Modified UTF-8 (0xc0 0x80) and zero is rather common to byte arrays, we increment
321            // every byte by one modulo max byte value, so that the less common value 0x7f will be represented as two bytes instead.
322            addModuloByte(bytes, 1);
323            return splitBytesToStringArray(bytes);
324        }
325    
326        /**
327         * Converts a byte array to another byte array, every element of which is in the range 0x0..0x7f.
328         *
329         * The conversion is equivalent to the following: input bytes are combined into one long bit string. This big string is then split into
330         * groups of 7 bits. Each resulting 7-bit chunk is then converted to a byte (with a leading bit = 0). The last chunk may have less than
331         * 7 bits, it's prepended with zeros to form a byte. The result is then the array of these bytes, each of which is obviously in the
332         * range 0x0..0x7f.
333         *
334         * Suppose the input of 4 bytes is given (bytes are listed from the beginning to the end, each byte from the least significant bit to
335         * the most significant bit, bits within each byte are numbered):
336         *
337         *     01234567 01234567 01234567 01234567
338         *
339         * The output for this kind of input will be of the following form ('#' represents a zero bit):
340         *
341         *     0123456# 7012345# 6701234# 5670123# 4567####
342         */
343        @NotNull
344        private static byte[] encode8to7(@NotNull byte[] data) {
345            // ceil(data.length * 8 / 7)
346            int resultLength = (data.length * 8 + 6) / 7;
347            byte[] result = new byte[resultLength];
348    
349            // We maintain a pointer to the bit in the input, which is represented by two numbers: index of the current byte in the input and
350            // the index of a bit inside this byte (0 is least significant, 7 is most significant)
351            int byteIndex = 0;
352            int bit = 0;
353    
354            // Write all resulting bytes except the last one. To do this we need to collect exactly 7 bits, starting from the current, into a
355            // byte. In almost all cases these 7 bits can be collected from two parts: the first is several (at least one) most significant bits
356            // from the current byte, the second is several (maybe zero) least significant bits from the next byte. The special case is when the
357            // current bit is the first (least significant) bit in its byte (bit == 0): then the 7 needed bits are just the 7 least significant
358            // of the current byte.
359            for (int i = 0; i < resultLength - 1; i++) {
360                if (bit == 0) {
361                    result[i] = (byte) (data[byteIndex] & 0x7f);
362                    bit = 7;
363                    continue;
364                }
365    
366                int firstPart = (data[byteIndex] & 0xff) >>> bit;
367                int newBit = (bit + 7) & 7;
368                int secondPart = (data[++byteIndex] & ((1 << newBit) - 1)) << 8 - bit;
369                result[i] = (byte) (firstPart + secondPart);
370                bit = newBit;
371            }
372    
373            // Write the last byte, which is just several most significant bits of the last byte in the input, padded with zeros
374            if (resultLength > 0) {
375                assert bit != 0 : "The last chunk cannot start from the input byte since otherwise at least one bit will remain unprocessed";
376                assert byteIndex == data.length - 1 : "The last 7-bit chunk should be encoded from the last input byte: " +
377                                                      byteIndex + " != " + (data.length - 1);
378                result[resultLength - 1] = (byte) ((data[byteIndex] & 0xff) >>> bit);
379            }
380    
381            return result;
382        }
383    
384        private static void addModuloByte(@NotNull byte[] data, int increment) {
385            for (int i = 0, n = data.length; i < n; i++) {
386                data[i] = (byte) ((data[i] + increment) & 0x7f);
387            }
388        }
389    
390        // The maximum possible length of the byte array in the CONSTANT_Utf8_info structure in the bytecode, as per JVMS7 4.4.7
391        private static final int MAX_UTF8_INFO_LENGTH = 65535;
392    
393        /**
394         * Converts a big byte array into the array of strings, where each string, when written to the constant pool table in bytecode, produces
395         * a byte array of not more than MAX_UTF8_INFO_LENGTH. Each byte, except those which are 0x0, occupies exactly one byte in the constant
396         * pool table. Zero bytes occupy two bytes in the table each.
397         *
398         * When strings are constructed from the array of bytes here, they are encoded in the platform's default encoding. This is fine: the
399         * conversion to the Modified UTF-8 (which here would be equivalent to replacing each 0x0 with 0xc0 0x80) will happen later by ASM, when
400         * it writes these strings to the bytecode
401         */
402        @NotNull
403        private static String[] splitBytesToStringArray(@NotNull byte[] data) {
404            List<String> result = new ArrayList<String>();
405    
406            // The offset where the currently processed string starts
407            int off = 0;
408    
409            // The effective length the bytes of the current string would occupy in the constant pool table
410            int len = 0;
411    
412            for (int i = 0, n = data.length; i < n; i++) {
413                // When the effective length reaches at least MAX - 1, we add the current string to the result. Note that the effective length
414                // is at most MAX here: non-zero bytes occupy 1 byte and zero bytes occupy 2 bytes, so we couldn't jump over more than one byte
415                if (len >= MAX_UTF8_INFO_LENGTH - 1) {
416                    assert len <= MAX_UTF8_INFO_LENGTH : "Produced strings cannot contain more than " + MAX_UTF8_INFO_LENGTH + " bytes: " + len;
417                    result.add(new String(data, off, i - off));
418                    off = i;
419                    len = 0;
420                }
421    
422                if (data[i] == 0) {
423                    len += 2;
424                }
425                else {
426                    len++;
427                }
428            }
429    
430            if (len >= 0) {
431                result.add(new String(data, off, data.length - off));
432            }
433    
434            return result.toArray(new String[result.size()]);
435        }
436    
437        /**
438         * Converts encoded array of {@code String} obtained by {@link JavaProtoBufUtil#encodeBytes(byte[])} back to a byte array.
439         */
440        @NotNull
441        public static byte[] decodeBytes(@NotNull String[] data) {
442            byte[] bytes = combineStringArrayIntoBytes(data);
443            // Adding 0x7f modulo max byte value is equivalent to subtracting 1 the same modulo, which is inverse to what happens in encodeBytes
444            addModuloByte(bytes, 0x7f);
445            return decode7to8(bytes);
446        }
447    
448        /**
449         * Combines the array of strings resulted from encodeBytes() into one long byte array
450         */
451        @NotNull
452        private static byte[] combineStringArrayIntoBytes(@NotNull String[] data) {
453            int resultLength = 0;
454            for (String s : data) {
455                assert s.length() <= MAX_UTF8_INFO_LENGTH : "Too long string: " + s.length();
456                resultLength += s.length();
457            }
458    
459            byte[] result = new byte[resultLength];
460            int p = 0;
461            for (String s : data) {
462                for (int i = 0, n = s.length(); i < n; i++) {
463                    result[p++] = (byte) s.charAt(i);
464                }
465            }
466    
467            return result;
468        }
469    
470        /**
471         * Decodes the byte array resulted from encode8to7().
472         *
473         * Each byte of the input array has at most 7 valuable bits of information. So the decoding is equivalent to the following: least
474         * significant 7 bits of all input bytes are combined into one long bit string. This bit string is then split into groups of 8 bits,
475         * each of which forms a byte in the output. If there are any leftovers, they are ignored, since they were added just as a padding and
476         * do not comprise a full byte.
477         *
478         * Suppose the following encoded byte array is given (bits are numbered the same way as in encode8to7() doc):
479         *
480         *     01234567 01234567 01234567 01234567
481         *
482         * The output of the following form would be produced:
483         *
484         *     01234560 12345601 23456012
485         *
486         * Note how all most significant bits and leftovers are dropped, since they don't contain any useful information
487         */
488        @NotNull
489        private static byte[] decode7to8(@NotNull byte[] data) {
490            // floor(7 * data.length / 8)
491            int resultLength = 7 * data.length / 8;
492    
493            byte[] result = new byte[resultLength];
494    
495            // We maintain a pointer to an input bit in the same fashion as in encode8to7(): it's represented as two numbers: index of the
496            // current byte in the input and index of the bit in the byte
497            int byteIndex = 0;
498            int bit = 0;
499    
500            // A resulting byte is comprised of 8 bits, starting from the current bit. Since each input byte only "contains 7 bytes", a
501            // resulting byte always consists of two parts: several most significant bits of the current byte and several least significant bits
502            // of the next byte
503            for (int i = 0; i < resultLength; i++) {
504                int firstPart = (data[byteIndex] & 0xff) >>> bit;
505                byteIndex++;
506                int secondPart = (data[byteIndex] & ((1 << (bit + 1)) - 1)) << 7 - bit;
507                result[i] = (byte) (firstPart + secondPart);
508    
509                if (bit == 6) {
510                    byteIndex++;
511                    bit = 0;
512                }
513                else {
514                    bit++;
515                }
516            }
517    
518            return result;
519        }
520    }