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 }