1 package com.uber.nullaway.jarinfer; 2 3 import com.google.common.collect.ImmutableList; 4 import com.google.common.collect.ImmutableSet; 5 import java.io.DataOutputStream; 6 import java.io.IOException; 7 import java.util.ArrayList; 8 import java.util.Collection; 9 import java.util.LinkedHashMap; 10 import java.util.List; 11 import java.util.Map; 12 import java.util.Set; 13 14 /** Simple writer for the astubx format. */ 15 final class StubxWriter { 16 /** 17 * The file magic number for version 0 .astubx files. It should be the first four bytes of any 18 * compatible .astubx file. 19 */ 20 private static final int VERSION_0_FILE_MAGIC_NUMBER = 691458791; 21 22 /** 23 * This method writes the provided list of annotations to a DataOutputStream in the astubx format. 24 * 25 * @param out Output stream. 26 * @param importedAnnotations Mapping of 'custom annotations' to their 'definition classes'. 27 * @param packageAnnotations Map of 'package names' to their 'list of package-level annotations'. 28 * @param typeAnnotations Map of 'type names' to their 'list of type annotations'. 29 * @param methodRecords Map of 'method signatures' to their 'method annotations record'. Method 30 * annotations record consists of return value annotations and argument annotations. {@link 31 * MethodAnnotationsRecord} 32 * @exception IOException On output error. 33 */ write( DataOutputStream out, Map<String, String> importedAnnotations, Map<String, Set<String>> packageAnnotations, Map<String, Set<String>> typeAnnotations, Map<String, MethodAnnotationsRecord> methodRecords)34 static void write( 35 DataOutputStream out, 36 Map<String, String> importedAnnotations, 37 Map<String, Set<String>> packageAnnotations, 38 Map<String, Set<String>> typeAnnotations, 39 Map<String, MethodAnnotationsRecord> methodRecords) 40 throws IOException { 41 // File format version/magic number 42 out.writeInt(VERSION_0_FILE_MAGIC_NUMBER); 43 // Followed by the number of string dictionary entries 44 int numStringEntries = 0; 45 Map<String, Integer> encodingDictionary = new LinkedHashMap<>(); 46 List<String> strings = new ArrayList<String>(); 47 List<Collection<String>> keysets = 48 ImmutableList.of( 49 importedAnnotations.values(), 50 packageAnnotations.keySet(), 51 typeAnnotations.keySet(), 52 methodRecords.keySet()); 53 for (Collection<String> keyset : keysets) { 54 for (String key : keyset) { 55 assert !encodingDictionary.containsKey(key); 56 strings.add(key); 57 encodingDictionary.put(key, numStringEntries); 58 ++numStringEntries; 59 } 60 } 61 out.writeInt(numStringEntries); 62 // Followed by the entries themselves 63 for (String s : strings) { 64 out.writeUTF(s); 65 } 66 // Followed by the number of encoded package annotation records 67 int packageAnnotationSize = 0; 68 for (Map.Entry<String, Set<String>> entry : packageAnnotations.entrySet()) { 69 packageAnnotationSize += entry.getValue().size(); 70 } 71 out.writeInt(packageAnnotationSize); 72 // Followed by those records as pairs of ints pointing into the dictionary 73 for (Map.Entry<String, Set<String>> entry : packageAnnotations.entrySet()) { 74 for (String annot : entry.getValue()) { 75 out.writeInt(encodingDictionary.get(entry.getKey())); 76 out.writeInt(encodingDictionary.get(importedAnnotations.get(annot))); 77 } 78 } 79 // Followed by the number of encoded type annotation records 80 int typeAnnotationSize = 0; 81 for (Map.Entry<String, Set<String>> entry : typeAnnotations.entrySet()) { 82 typeAnnotationSize += entry.getValue().size(); 83 } 84 out.writeInt(typeAnnotationSize); 85 // Followed by those records as pairs of ints pointing into the dictionary 86 for (Map.Entry<String, Set<String>> entry : typeAnnotations.entrySet()) { 87 for (String annot : entry.getValue()) { 88 out.writeInt(encodingDictionary.get(entry.getKey())); 89 out.writeInt(encodingDictionary.get(importedAnnotations.get(annot))); 90 } 91 } 92 // Followed by the number of encoded method return/declaration annotation records 93 int methodAnnotationSize = 0; 94 int methodArgumentRecordsSize = 0; 95 for (Map.Entry<String, MethodAnnotationsRecord> entry : methodRecords.entrySet()) { 96 methodAnnotationSize += entry.getValue().getMethodAnnotations().size(); 97 methodArgumentRecordsSize += entry.getValue().getArgumentAnnotations().size(); 98 } 99 out.writeInt(methodAnnotationSize); 100 // Followed by those records as pairs of ints pointing into the dictionary 101 for (Map.Entry<String, MethodAnnotationsRecord> entry : methodRecords.entrySet()) { 102 for (String annot : entry.getValue().getMethodAnnotations()) { 103 out.writeInt(encodingDictionary.get(entry.getKey())); 104 out.writeInt(encodingDictionary.get(importedAnnotations.get(annot))); 105 } 106 } 107 // Followed by the number of encoded method argument annotation records 108 out.writeInt(methodArgumentRecordsSize); 109 // Followed by those records as a triplet of ints ( 0 and 2 point in the dictionary, 1 is the 110 // argument position) 111 for (Map.Entry<String, MethodAnnotationsRecord> entry : methodRecords.entrySet()) { 112 for (Map.Entry<Integer, ImmutableSet<String>> argEntry : 113 entry.getValue().getArgumentAnnotations().entrySet()) { 114 for (String annot : argEntry.getValue()) { 115 out.writeInt(encodingDictionary.get(entry.getKey())); 116 out.writeInt(argEntry.getKey()); 117 out.writeInt(encodingDictionary.get(importedAnnotations.get(annot))); 118 } 119 } 120 } 121 } 122 } 123