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 }