Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 1 | package aQute.libg.classdump; |
| 2 | |
| 3 | import java.io.*; |
| 4 | import java.lang.reflect.*; |
| 5 | |
| 6 | public class ClassDumper { |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 7 | /** |
| 8 | * <pre> |
| 9 | * ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its |
| 10 | * package. |
| 11 | * ACC_FINAL 0x0010 Declared final; no subclasses allowed. |
| 12 | * ACC_SUPER 0x0020 Treat superclass methods specially when invoked by the |
| 13 | * invokespecial instruction. |
| 14 | * ACC_INTERFACE 0x0200 Is an interface, not a |
| 15 | * class. |
| 16 | * ACC_ABSTRACT 0x0400 Declared abstract; may not be instantiated. |
| 17 | * </pre> |
| 18 | * |
| 19 | * @param mod |
| 20 | */ |
| 21 | final static int ACC_PUBLIC = 0x0001; // Declared public; may be |
| 22 | // accessed |
| 23 | // from outside its package. |
| 24 | final static int ACC_FINAL = 0x0010; // Declared final; no |
| 25 | // subclasses |
| 26 | // allowed. |
| 27 | final static int ACC_SUPER = 0x0020; // Treat superclass methods |
| 28 | // specially when invoked by the |
| 29 | // invokespecial instruction. |
| 30 | final static int ACC_INTERFACE = 0x0200; // Is an interface, not a |
| 31 | // classs |
| 32 | final static int ACC_ABSTRACT = 0x0400; // Declared abstract; may |
| 33 | // not be |
| 34 | // instantiated. |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 35 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 36 | final static class Assoc { |
| 37 | Assoc(byte tag, int a, int b) { |
| 38 | this.tag = tag; |
| 39 | this.a = a; |
| 40 | this.b = b; |
| 41 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 42 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 43 | byte tag; |
| 44 | int a; |
| 45 | int b; |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 46 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 47 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 48 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 49 | final String path; |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 50 | final static String NUM_COLUMN = "%-30s %d%n"; |
| 51 | final static String HEX_COLUMN = "%-30s %x%n"; |
| 52 | final static String STR_COLUMN = "%-30s %s%n"; |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 53 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 54 | PrintStream ps = System.err; |
| 55 | Object[] pool; |
| 56 | InputStream in; |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 57 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 58 | public ClassDumper(String path) throws Exception { |
| 59 | this(path, new FileInputStream(new File(path))); |
| 60 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 61 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 62 | public ClassDumper(String path, InputStream in) throws IOException { |
| 63 | this.path = path; |
| 64 | this.in = in; |
| 65 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 66 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 67 | public void dump(PrintStream ps) throws Exception { |
| 68 | if (ps != null) |
| 69 | this.ps = ps; |
| 70 | DataInputStream din = new DataInputStream(in); |
| 71 | parseClassFile(din); |
| 72 | din.close(); |
| 73 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 74 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 75 | void parseClassFile(DataInputStream in) throws IOException { |
| 76 | int magic = in.readInt(); |
| 77 | if (magic != 0xCAFEBABE) |
| 78 | throw new IOException("Not a valid class file (no CAFEBABE header)"); |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 79 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 80 | ps.printf(HEX_COLUMN, "magic", magic); |
| 81 | int minor = in.readUnsignedShort(); // minor version |
| 82 | int major = in.readUnsignedShort(); // major version |
| 83 | ps.printf(STR_COLUMN, "version", "" + major + "." + minor); |
| 84 | int pool_size = in.readUnsignedShort(); |
| 85 | ps.printf(NUM_COLUMN, "pool size", pool_size); |
| 86 | pool = new Object[pool_size]; |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 87 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 88 | process: for (int poolIndex = 1; poolIndex < pool_size; poolIndex++) { |
| 89 | byte tag = in.readByte(); |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 90 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 91 | switch (tag) { |
| 92 | case 0 : |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 93 | ps.printf("%30d tag (0)%n", poolIndex); |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 94 | break process; |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 95 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 96 | case 1 : |
| 97 | String name = in.readUTF(); |
| 98 | pool[poolIndex] = name; |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 99 | ps.printf("%30d tag(1) utf8 '%s'%n", poolIndex, name); |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 100 | break; |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 101 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 102 | case 2 : |
| 103 | throw new IOException("Invalid tag " + tag); |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 104 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 105 | case 3 : |
| 106 | int i = in.readInt(); |
| 107 | pool[poolIndex] = Integer.valueOf(i); |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 108 | ps.printf("%30d tag(3) int %s%n", poolIndex, i); |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 109 | break; |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 110 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 111 | case 4 : |
| 112 | float f = in.readFloat(); |
| 113 | pool[poolIndex] = new Float(f); |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 114 | ps.printf("%30d tag(4) float %s%n", poolIndex, f); |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 115 | break; |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 116 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 117 | // For some insane optimization reason are |
| 118 | // the long and the double two entries in the |
| 119 | // constant pool. See 4.4.5 |
| 120 | case 5 : |
| 121 | long l = in.readLong(); |
| 122 | pool[poolIndex] = Long.valueOf(l); |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 123 | ps.printf("%30d tag(5) long %s%n", poolIndex, l); |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 124 | poolIndex++; |
| 125 | break; |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 126 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 127 | case 6 : |
| 128 | double d = in.readDouble(); |
| 129 | pool[poolIndex] = new Double(d); |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 130 | ps.printf("%30d tag(6) double %s%n", poolIndex, d); |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 131 | poolIndex++; |
| 132 | break; |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 133 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 134 | case 7 : |
| 135 | int class_index = in.readUnsignedShort(); |
| 136 | pool[poolIndex] = Integer.valueOf(class_index); |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 137 | ps.printf("%30d tag(7) constant classs %d%n", poolIndex, class_index); |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 138 | break; |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 139 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 140 | case 8 : |
| 141 | int string_index = in.readUnsignedShort(); |
| 142 | pool[poolIndex] = Integer.valueOf(string_index); |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 143 | ps.printf("%30d tag(8) constant string %d%n", poolIndex, string_index); |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 144 | break; |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 145 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 146 | case 9 : // Field ref |
| 147 | class_index = in.readUnsignedShort(); |
| 148 | int name_and_type_index = in.readUnsignedShort(); |
| 149 | pool[poolIndex] = new Assoc((byte) 9, class_index, name_and_type_index); |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 150 | ps.printf("%30d tag(9) field ref %d/%d%n", poolIndex, class_index, name_and_type_index); |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 151 | break; |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 152 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 153 | case 10 : // Method ref |
| 154 | class_index = in.readUnsignedShort(); |
| 155 | name_and_type_index = in.readUnsignedShort(); |
| 156 | pool[poolIndex] = new Assoc((byte) 10, class_index, name_and_type_index); |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 157 | ps.printf("%30d tag(10) method ref %d/%d%n", poolIndex, class_index, name_and_type_index); |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 158 | break; |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 159 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 160 | case 11 : // Interface and Method ref |
| 161 | class_index = in.readUnsignedShort(); |
| 162 | name_and_type_index = in.readUnsignedShort(); |
| 163 | pool[poolIndex] = new Assoc((byte) 11, class_index, name_and_type_index); |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 164 | ps.printf("%30d tag(11) interface and method ref %d/%d%n", poolIndex, class_index, |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 165 | name_and_type_index); |
| 166 | break; |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 167 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 168 | // Name and Type |
| 169 | case 12 : |
| 170 | int name_index = in.readUnsignedShort(); |
| 171 | int descriptor_index = in.readUnsignedShort(); |
| 172 | pool[poolIndex] = new Assoc(tag, name_index, descriptor_index); |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 173 | ps.printf("%30d tag(12) name and type %d/%d%n", poolIndex, name_index, descriptor_index); |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 174 | break; |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 175 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 176 | default : |
| 177 | throw new IllegalArgumentException("Unknown tag: " + tag); |
| 178 | } |
| 179 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 180 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 181 | int access = in.readUnsignedShort(); // access |
| 182 | printAccess(access); |
| 183 | int this_class = in.readUnsignedShort(); |
| 184 | int super_class = in.readUnsignedShort(); |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 185 | ps.printf("%-30s %x %s(#%d)%n", "this_class", access, pool[this_class], this_class); |
| 186 | ps.printf("%-30s %s(#%d)%n", "super_class", pool[super_class], super_class); |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 187 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 188 | int interfaces_count = in.readUnsignedShort(); |
| 189 | ps.printf(NUM_COLUMN, "interface count", interfaces_count); |
| 190 | for (int i = 0; i < interfaces_count; i++) { |
| 191 | int interface_index = in.readUnsignedShort(); |
| 192 | ps.printf("%-30s interface %s(#%d)", "interface count", pool[interface_index], interfaces_count); |
| 193 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 194 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 195 | int field_count = in.readUnsignedShort(); |
| 196 | ps.printf(NUM_COLUMN, "field count", field_count); |
| 197 | for (int i = 0; i < field_count; i++) { |
| 198 | access = in.readUnsignedShort(); // access |
| 199 | printAccess(access); |
| 200 | int name_index = in.readUnsignedShort(); |
| 201 | int descriptor_index = in.readUnsignedShort(); |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 202 | ps.printf("%-30s %x %s(#%d) %s(#%d)%n", "field def", access, pool[name_index], name_index, |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 203 | pool[descriptor_index], descriptor_index); |
| 204 | doAttributes(in, " "); |
| 205 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 206 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 207 | int method_count = in.readUnsignedShort(); |
| 208 | ps.printf(NUM_COLUMN, "method count", method_count); |
| 209 | for (int i = 0; i < method_count; i++) { |
| 210 | int access_flags = in.readUnsignedShort(); |
| 211 | printAccess(access_flags); |
| 212 | int name_index = in.readUnsignedShort(); |
| 213 | int descriptor_index = in.readUnsignedShort(); |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 214 | ps.printf("%-30s %x %s(#%d) %s(#%d)%n", "method def", access_flags, pool[name_index], name_index, |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 215 | pool[descriptor_index], descriptor_index); |
| 216 | doAttributes(in, " "); |
| 217 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 218 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 219 | doAttributes(in, ""); |
| 220 | if (in.read() >= 0) |
| 221 | ps.printf("Extra bytes follow ..."); |
| 222 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 223 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 224 | /** |
| 225 | * Called for each attribute in the class, field, or method. |
| 226 | * |
| 227 | * @param in |
| 228 | * The stream |
| 229 | * @throws IOException |
| 230 | */ |
| 231 | private void doAttributes(DataInputStream in, String indent) throws IOException { |
| 232 | int attribute_count = in.readUnsignedShort(); |
| 233 | ps.printf(NUM_COLUMN, indent + "attribute count", attribute_count); |
| 234 | for (int j = 0; j < attribute_count; j++) { |
| 235 | doAttribute(in, indent + j + ": "); |
| 236 | } |
| 237 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 238 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 239 | /** |
| 240 | * Process a single attribute, if not recognized, skip it. |
| 241 | * |
| 242 | * @param in |
| 243 | * the data stream |
| 244 | * @throws IOException |
| 245 | */ |
| 246 | private void doAttribute(DataInputStream in, String indent) throws IOException { |
| 247 | int attribute_name_index = in.readUnsignedShort(); |
| 248 | long attribute_length = in.readInt(); |
| 249 | attribute_length &= 0xFFFF; |
| 250 | String attributeName = (String) pool[attribute_name_index]; |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 251 | ps.printf("%-30s %s(#%d)%n", indent + "attribute", attributeName, attribute_name_index); |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 252 | if ("RuntimeVisibleAnnotations".equals(attributeName)) |
| 253 | doAnnotations(in, indent); |
| 254 | else if ("SourceFile".equals(attributeName)) |
| 255 | doSourceFile(in, indent); |
| 256 | else if ("Code".equals(attributeName)) |
| 257 | doCode(in, indent); |
| 258 | else if ("LineNumberTable".equals(attributeName)) |
| 259 | doLineNumberTable(in, indent); |
| 260 | else if ("LocalVariableTable".equals(attributeName)) |
| 261 | doLocalVariableTable(in, indent); |
| 262 | else if ("InnerClasses".equals(attributeName)) |
| 263 | doInnerClasses(in, indent); |
| 264 | else if ("Exceptions".equals(attributeName)) |
| 265 | doExceptions(in, indent); |
| 266 | else if ("EnclosingMethod".equals(attributeName)) |
| 267 | doEnclosingMethod(in, indent); |
| 268 | else if ("Signature".equals(attributeName)) |
| 269 | doSignature(in, indent); |
| 270 | else if ("Synthetic".equals(attributeName)) |
| 271 | ; // Is empty! |
| 272 | else if ("Deprecated".equals(attributeName)) |
| 273 | ; // Is Empty |
| 274 | else { |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 275 | ps.printf("%-30s %d%n", indent + "Unknown attribute, skipping", attribute_length); |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 276 | if (attribute_length > 0x7FFFFFFF) { |
| 277 | throw new IllegalArgumentException("Attribute > 2Gb"); |
| 278 | } |
| 279 | byte buffer[] = new byte[(int) attribute_length]; |
| 280 | in.readFully(buffer); |
| 281 | printHex(buffer); |
| 282 | } |
| 283 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 284 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 285 | /** |
| 286 | * <pre> |
| 287 | * Signature_attribute { |
| 288 | * u2 attribute_name_index; |
| 289 | * u4 attribute_length; |
| 290 | * u2 signature_index; |
| 291 | * } |
| 292 | * </pre> |
| 293 | * |
| 294 | * @param in |
| 295 | * @param indent |
| 296 | */ |
| 297 | void doSignature(DataInputStream in, String indent) throws IOException { |
| 298 | int signature_index = in.readUnsignedShort(); |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 299 | ps.printf("%-30s %s(#%d)%n", indent + "signature", pool[signature_index], signature_index); |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 300 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 301 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 302 | /** |
| 303 | * <pre> |
| 304 | * EnclosingMethod_attribute { |
| 305 | * u2 attribute_name_index; |
| 306 | * u4 attribute_length; |
| 307 | * u2 class_index |
| 308 | * u2 method_index; |
| 309 | * } |
| 310 | * |
| 311 | * </pre> |
| 312 | */ |
| 313 | void doEnclosingMethod(DataInputStream in, String indent) throws IOException { |
| 314 | int class_index = in.readUnsignedShort(); |
| 315 | int method_index = in.readUnsignedShort(); |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 316 | ps.printf("%-30s %s(#%d/c) %s%n", // |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 317 | indent + "enclosing method", // |
| 318 | pool[((Integer) pool[class_index]).intValue()], // |
| 319 | class_index, // |
| 320 | (method_index == 0 ? "<>" : pool[method_index])); |
| 321 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 322 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 323 | /** |
| 324 | * <pre> |
| 325 | * Exceptions_attribute { |
| 326 | * u2 attribute_name_index; |
| 327 | * u4 attribute_length; |
| 328 | * u2 number_of_exceptions; |
| 329 | * u2 exception_index_table[number_of_exceptions]; |
| 330 | * } |
| 331 | * </pre> |
| 332 | * |
| 333 | * @param in |
| 334 | * @param indent |
| 335 | */ |
| 336 | private void doExceptions(DataInputStream in, String indent) throws IOException { |
| 337 | int number_of_exceptions = in.readUnsignedShort(); |
| 338 | ps.printf(NUM_COLUMN, indent + "number of exceptions", number_of_exceptions); |
| 339 | StringBuilder sb = new StringBuilder(); |
| 340 | String del = ""; |
| 341 | for (int i = 0; i < number_of_exceptions; i++) { |
| 342 | int exception_index_table = in.readUnsignedShort(); |
| 343 | sb.append(del); |
| 344 | sb.append(pool[((Integer) pool[exception_index_table])]); |
| 345 | sb.append("(#"); |
| 346 | sb.append(exception_index_table); |
| 347 | sb.append("/c)"); |
| 348 | del = ", "; |
| 349 | } |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 350 | ps.printf("%-30s %d: %s%n", indent + "exceptions", number_of_exceptions, sb); |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 351 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 352 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 353 | /** |
| 354 | * <pre> |
| 355 | * Code_attribute { |
| 356 | * u2 attribute_name_index; |
| 357 | * u4 attribute_length; |
| 358 | * u2 max_stack; |
| 359 | * u2 max_locals; |
| 360 | * u4 code_length; |
| 361 | * u1 code[code_length]; |
| 362 | * u2 exception_table_length; |
| 363 | * { u2 start_pc; |
| 364 | * u2 end_pc; |
| 365 | * u2 handler_pc; |
| 366 | * u2 catch_type; |
| 367 | * } exception_table[exception_table_length]; |
| 368 | * u2 attributes_count; |
| 369 | * attribute_info attributes[attributes_count]; |
| 370 | * } |
| 371 | * </pre> |
| 372 | * |
| 373 | * @param in |
| 374 | * @param pool |
| 375 | * @throws IOException |
| 376 | */ |
| 377 | private void doCode(DataInputStream in, String indent) throws IOException { |
| 378 | int max_stack = in.readUnsignedShort(); |
| 379 | int max_locals = in.readUnsignedShort(); |
| 380 | int code_length = in.readInt(); |
| 381 | ps.printf(NUM_COLUMN, indent + "max_stack", max_stack); |
| 382 | ps.printf(NUM_COLUMN, indent + "max_locals", max_locals); |
| 383 | ps.printf(NUM_COLUMN, indent + "code_length", code_length); |
| 384 | byte code[] = new byte[code_length]; |
| 385 | in.readFully(code); |
| 386 | printHex(code); |
| 387 | int exception_table_length = in.readUnsignedShort(); |
| 388 | ps.printf(NUM_COLUMN, indent + "exception_table_length", exception_table_length); |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 389 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 390 | for (int i = 0; i < exception_table_length; i++) { |
| 391 | int start_pc = in.readUnsignedShort(); |
| 392 | int end_pc = in.readUnsignedShort(); |
| 393 | int handler_pc = in.readUnsignedShort(); |
| 394 | int catch_type = in.readUnsignedShort(); |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 395 | ps.printf("%-30s %d/%d/%d/%d%n", indent + "exception_table", start_pc, end_pc, handler_pc, catch_type); |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 396 | } |
| 397 | doAttributes(in, indent + " "); |
| 398 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 399 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 400 | /** |
| 401 | * We must find Class.forName references ... |
| 402 | * |
| 403 | * @param code |
| 404 | */ |
| 405 | protected void printHex(byte[] code) { |
| 406 | int index = 0; |
| 407 | while (index < code.length) { |
| 408 | StringBuilder sb = new StringBuilder(); |
| 409 | for (int i = 0; i < 16 && index < code.length; i++) { |
| 410 | String s = Integer.toHexString((0xFF & code[index++])).toUpperCase(); |
| 411 | if (s.length() == 1) |
| 412 | sb.append("0"); |
| 413 | sb.append(s); |
| 414 | sb.append(" "); |
| 415 | } |
| 416 | ps.printf(STR_COLUMN, "", sb.toString()); |
| 417 | } |
| 418 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 419 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 420 | private void doSourceFile(DataInputStream in, String indent) throws IOException { |
| 421 | int sourcefile_index = in.readUnsignedShort(); |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 422 | ps.printf("%-30s %s(#%d)%n", indent + "Source file", pool[sourcefile_index], sourcefile_index); |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 423 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 424 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 425 | private void doAnnotations(DataInputStream in, String indent) throws IOException { |
| 426 | int num_annotations = in.readUnsignedShort(); // # of annotations |
| 427 | ps.printf(NUM_COLUMN, indent + "Number of annotations", num_annotations); |
| 428 | for (int a = 0; a < num_annotations; a++) { |
| 429 | doAnnotation(in, indent); |
| 430 | } |
| 431 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 432 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 433 | private void doAnnotation(DataInputStream in, String indent) throws IOException { |
| 434 | int type_index = in.readUnsignedShort(); |
| 435 | ps.printf("%-30s %s(#%d)", indent + "type", pool[type_index], type_index); |
| 436 | int num_element_value_pairs = in.readUnsignedShort(); |
| 437 | ps.printf(NUM_COLUMN, indent + "num_element_value_pairs", num_element_value_pairs); |
| 438 | for (int v = 0; v < num_element_value_pairs; v++) { |
| 439 | int element_name_index = in.readUnsignedShort(); |
| 440 | ps.printf(NUM_COLUMN, indent + "element_name_index", element_name_index); |
| 441 | doElementValue(in, indent); |
| 442 | } |
| 443 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 444 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 445 | private void doElementValue(DataInputStream in, String indent) throws IOException { |
| 446 | int tag = in.readUnsignedByte(); |
| 447 | switch (tag) { |
| 448 | case 'B' : |
| 449 | case 'C' : |
| 450 | case 'D' : |
| 451 | case 'F' : |
| 452 | case 'I' : |
| 453 | case 'J' : |
| 454 | case 'S' : |
| 455 | case 'Z' : |
| 456 | case 's' : |
| 457 | int const_value_index = in.readUnsignedShort(); |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 458 | ps.printf("%-30s %c %s(#%d)%n", indent + "element value", tag, pool[const_value_index], |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 459 | const_value_index); |
| 460 | break; |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 461 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 462 | case 'e' : |
| 463 | int type_name_index = in.readUnsignedShort(); |
| 464 | int const_name_index = in.readUnsignedShort(); |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 465 | ps.printf("%-30s %c %s(#%d) %s(#%d)%n", indent + "type+const", tag, pool[type_name_index], |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 466 | type_name_index, pool[const_name_index], const_name_index); |
| 467 | break; |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 468 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 469 | case 'c' : |
| 470 | int class_info_index = in.readUnsignedShort(); |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 471 | ps.printf("%-30s %c %s(#%d)%n", indent + "element value", tag, pool[class_info_index], class_info_index); |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 472 | break; |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 473 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 474 | case '@' : |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 475 | ps.printf("%-30s %c%n", indent + "sub annotation", tag); |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 476 | doAnnotation(in, indent); |
| 477 | break; |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 478 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 479 | case '[' : |
| 480 | int num_values = in.readUnsignedShort(); |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 481 | ps.printf("%-30s %c num_values=%d%n", indent + "sub element value", tag, num_values); |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 482 | for (int i = 0; i < num_values; i++) { |
| 483 | doElementValue(in, indent); |
| 484 | } |
| 485 | break; |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 486 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 487 | default : |
| 488 | throw new IllegalArgumentException("Invalid value for Annotation ElementValue tag " + tag); |
| 489 | } |
| 490 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 491 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 492 | /** |
| 493 | * <pre> |
| 494 | * LineNumberTable_attribute { |
| 495 | * u2 attribute_name_index; |
| 496 | * u4 attribute_length; |
| 497 | * u2 line_number_table_length; |
| 498 | * { u2 start_pc; |
| 499 | * u2 line_number; |
| 500 | * } line_number_table[line_number_table_length]; |
| 501 | * } |
| 502 | * |
| 503 | * </pre> |
| 504 | */ |
| 505 | void doLineNumberTable(DataInputStream in, String indent) throws IOException { |
| 506 | int line_number_table_length = in.readUnsignedShort(); |
| 507 | ps.printf(NUM_COLUMN, indent + "line number table length", line_number_table_length); |
| 508 | StringBuilder sb = new StringBuilder(); |
| 509 | for (int i = 0; i < line_number_table_length; i++) { |
| 510 | int start_pc = in.readUnsignedShort(); |
| 511 | int line_number = in.readUnsignedShort(); |
| 512 | sb.append(start_pc); |
| 513 | sb.append("/"); |
| 514 | sb.append(line_number); |
| 515 | sb.append(" "); |
| 516 | } |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 517 | ps.printf("%-30s %d: %s%n", indent + "line number table", line_number_table_length, sb); |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 518 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 519 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 520 | /** |
| 521 | * <pre> |
| 522 | * LocalVariableTable_attribute { |
| 523 | * u2 attribute_name_index; |
| 524 | * u4 attribute_length; |
| 525 | * u2 local_variable_table_length; |
| 526 | * { u2 start_pc; |
| 527 | * u2 length; |
| 528 | * u2 name_index; |
| 529 | * u2 descriptor_index; |
| 530 | * u2 index; |
| 531 | * } local_variable_table[local_variable_table_length]; |
| 532 | * } |
| 533 | * </pre> |
| 534 | */ |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 535 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 536 | void doLocalVariableTable(DataInputStream in, String indent) throws IOException { |
| 537 | int local_variable_table_length = in.readUnsignedShort(); |
| 538 | ps.printf(NUM_COLUMN, indent + "local variable table length", local_variable_table_length); |
| 539 | for (int i = 0; i < local_variable_table_length; i++) { |
| 540 | int start_pc = in.readUnsignedShort(); |
| 541 | int length = in.readUnsignedShort(); |
| 542 | int name_index = in.readUnsignedShort(); |
| 543 | int descriptor_index = in.readUnsignedShort(); |
| 544 | int index = in.readUnsignedShort(); |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 545 | ps.printf("%-30s %d: %d/%d %s(#%d) %s(#%d)%n", indent, index, start_pc, length, pool[name_index], |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 546 | name_index, pool[descriptor_index], descriptor_index); |
| 547 | } |
| 548 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 549 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 550 | /** |
| 551 | * <pre> |
| 552 | * InnerClasses_attribute { |
| 553 | * u2 attribute_name_index; |
| 554 | * u4 attribute_length; |
| 555 | * u2 number_of_classes; |
| 556 | * { u2 inner_class_info_index; |
| 557 | * u2 outer_class_info_index; |
| 558 | * u2 inner_name_index; |
| 559 | * u2 inner_class_access_flags; |
| 560 | * } classes[number_of_classes]; |
| 561 | * } |
| 562 | * </pre> |
| 563 | */ |
| 564 | void doInnerClasses(DataInputStream in, String indent) throws IOException { |
| 565 | int number_of_classes = in.readUnsignedShort(); |
| 566 | ps.printf(NUM_COLUMN, indent + "number of classes", number_of_classes); |
| 567 | for (int i = 0; i < number_of_classes; i++) { |
| 568 | int inner_class_info_index = in.readUnsignedShort(); |
| 569 | int outer_class_info_index = in.readUnsignedShort(); |
| 570 | int inner_name_index = in.readUnsignedShort(); |
| 571 | int inner_class_access_flags = in.readUnsignedShort(); |
| 572 | printAccess(inner_class_access_flags); |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 573 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 574 | String iname = "<>"; |
| 575 | String oname = iname; |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 576 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 577 | if (inner_class_info_index != 0) |
| 578 | iname = (String) pool[((Integer) pool[inner_class_info_index]).intValue()]; |
| 579 | if (outer_class_info_index != 0) |
| 580 | oname = (String) pool[((Integer) pool[outer_class_info_index]).intValue()]; |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 581 | |
Stuart McCulloch | 7adbc95 | 2012-07-12 22:12:58 +0000 | [diff] [blame^] | 582 | ps.printf("%-30s %d: %x %s(#%d/c) %s(#%d/c) %s(#%d) %n", indent, i, inner_class_access_flags, iname, |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 583 | inner_class_info_index, oname, outer_class_info_index, pool[inner_name_index], inner_name_index); |
| 584 | } |
| 585 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 586 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 587 | void printClassAccess(int mod) { |
| 588 | ps.printf("%-30s", "Class Access"); |
| 589 | if ((ACC_PUBLIC & mod) != 0) |
| 590 | ps.print(" public"); |
| 591 | if ((ACC_FINAL & mod) != 0) |
| 592 | ps.print(" final"); |
| 593 | if ((ACC_SUPER & mod) != 0) |
| 594 | ps.print(" super"); |
| 595 | if ((ACC_INTERFACE & mod) != 0) |
| 596 | ps.print(" interface"); |
| 597 | if ((ACC_ABSTRACT & mod) != 0) |
| 598 | ps.print(" abstract"); |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 599 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 600 | ps.println(); |
| 601 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 602 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 603 | void printAccess(int mod) { |
| 604 | ps.printf("%-30s", "Access"); |
| 605 | if (Modifier.isStatic(mod)) |
| 606 | ps.print(" static"); |
| 607 | if (Modifier.isAbstract(mod)) |
| 608 | ps.print(" abstract"); |
| 609 | if (Modifier.isPublic(mod)) |
| 610 | ps.print(" public"); |
| 611 | if (Modifier.isFinal(mod)) |
| 612 | ps.print(" final"); |
| 613 | if (Modifier.isInterface(mod)) |
| 614 | ps.print(" interface"); |
| 615 | if (Modifier.isNative(mod)) |
| 616 | ps.print(" native"); |
| 617 | if (Modifier.isPrivate(mod)) |
| 618 | ps.print(" private"); |
| 619 | if (Modifier.isProtected(mod)) |
| 620 | ps.print(" protected"); |
| 621 | if (Modifier.isStrict(mod)) |
| 622 | ps.print(" strict"); |
| 623 | if (Modifier.isSynchronized(mod)) |
| 624 | ps.print(" synchronized"); |
| 625 | if (Modifier.isTransient(mod)) |
| 626 | ps.print(" transient"); |
| 627 | if (Modifier.isVolatile(mod)) |
| 628 | ps.print(" volatile"); |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 629 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 630 | ps.println(); |
| 631 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 632 | |
Stuart McCulloch | 2286f23 | 2012-06-15 13:27:53 +0000 | [diff] [blame] | 633 | public static void main(String args[]) throws Exception { |
| 634 | if (args.length == 0) { |
| 635 | System.err.println("clsd <class file>+"); |
| 636 | } |
| 637 | for (int i = 0; i < args.length; i++) { |
| 638 | File f = new File(args[i]); |
| 639 | if (!f.isFile()) |
| 640 | System.err.println("File does not exist or is directory " + f); |
| 641 | else { |
| 642 | ClassDumper cd = new ClassDumper(args[i]); |
| 643 | cd.dump(null); |
| 644 | } |
| 645 | } |
| 646 | } |
Stuart McCulloch | bb01437 | 2012-06-07 21:57:32 +0000 | [diff] [blame] | 647 | |
| 648 | } |