1
|
package jdeserialize;
|
2
|
|
3
|
import java.io.*;
|
4
|
import java.util.*;
|
5
|
import java.util.regex.*;
|
6
|
|
7
|
/**
|
8
|
* The main user-facing class for the jdeserialize library. Also the implementation of
|
9
|
* the command-line tool.<br/>
|
10
|
* <br/>
|
11
|
* Library:<br/>
|
12
|
* <br/>
|
13
|
* The jdeserialize class parses the stream (method run()). From there, call the
|
14
|
* getContent() method to get an itemized list of all items written to the stream,
|
15
|
* or getHandleMaps() to get a list of all handle->content maps generated during parsing.
|
16
|
* The objects are generally instances that implement the interface "content"; see the
|
17
|
* documentation of various implementors to get more information about the inner
|
18
|
* representations.<br/>
|
19
|
* <br/>
|
20
|
* To enable debugging on stdout, use the enableDebug() or disableDebug() options. <br/>
|
21
|
* <br/>
|
22
|
* <br/>
|
23
|
* Command-line tool: <br/>
|
24
|
* <br/>
|
25
|
* The tool reads in a set of files and generates configurable output on stdout. The
|
26
|
* primary output consists of three separate stages. The first stage is a textual
|
27
|
* description of every piece of content in the stream, in the order it was written.
|
28
|
* There is generally a one-to-one mapping between ObjectOutputStream.writeXXX() calls and
|
29
|
* items printed in the first stage. The first stage may be suppressed with the
|
30
|
* -nocontent command-line option. <br/>
|
31
|
* <br/>
|
32
|
* The second stage is a list of every class declaration serialized in the file. These
|
33
|
* are formatted as normal Java language class declarations. Several options are
|
34
|
* available to govern this stage, including -filter, -showarrays, -noclasses, and
|
35
|
* -fixnames. <br/>
|
36
|
* <br/>
|
37
|
* The third stage is a dump of every instance embedded inside the stream, including
|
38
|
* textual descriptions of field values. This is useful for casual viewing of class data.
|
39
|
* To suppress this stage, use -noinstances. <br/>
|
40
|
* <br/>
|
41
|
* You can also get debugging information generated during the parse phase by supplying
|
42
|
* -debug.
|
43
|
* <br/>
|
44
|
* The data from block data objects can be extracted with the -blockdata <file> option.
|
45
|
* Additionally, a manifest describing the size of each individual block can be generated
|
46
|
* with the -blockdatamanifest <file> option.
|
47
|
* <br/>
|
48
|
* References: <br/>
|
49
|
* - Java Object Serialization Specification ch. 6 (Object Serialization Stream
|
50
|
* Protocol): <br/>
|
51
|
* http://download.oracle.com/javase/6/docs/platform/serialization/spec/protocol.html <br/>
|
52
|
* - "Modified UTF-8 Strings" within the JNI specification:
|
53
|
* http://download.oracle.com/javase/1.5.0/docs/guide/jni/spec/types.html#wp16542 <br/>
|
54
|
* - "Inner Classes Specification" within the JDK 1.1.8 docs:
|
55
|
* http://java.sun.com/products/archive/jdk/1.1/ <br/>
|
56
|
* - "Java Language Specification", third edition, particularly section 3:
|
57
|
* http://java.sun.com/docs/books/jls/third_edition/html/j3TOC.html <br/>
|
58
|
*
|
59
|
* @see content
|
60
|
*/
|
61
|
public class jdeserialize {
|
62
|
public static final long serialVersionUID = 78790714646095L;
|
63
|
public static final String INDENT = " ";
|
64
|
public static final int CODEWIDTH = 90;
|
65
|
public static final String linesep = System.getProperty("line.separator");
|
66
|
public static final String[] keywords = new String[] {
|
67
|
"abstract", "continue", "for", "new", "switch", "assert", "default", "if",
|
68
|
"package", "synchronized", "boolean", "do", "goto", "private", "this",
|
69
|
"break", "double", "implements", "protected", "throw", "byte", "else",
|
70
|
"import", "public", "throws", "case", "enum", "instanceof", "return",
|
71
|
"transient", "catch", "extends", "int", "short", "try", "char", "final",
|
72
|
"interface", "static", "void", "class", "finally", "long", "strictfp",
|
73
|
"volatile", "const", "float", "native", "super", "while" };
|
74
|
public static HashSet<String> keywordSet;
|
75
|
|
76
|
private String filename;
|
77
|
private HashMap<Integer,content> handles = new HashMap<Integer,content>();
|
78
|
private ArrayList<Map<Integer,content>> handlemaps = new ArrayList<Map<Integer,content>>();
|
79
|
private ArrayList<content> content;
|
80
|
private int curhandle;
|
81
|
private boolean debugEnabled;
|
82
|
|
83
|
static {
|
84
|
keywordSet = new HashSet<String>();
|
85
|
for(String kw: keywords) {
|
86
|
keywordSet.add(kw);
|
87
|
}
|
88
|
}
|
89
|
|
90
|
/**
|
91
|
* <p>
|
92
|
* Retrieves the list of content objects that were written to the stream. Each item
|
93
|
* generally corresponds to an invocation of an ObjectOutputStream writeXXX() method.
|
94
|
* A notable exception is the class exceptionstate, which represents an embedded
|
95
|
* exception that was caught during serialization.
|
96
|
* </p>
|
97
|
*
|
98
|
* <p>
|
99
|
* See the various implementors of content to get information about what data is
|
100
|
* available.
|
101
|
* </p>
|
102
|
*
|
103
|
* <p>
|
104
|
* Entries in the list may be null, because it's perfectly legitimate to write a null
|
105
|
* reference to the stream.
|
106
|
* </p>
|
107
|
*
|
108
|
* @return a list of content objects
|
109
|
* @see content
|
110
|
* @see exceptionstate
|
111
|
*/
|
112
|
public List<content> getContent() {
|
113
|
return content;
|
114
|
}
|
115
|
|
116
|
/**
|
117
|
* <p>
|
118
|
* Return a list of Maps containing every object with a handle. The keys are integers
|
119
|
* -- the handles themselves -- and the values are instances of type content.
|
120
|
* </p>
|
121
|
*
|
122
|
* <p>
|
123
|
* Although there is only one map active at a given point, a stream may have multiple
|
124
|
* logical maps: when a reset happens (indicated by TC_RESET), the current map is
|
125
|
* cleared.
|
126
|
* </p>
|
127
|
*
|
128
|
* <p>
|
129
|
* See the spec for details on handles.
|
130
|
* </p>
|
131
|
* @return a list of <Integer,content> maps
|
132
|
*/
|
133
|
public List<Map<Integer,content>> getHandleMaps() {
|
134
|
return handlemaps;
|
135
|
}
|
136
|
|
137
|
/**
|
138
|
* Suitably escapes non-printable-ASCII characters (and doublequotes) for use
|
139
|
* in a Java string literal.
|
140
|
*
|
141
|
* @param str string to escape
|
142
|
* @return an escaped version of the string
|
143
|
*/
|
144
|
public static String unicodeEscape(String str) {
|
145
|
StringBuffer sb = new StringBuffer();
|
146
|
int cplen = str.codePointCount(0, str.length());
|
147
|
for(int i = 0; i < cplen; i++) {
|
148
|
int cp = str.codePointAt(i);
|
149
|
if(cp == '"') {
|
150
|
sb.append("\\\"");
|
151
|
}
|
152
|
if(cp < 0x20 || cp > 0x7f) {
|
153
|
sb.append("\\u" + hexnoprefix(4));
|
154
|
} else {
|
155
|
sb.appendCodePoint(cp);
|
156
|
}
|
157
|
}
|
158
|
return sb.toString();
|
159
|
}
|
160
|
|
161
|
public static String indent(int level) {
|
162
|
StringBuffer sb = new StringBuffer("");
|
163
|
for(int i = 0; i < level; i++) {
|
164
|
sb.append(INDENT);
|
165
|
}
|
166
|
return sb.toString();
|
167
|
}
|
168
|
|
169
|
public void read_Classdata(DataInputStream dis, instance inst) throws IOException {
|
170
|
ArrayList<classdesc> classes = new ArrayList<classdesc>();
|
171
|
inst.classdesc.getHierarchy(classes);
|
172
|
Map<classdesc, Map<field, Object>> alldata = new HashMap<classdesc, Map<field, Object>>();
|
173
|
Map<classdesc, List<content>> ann = new HashMap<classdesc, List<content>>();
|
174
|
for(classdesc cd: classes) {
|
175
|
Map<field, Object> values = new HashMap<field, Object>();
|
176
|
if((cd.descflags & ObjectStreamConstants.SC_SERIALIZABLE) != 0) {
|
177
|
if((cd.descflags & ObjectStreamConstants.SC_EXTERNALIZABLE) != 0) {
|
178
|
throw new IOException("SC_EXTERNALIZABLE & SC_SERIALIZABLE encountered");
|
179
|
}
|
180
|
for(field f: cd.fields) {
|
181
|
Object o = read_FieldValue(f.type, dis);
|
182
|
values.put(f, o);
|
183
|
}
|
184
|
alldata.put(cd, values);
|
185
|
if((cd.descflags & ObjectStreamConstants.SC_WRITE_METHOD) != 0) {
|
186
|
if((cd.descflags & ObjectStreamConstants.SC_ENUM) != 0) {
|
187
|
throw new IOException("SC_ENUM & SC_WRITE_METHOD encountered!");
|
188
|
}
|
189
|
ann.put(cd, read_classAnnotation(dis));
|
190
|
}
|
191
|
} else if((cd.descflags & ObjectStreamConstants.SC_EXTERNALIZABLE) != 0) {
|
192
|
if((cd.descflags & ObjectStreamConstants.SC_SERIALIZABLE) != 0) {
|
193
|
throw new IOException("SC_SERIALIZABLE & SC_EXTERNALIZABLE encountered");
|
194
|
}
|
195
|
if((cd.descflags & ObjectStreamConstants.SC_BLOCK_DATA) != 0) {
|
196
|
throw new EOFException("hit externalizable with nonzero SC_BLOCK_DATA; can't interpret data");
|
197
|
} else {
|
198
|
ann.put(cd, read_classAnnotation(dis));
|
199
|
}
|
200
|
}
|
201
|
}
|
202
|
inst.annotations = ann;
|
203
|
inst.fielddata = alldata;
|
204
|
}
|
205
|
|
206
|
public Object read_FieldValue(fieldtype f, DataInputStream dis) throws IOException {
|
207
|
switch(f) {
|
208
|
case BYTE:
|
209
|
return Byte.valueOf(dis.readByte());
|
210
|
case CHAR:
|
211
|
return Character.valueOf(dis.readChar());
|
212
|
case DOUBLE:
|
213
|
return Double.valueOf(dis.readDouble());
|
214
|
case FLOAT:
|
215
|
return Float.valueOf(dis.readFloat());
|
216
|
case INTEGER:
|
217
|
return Integer.valueOf(dis.readInt());
|
218
|
case LONG:
|
219
|
return Long.valueOf(dis.readLong());
|
220
|
case SHORT:
|
221
|
return Short.valueOf(dis.readShort());
|
222
|
case BOOLEAN:
|
223
|
return Boolean.valueOf(dis.readBoolean());
|
224
|
case OBJECT:
|
225
|
case ARRAY:
|
226
|
byte stc = dis.readByte();
|
227
|
if(f == fieldtype.ARRAY && stc != ObjectStreamConstants.TC_ARRAY) {
|
228
|
throw new IOException("array type listed, but typecode is not TC_ARRAY: " + hex(stc));
|
229
|
}
|
230
|
content c = read_Content(stc, dis, false);
|
231
|
if(c != null && c.isExceptionObject()) {
|
232
|
throw new ExceptionReadException(c);
|
233
|
}
|
234
|
return c;
|
235
|
default:
|
236
|
throw new IOException("can't process type: " + f.toString());
|
237
|
}
|
238
|
}
|
239
|
public jdeserialize(String filename) {
|
240
|
this.filename = filename;
|
241
|
}
|
242
|
private int newHandle() {
|
243
|
return curhandle++;
|
244
|
}
|
245
|
|
246
|
public static String resolveJavaType(fieldtype type, String classname, boolean convertSlashes, boolean fixname) throws IOException {
|
247
|
if(type == fieldtype.ARRAY) {
|
248
|
StringBuffer asb = new StringBuffer("");
|
249
|
for(int i = 0; i < classname.length(); i++) {
|
250
|
char ch = classname.charAt(i);
|
251
|
switch(ch) {
|
252
|
case '[':
|
253
|
asb.append("[]");
|
254
|
continue;
|
255
|
case 'L':
|
256
|
String cn = decodeClassName(classname.substring(i), convertSlashes);
|
257
|
if(fixname) {
|
258
|
cn = fixClassName(cn);
|
259
|
}
|
260
|
return cn + asb.toString();
|
261
|
default:
|
262
|
if(ch < 1 || ch > 127) {
|
263
|
throw new ValidityException("invalid array field type descriptor character: " + classname);
|
264
|
}
|
265
|
fieldtype ft = fieldtype.get((byte)ch);
|
266
|
if(i != (classname.length()-1)) {
|
267
|
throw new ValidityException("array field type descriptor is too long: " + classname);
|
268
|
}
|
269
|
String ftn = ft.getJavaType();
|
270
|
if(fixname) {
|
271
|
ftn = fixClassName(ftn);
|
272
|
}
|
273
|
return ftn + asb.toString();
|
274
|
}
|
275
|
}
|
276
|
throw new ValidityException("array field type descriptor is too short: " + classname);
|
277
|
} else if(type == fieldtype.OBJECT) {
|
278
|
return decodeClassName(classname, convertSlashes);
|
279
|
} else {
|
280
|
return type.getJavaType();
|
281
|
}
|
282
|
}
|
283
|
|
284
|
public List<content> read_classAnnotation(DataInputStream dis) throws IOException {
|
285
|
List<content> list = new ArrayList<content>();
|
286
|
while(true) {
|
287
|
byte tc = dis.readByte();
|
288
|
if(tc == ObjectStreamConstants.TC_ENDBLOCKDATA) {
|
289
|
return list;
|
290
|
}
|
291
|
if(tc == ObjectStreamConstants.TC_RESET) {
|
292
|
reset();
|
293
|
continue;
|
294
|
}
|
295
|
content c = read_Content(tc, dis, true);
|
296
|
if(c != null && c.isExceptionObject()) {
|
297
|
throw new ExceptionReadException(c);
|
298
|
}
|
299
|
list.add(c);
|
300
|
}
|
301
|
}
|
302
|
public static void dump_Instance(int indentlevel, instance inst, PrintStream ps) {
|
303
|
StringBuffer sb = new StringBuffer();
|
304
|
sb.append("[instance " + hex(inst.handle) + ": " + hex(inst.classdesc.handle) + "/" + inst.classdesc.name);
|
305
|
if(inst.annotations != null && inst.annotations.size() > 0) {
|
306
|
sb.append(linesep).append(" object annotations:").append(linesep);
|
307
|
for(classdesc cd: inst.annotations.keySet()) {
|
308
|
sb.append(" ").append(cd.name).append(linesep);
|
309
|
for(content c: inst.annotations.get(cd)) {
|
310
|
sb.append(" ").append(c.toString()).append(linesep);
|
311
|
}
|
312
|
}
|
313
|
}
|
314
|
if(inst.fielddata != null && inst.fielddata.size() > 0) {
|
315
|
sb.append(linesep).append(" field data:").append(linesep);
|
316
|
for(classdesc cd: inst.fielddata.keySet()) {
|
317
|
sb.append(" ").append(hex(cd.handle)).append("/").append(cd.name).append(":").append(linesep);
|
318
|
for(field f: inst.fielddata.get(cd).keySet()) {
|
319
|
Object o = inst.fielddata.get(cd).get(f);
|
320
|
sb.append(" ").append(f.name).append(": ");
|
321
|
if(o instanceof content) {
|
322
|
content c = (content)o;
|
323
|
int h = c.getHandle();
|
324
|
if(h == inst.handle) {
|
325
|
sb.append("this");
|
326
|
} else {
|
327
|
sb.append("r" + hex(h));
|
328
|
}
|
329
|
sb.append(": ").append(c.toString());
|
330
|
sb.append(linesep);
|
331
|
} else {
|
332
|
sb.append("" + o).append(linesep);
|
333
|
}
|
334
|
}
|
335
|
}
|
336
|
}
|
337
|
sb.append("]");
|
338
|
ps.println(sb);
|
339
|
}
|
340
|
|
341
|
/**
|
342
|
* "Fix" the given name by transforming illegal characters, such that the end result
|
343
|
* is a legal Java identifier that is not a keyword.
|
344
|
* If the string is modified at all, the result will be prepended with "$__".
|
345
|
*
|
346
|
* @param name the name to be transformed
|
347
|
* @return the unmodified string if it is legal, otherwise a legal-identifier version
|
348
|
*/
|
349
|
public static String fixClassName(String name) {
|
350
|
if(name == null) {
|
351
|
return "$__null";
|
352
|
}
|
353
|
if(keywordSet.contains(name)) {
|
354
|
return "$__" + name;
|
355
|
}
|
356
|
StringBuffer sb = new StringBuffer();
|
357
|
int cplen = name.codePointCount(0, name.length());
|
358
|
if(cplen < 1) {
|
359
|
return "$__zerolen";
|
360
|
}
|
361
|
boolean modified = false;
|
362
|
int scp = name.codePointAt(0);
|
363
|
if(!Character.isJavaIdentifierStart(scp)) {
|
364
|
modified = true;
|
365
|
if(!Character.isJavaIdentifierPart(scp) || Character.isIdentifierIgnorable(scp)) {
|
366
|
sb.append("x");
|
367
|
} else {
|
368
|
sb.appendCodePoint(scp);
|
369
|
}
|
370
|
} else {
|
371
|
sb.appendCodePoint(scp);
|
372
|
}
|
373
|
|
374
|
for(int i = 1; i < cplen; i++) {
|
375
|
int cp = name.codePointAt(i);
|
376
|
if(!Character.isJavaIdentifierPart(cp) || Character.isIdentifierIgnorable(cp)) {
|
377
|
modified = true;
|
378
|
sb.append("x");
|
379
|
} else {
|
380
|
sb.appendCodePoint(cp);
|
381
|
}
|
382
|
}
|
383
|
if(modified) {
|
384
|
return "$__" + sb.toString();
|
385
|
} else {
|
386
|
return name;
|
387
|
}
|
388
|
}
|
389
|
|
390
|
public static void dump_ClassDesc(int indentlevel, classdesc cd, PrintStream ps, boolean fixname) throws IOException {
|
391
|
String classname = cd.name;
|
392
|
if(fixname) {
|
393
|
classname = fixClassName(classname);
|
394
|
}
|
395
|
if(cd.annotations != null && cd.annotations.size() > 0) {
|
396
|
ps.println(indent(indentlevel) + "// annotations: ");
|
397
|
for(content c: cd.annotations) {
|
398
|
ps.print(indent(indentlevel) + "// " + indent(1));
|
399
|
ps.println(c.toString());
|
400
|
}
|
401
|
}
|
402
|
if(cd.classtype == classdesctype.NORMALCLASS) {
|
403
|
if((cd.descflags & ObjectStreamConstants.SC_ENUM) != 0) {
|
404
|
ps.print(indent(indentlevel) + "enum " + classname + " {");
|
405
|
boolean shouldindent = true;
|
406
|
int len = indent(indentlevel+1).length();
|
407
|
for(String econst: cd.enumconstants) {
|
408
|
if(shouldindent) {
|
409
|
ps.println("");
|
410
|
ps.print(indent(indentlevel+1));
|
411
|
shouldindent = false;
|
412
|
}
|
413
|
len += econst.length();
|
414
|
ps.print(econst + ", ");
|
415
|
if(len >= CODEWIDTH) {
|
416
|
len = indent(indentlevel+1).length();
|
417
|
shouldindent = true;
|
418
|
}
|
419
|
}
|
420
|
ps.println("");
|
421
|
ps.println(indent(indentlevel) + "}");
|
422
|
return;
|
423
|
}
|
424
|
ps.print(indent(indentlevel));
|
425
|
if(cd.isStaticMemberClass()) {
|
426
|
ps.print("static ");
|
427
|
}
|
428
|
ps.print("class " + (classname.charAt(0) == '[' ? resolveJavaType(fieldtype.ARRAY, cd.name, false, fixname) : classname));
|
429
|
if(cd.superclass != null) {
|
430
|
ps.print(" extends " + cd.superclass.name);
|
431
|
}
|
432
|
ps.print(" implements ");
|
433
|
if((cd.descflags & ObjectStreamConstants.SC_EXTERNALIZABLE) != 0) {
|
434
|
ps.print("java.io.Externalizable");
|
435
|
} else {
|
436
|
ps.print("java.io.Serializable");
|
437
|
}
|
438
|
if(cd.interfaces != null) {
|
439
|
for(String intf: cd.interfaces) {
|
440
|
ps.print(", " + intf);
|
441
|
}
|
442
|
}
|
443
|
ps.println(" {");
|
444
|
for(field f: cd.fields) {
|
445
|
if(f.isInnerClassReference()) {
|
446
|
continue;
|
447
|
}
|
448
|
ps.print(indent(indentlevel+1) + f.getJavaType());
|
449
|
ps.println(" " + f.name + ";");
|
450
|
}
|
451
|
for(classdesc icd: cd.innerclasses) {
|
452
|
dump_ClassDesc(indentlevel+1, icd, ps, fixname);
|
453
|
}
|
454
|
ps.println(indent(indentlevel)+"}");
|
455
|
} else if(cd.classtype == classdesctype.PROXYCLASS) {
|
456
|
ps.print(indent(indentlevel) + "// proxy class " + hex(cd.handle));
|
457
|
if(cd.superclass != null) {
|
458
|
ps.print(" extends " + cd.superclass.name);
|
459
|
}
|
460
|
ps.println(" implements ");
|
461
|
for(String intf: cd.interfaces) {
|
462
|
ps.println(indent(indentlevel) + "// " + intf + ", ");
|
463
|
}
|
464
|
if((cd.descflags & ObjectStreamConstants.SC_EXTERNALIZABLE) != 0) {
|
465
|
ps.println(indent(indentlevel) + "// java.io.Externalizable");
|
466
|
} else {
|
467
|
ps.println(indent(indentlevel) + "// java.io.Serializable");
|
468
|
}
|
469
|
} else {
|
470
|
throw new ValidityException("encountered invalid classdesc type!");
|
471
|
}
|
472
|
}
|
473
|
|
474
|
public void setHandle(int handle, content c) throws IOException {
|
475
|
if(handles.containsKey(handle)) {
|
476
|
throw new IOException("trying to reset handle " + hex(handle));
|
477
|
}
|
478
|
handles.put(handle, c);
|
479
|
}
|
480
|
public void reset() {
|
481
|
debug("reset ordered!");
|
482
|
if(handles != null && handles.size() > 0) {
|
483
|
HashMap<Integer,content> hm = new HashMap<Integer,content>();
|
484
|
hm.putAll(handles);
|
485
|
handlemaps.add(hm);
|
486
|
}
|
487
|
handles.clear();
|
488
|
curhandle = ObjectStreamConstants.baseWireHandle; // 0x7e0000
|
489
|
}
|
490
|
/**
|
491
|
* Read the content of a thrown exception object. According to the spec, this must be
|
492
|
* an object of type Throwable. Although the Sun JDK always appears to provide enough
|
493
|
* information about the hierarchy to reach all the way back to java.lang.Throwable,
|
494
|
* it's unclear whether this is actually a requirement. From my reading, it's
|
495
|
* possible that some other ObjectOutputStream implementations may leave some gaps in
|
496
|
* the hierarchy, forcing this app to hit the classloader. To avoid this, we merely
|
497
|
* ensure that the written object is indeed an instance; ensuring that the object is
|
498
|
* indeed a Throwable is an exercise left to the user.
|
499
|
*/
|
500
|
public content read_Exception(DataInputStream dis) throws IOException {
|
501
|
reset();
|
502
|
byte tc = dis.readByte();
|
503
|
if(tc == ObjectStreamConstants.TC_RESET) {
|
504
|
throw new ValidityException("TC_RESET for object while reading exception: what should we do?");
|
505
|
}
|
506
|
content c = read_Content(tc, dis, false);
|
507
|
if(c == null) {
|
508
|
throw new ValidityException("stream signaled for an exception, but exception object was null!");
|
509
|
}
|
510
|
if(!(c instanceof instance)) {
|
511
|
throw new ValidityException("stream signaled for an exception, but content is not an object!");
|
512
|
}
|
513
|
if(c.isExceptionObject()) {
|
514
|
throw new ExceptionReadException(c);
|
515
|
}
|
516
|
c.setIsExceptionObject(true);
|
517
|
reset();
|
518
|
return c;
|
519
|
}
|
520
|
|
521
|
public classdesc read_classDesc(DataInputStream dis) throws IOException {
|
522
|
byte tc = dis.readByte();
|
523
|
classdesc cd = handle_classDesc(tc, dis, false);
|
524
|
return cd;
|
525
|
}
|
526
|
public classdesc read_newClassDesc(DataInputStream dis) throws IOException {
|
527
|
byte tc = dis.readByte();
|
528
|
classdesc cd = handle_newClassDesc(tc, dis);
|
529
|
return cd;
|
530
|
}
|
531
|
public content read_prevObject(DataInputStream dis) throws IOException {
|
532
|
int handle = dis.readInt();
|
533
|
if(!handles.containsKey(Integer.valueOf(handle))) {
|
534
|
throw new ValidityException("can't find an entry for handle " + hex(handle));
|
535
|
}
|
536
|
content c = handles.get(handle);
|
537
|
debug("prevObject: handle " + hex(c.getHandle()) + " classdesc " + c.toString());
|
538
|
return c;
|
539
|
}
|
540
|
|
541
|
public classdesc handle_newClassDesc(byte tc, DataInputStream dis) throws IOException {
|
542
|
return handle_classDesc(tc, dis, true);
|
543
|
}
|
544
|
public classdesc handle_classDesc(byte tc, DataInputStream dis, boolean mustBeNew) throws IOException {
|
545
|
if(tc == ObjectStreamConstants.TC_CLASSDESC) {
|
546
|
String name = dis.readUTF();
|
547
|
long serialVersionUID = dis.readLong();
|
548
|
int handle = newHandle();
|
549
|
byte descflags = dis.readByte();
|
550
|
short nfields = dis.readShort();
|
551
|
if(nfields < 0) {
|
552
|
throw new IOException("invalid field count: " + nfields);
|
553
|
}
|
554
|
field[] fields = new field[nfields];
|
555
|
for(short s = 0; s < nfields; s++) {
|
556
|
byte ftype = dis.readByte();
|
557
|
if(ftype == 'B' || ftype == 'C' || ftype == 'D'
|
558
|
|| ftype == 'F' || ftype == 'I' || ftype == 'J'
|
559
|
|| ftype == 'S' || ftype == 'Z') {
|
560
|
String fieldname = dis.readUTF();
|
561
|
fields[s] = new field(fieldtype.get(ftype), fieldname);
|
562
|
} else if(ftype == '[' || ftype == 'L') {
|
563
|
String fieldname = dis.readUTF();
|
564
|
byte stc = dis.readByte();
|
565
|
stringobj classname = read_newString(stc, dis);
|
566
|
//String classname = dis.readUTF();
|
567
|
fields[s] = new field(fieldtype.get(ftype), fieldname, classname);
|
568
|
} else {
|
569
|
throw new IOException("invalid field type char: " + hex(ftype));
|
570
|
}
|
571
|
}
|
572
|
classdesc cd = new classdesc(classdesctype.NORMALCLASS);
|
573
|
cd.name = name;
|
574
|
cd.serialVersionUID = serialVersionUID;
|
575
|
cd.handle = handle;
|
576
|
cd.descflags = descflags;
|
577
|
cd.fields = fields;
|
578
|
cd.annotations = read_classAnnotation(dis);
|
579
|
cd.superclass = read_classDesc(dis);
|
580
|
setHandle(handle, cd);
|
581
|
debug("read new classdesc: handle " + hex(handle) + " name " + name);
|
582
|
return cd;
|
583
|
} else if(tc == ObjectStreamConstants.TC_NULL) {
|
584
|
if(mustBeNew) {
|
585
|
throw new ValidityException("expected new class description -- got null!");
|
586
|
}
|
587
|
debug("read null classdesc");
|
588
|
return null;
|
589
|
} else if(tc == ObjectStreamConstants.TC_REFERENCE) {
|
590
|
if(mustBeNew) {
|
591
|
throw new ValidityException("expected new class description -- got a reference!");
|
592
|
}
|
593
|
content c = read_prevObject(dis);
|
594
|
if(!(c instanceof classdesc)) {
|
595
|
throw new IOException("referenced object not a class description!");
|
596
|
}
|
597
|
classdesc cd = (classdesc)c;
|
598
|
return cd;
|
599
|
} else if(tc == ObjectStreamConstants.TC_PROXYCLASSDESC) {
|
600
|
int handle = newHandle();
|
601
|
int icount = dis.readInt();
|
602
|
if(icount < 0) {
|
603
|
throw new IOException("invalid proxy interface count: " + hex(icount));
|
604
|
}
|
605
|
String interfaces[] = new String[icount];
|
606
|
for(int i = 0; i < icount; i++) {
|
607
|
interfaces[i] = dis.readUTF();
|
608
|
}
|
609
|
classdesc cd = new classdesc(classdesctype.PROXYCLASS);
|
610
|
cd.handle = handle;
|
611
|
cd.interfaces = interfaces;
|
612
|
cd.annotations = read_classAnnotation(dis);
|
613
|
cd.superclass = read_classDesc(dis);
|
614
|
setHandle(handle, cd);
|
615
|
cd.name = "(proxy class; no name)";
|
616
|
debug("read new proxy classdesc: handle " + hex(handle) + " names [" + Arrays.toString(interfaces) + "]");
|
617
|
return cd;
|
618
|
} else {
|
619
|
throw new ValidityException("expected a valid class description starter got " + hex(tc));
|
620
|
}
|
621
|
}
|
622
|
public arrayobj read_newArray(DataInputStream dis) throws IOException {
|
623
|
classdesc cd = read_classDesc(dis);
|
624
|
int handle = newHandle();
|
625
|
debug("reading new array: handle " + hex(handle) + " classdesc " + cd.toString());
|
626
|
if(cd.name.length() < 2) {
|
627
|
throw new IOException("invalid name in array classdesc: " + cd.name);
|
628
|
}
|
629
|
arraycoll ac = read_arrayValues(cd.name.substring(1), dis);
|
630
|
return new arrayobj(handle, cd, ac);
|
631
|
}
|
632
|
public arraycoll read_arrayValues(String str, DataInputStream dis) throws IOException {
|
633
|
byte b = str.getBytes("UTF-8")[0];
|
634
|
fieldtype ft = fieldtype.get(b);
|
635
|
int size = dis.readInt();
|
636
|
if(size < 0) {
|
637
|
throw new IOException("invalid array size: " + size);
|
638
|
}
|
639
|
|
640
|
arraycoll ac = new arraycoll(ft);
|
641
|
for(int i = 0; i < size; i++) {
|
642
|
ac.add(read_FieldValue(ft, dis));
|
643
|
continue;
|
644
|
}
|
645
|
return ac;
|
646
|
}
|
647
|
public classobj read_newClass(DataInputStream dis) throws IOException {
|
648
|
classdesc cd = read_classDesc(dis);
|
649
|
int handle = newHandle();
|
650
|
debug("reading new class: handle " + hex(handle) + " classdesc " + cd.toString());
|
651
|
classobj c = new classobj(handle, cd);
|
652
|
setHandle(handle, c);
|
653
|
return c;
|
654
|
}
|
655
|
public enumobj read_newEnum(DataInputStream dis) throws IOException {
|
656
|
classdesc cd = read_classDesc(dis);
|
657
|
if(cd == null) {
|
658
|
throw new IOException("enum classdesc can't be null!");
|
659
|
}
|
660
|
int handle = newHandle();
|
661
|
debug("reading new enum: handle " + hex(handle) + " classdesc " + cd.toString());
|
662
|
byte tc = dis.readByte();
|
663
|
stringobj so = read_newString(tc, dis);
|
664
|
cd.addEnum(so.value);
|
665
|
setHandle(handle, so);
|
666
|
return new enumobj(handle, cd, so);
|
667
|
}
|
668
|
public stringobj read_newString(byte tc, DataInputStream dis) throws IOException {
|
669
|
byte[] data;
|
670
|
if(tc == ObjectStreamConstants.TC_REFERENCE) {
|
671
|
content c = read_prevObject(dis);
|
672
|
if(!(c instanceof stringobj)) {
|
673
|
throw new IOException("got reference for a string, but referenced value was something else!");
|
674
|
}
|
675
|
return (stringobj)c;
|
676
|
}
|
677
|
int handle = newHandle();
|
678
|
if(tc == ObjectStreamConstants.TC_STRING) {
|
679
|
int len = dis.readUnsignedShort();
|
680
|
data = new byte[len];
|
681
|
} else if(tc == ObjectStreamConstants.TC_LONGSTRING) {
|
682
|
long len = dis.readLong();
|
683
|
if(len < 0) {
|
684
|
throw new IOException("invalid long string length: " + len);
|
685
|
}
|
686
|
if(len > 2147483647) {
|
687
|
throw new IOException("long string is too long: " + len);
|
688
|
}
|
689
|
if(len < 65536) {
|
690
|
debugerr("warning: small string length encoded as TC_LONGSTRING: " + len);
|
691
|
}
|
692
|
data = new byte[(int)len];
|
693
|
} else if(tc == ObjectStreamConstants.TC_NULL) {
|
694
|
throw new ValidityException("stream signaled TC_NULL when string type expected!");
|
695
|
} else {
|
696
|
throw new IOException("invalid tc byte in string: " + hex(tc));
|
697
|
}
|
698
|
dis.readFully(data);
|
699
|
debug("reading new string: handle " + hex(handle) + " bufsz " + data.length);
|
700
|
stringobj sobj = new stringobj(handle, data);
|
701
|
setHandle(handle, sobj);
|
702
|
return sobj;
|
703
|
}
|
704
|
public blockdata read_blockdata(byte tc, DataInputStream dis) throws IOException {
|
705
|
int size;
|
706
|
if(tc == ObjectStreamConstants.TC_BLOCKDATA) {
|
707
|
size = dis.readUnsignedByte();
|
708
|
} else if(tc == ObjectStreamConstants.TC_BLOCKDATALONG) {
|
709
|
size = dis.readInt();
|
710
|
} else {
|
711
|
throw new IOException("invalid tc value for blockdata: " + hex(tc));
|
712
|
}
|
713
|
if(size < 0) {
|
714
|
throw new IOException("invalid value for blockdata size: " + size);
|
715
|
}
|
716
|
byte[] b = new byte[size];
|
717
|
dis.readFully(b);
|
718
|
debug("read blockdata of size " + size);
|
719
|
return new blockdata(b);
|
720
|
}
|
721
|
public instance read_newObject(DataInputStream dis) throws IOException {
|
722
|
classdesc cd = read_classDesc(dis);
|
723
|
int handle = newHandle();
|
724
|
debug("reading new object: handle " + hex(handle) + " classdesc " + cd.toString());
|
725
|
instance i = new instance();
|
726
|
i.classdesc = cd;
|
727
|
i.handle = handle;
|
728
|
setHandle(handle, i);
|
729
|
read_Classdata(dis, i);
|
730
|
debug("done reading object for handle " + hex(handle));
|
731
|
return i;
|
732
|
}
|
733
|
|
734
|
/**
|
735
|
* <p>
|
736
|
* Read the next object corresponding to the spec grammar rule "content", and return
|
737
|
* an object of type content.
|
738
|
* </p>
|
739
|
*
|
740
|
* <p>
|
741
|
* Usually, there is a 1:1 mapping of content items and returned instances. The
|
742
|
* one case where this isn't true is when an exception is embedded inside another
|
743
|
* object. When this is encountered, only the serialized exception object is
|
744
|
* returned; it's up to the caller to backtrack in order to gather any data from the
|
745
|
* object that was being serialized when the exception was thrown.
|
746
|
* </p>
|
747
|
*
|
748
|
* @param tc the last byte read from the stream; it must be one of the TC_* values
|
749
|
* within ObjectStreamConstants.*
|
750
|
* @param dis the DataInputStream to read from
|
751
|
* @param blockdata whether or not to read TC_BLOCKDATA (this is the difference
|
752
|
* between spec rules "object" and "content").
|
753
|
* @return an object representing the last read item from the stream
|
754
|
* @throws IOException when a validity or I/O error occurs while reading
|
755
|
*/
|
756
|
public content read_Content(byte tc, DataInputStream dis, boolean blockdata) throws IOException {
|
757
|
try {
|
758
|
switch(tc) {
|
759
|
case ObjectStreamConstants.TC_OBJECT:
|
760
|
return read_newObject(dis);
|
761
|
case ObjectStreamConstants.TC_CLASS:
|
762
|
return read_newClass(dis);
|
763
|
case ObjectStreamConstants.TC_ARRAY:
|
764
|
return read_newArray(dis);
|
765
|
case ObjectStreamConstants.TC_STRING:
|
766
|
case ObjectStreamConstants.TC_LONGSTRING:
|
767
|
return read_newString(tc, dis);
|
768
|
case ObjectStreamConstants.TC_ENUM:
|
769
|
return read_newEnum(dis);
|
770
|
case ObjectStreamConstants.TC_CLASSDESC:
|
771
|
case ObjectStreamConstants.TC_PROXYCLASSDESC:
|
772
|
return handle_newClassDesc(tc, dis);
|
773
|
case ObjectStreamConstants.TC_REFERENCE:
|
774
|
return read_prevObject(dis);
|
775
|
case ObjectStreamConstants.TC_NULL:
|
776
|
return null;
|
777
|
case ObjectStreamConstants.TC_EXCEPTION:
|
778
|
return read_Exception(dis);
|
779
|
case ObjectStreamConstants.TC_BLOCKDATA:
|
780
|
case ObjectStreamConstants.TC_BLOCKDATALONG:
|
781
|
if(blockdata == false) {
|
782
|
throw new IOException("got a blockdata TC_*, but not allowed here: " + hex(tc));
|
783
|
}
|
784
|
return read_blockdata(tc, dis);
|
785
|
default:
|
786
|
throw new IOException("unknown content tc byte in stream: " + hex(tc));
|
787
|
}
|
788
|
} catch (ExceptionReadException ere) {
|
789
|
return ere.getExceptionObject();
|
790
|
}
|
791
|
}
|
792
|
|
793
|
/**
|
794
|
* <p>
|
795
|
* Reads in an entire ObjectOutputStream output on the given stream, filing
|
796
|
* this object's content and handle maps with data about the objects in the stream.
|
797
|
* </p>
|
798
|
*
|
799
|
* <p>
|
800
|
* If shouldConnect is true, then jdeserialize will attempt to identify member classes
|
801
|
* by their names according to the details laid out in the Inner Classes
|
802
|
* Specification. If it finds one, it will set the classdesc's flag indicating that
|
803
|
* it is an member class, and it will create a reference in its enclosing class.
|
804
|
* </p>
|
805
|
*
|
806
|
* @param is an open InputStream on a serialized stream of data
|
807
|
* @param shouldConnect true if jdeserialize should attempt to identify and connect
|
808
|
* member classes with their enclosing classes
|
809
|
*
|
810
|
* Also see the <pre>connectMemberClasses</pre> method for more information on the
|
811
|
* member-class-detection algorithm.
|
812
|
*/
|
813
|
public void run(InputStream is, boolean shouldConnect) throws IOException {
|
814
|
LoggerInputStream lis = null;
|
815
|
DataInputStream dis = null;
|
816
|
try {
|
817
|
lis = new LoggerInputStream(is);
|
818
|
dis = new DataInputStream(lis);
|
819
|
|
820
|
short magic = dis.readShort();
|
821
|
if(magic != ObjectStreamConstants.STREAM_MAGIC) {
|
822
|
throw new ValidityException("file magic mismatch! expected " + ObjectStreamConstants.STREAM_MAGIC + ", got " + magic);
|
823
|
}
|
824
|
short streamversion = dis.readShort();
|
825
|
if(streamversion != ObjectStreamConstants.STREAM_VERSION) {
|
826
|
throw new ValidityException("file version mismatch! expected " + ObjectStreamConstants.STREAM_VERSION + ", got " + streamversion);
|
827
|
}
|
828
|
reset();
|
829
|
content = new ArrayList<content>();
|
830
|
while(true) {
|
831
|
byte tc;
|
832
|
try {
|
833
|
lis.record();
|
834
|
tc = dis.readByte();
|
835
|
if(tc == ObjectStreamConstants.TC_RESET) {
|
836
|
reset();
|
837
|
continue;
|
838
|
}
|
839
|
} catch (EOFException eoe) {
|
840
|
break;
|
841
|
}
|
842
|
content c = read_Content(tc, dis, true);
|
843
|
System.out.println("read: " + c.toString());
|
844
|
if(c != null && c.isExceptionObject()) {
|
845
|
c = new exceptionstate(c, lis.getRecordedData());
|
846
|
}
|
847
|
content.add(c);
|
848
|
}
|
849
|
} finally {
|
850
|
if(dis != null) {
|
851
|
try {
|
852
|
dis.close();
|
853
|
} catch (Exception ignore) { }
|
854
|
}
|
855
|
if(lis != null) {
|
856
|
try {
|
857
|
lis.close();
|
858
|
} catch (Exception ignore) {}
|
859
|
}
|
860
|
}
|
861
|
for(content c: handles.values()) {
|
862
|
c.validate();
|
863
|
}
|
864
|
if(shouldConnect) {
|
865
|
connectMemberClasses();
|
866
|
for(content c: handles.values()) {
|
867
|
c.validate();
|
868
|
}
|
869
|
}
|
870
|
if(handles != null && handles.size() > 0) {
|
871
|
HashMap<Integer,content> hm = new HashMap<Integer,content>();
|
872
|
hm.putAll(handles);
|
873
|
handlemaps.add(hm);
|
874
|
}
|
875
|
}
|
876
|
public void dump(Getopt go) throws IOException {
|
877
|
if(go.hasOption("-blockdata") || go.hasOption("-blockdatamanifest")) {
|
878
|
List<String> bout = go.getArguments("-blockdata");
|
879
|
List<String> mout = go.getArguments("-blockdatamanifest");
|
880
|
FileOutputStream bos = null, mos = null;
|
881
|
PrintWriter pw = null;
|
882
|
try {
|
883
|
if(bout != null && bout.size() > 0) {
|
884
|
bos = new FileOutputStream(bout.get(0));
|
885
|
}
|
886
|
if(mout != null && bout.size() > 0) {
|
887
|
mos = new FileOutputStream(mout.get(0));
|
888
|
pw = new PrintWriter(mos);
|
889
|
pw.println("# Each line in this file that doesn't begin with a '#' contains the size of");
|
890
|
pw.println("# an individual blockdata block written to the stream.");
|
891
|
}
|
892
|
for(content c: content) {
|
893
|
System.out.println(c.toString());
|
894
|
if(c instanceof blockdata) {
|
895
|
blockdata bd = (blockdata)c;
|
896
|
if(mos != null) {
|
897
|
pw.println(bd.buf.length);
|
898
|
}
|
899
|
if(bos != null) {
|
900
|
bos.write(bd.buf);
|
901
|
}
|
902
|
}
|
903
|
}
|
904
|
} finally {
|
905
|
if(bos != null) {
|
906
|
try {
|
907
|
bos.close();
|
908
|
} catch (IOException ignore) { }
|
909
|
}
|
910
|
if(mos != null) {
|
911
|
try {
|
912
|
pw.close();
|
913
|
mos.close();
|
914
|
} catch (IOException ignore) { }
|
915
|
}
|
916
|
}
|
917
|
}
|
918
|
if(!go.hasOption("-nocontent")) {
|
919
|
System.out.println("//// BEGIN stream content output");
|
920
|
for(content c: content) {
|
921
|
System.out.println(c.toString());
|
922
|
}
|
923
|
System.out.println("//// END stream content output");
|
924
|
System.out.println("");
|
925
|
}
|
926
|
|
927
|
if(!go.hasOption("-noclasses")) {
|
928
|
boolean showarray = go.hasOption("-showarrays");
|
929
|
List<String> fpat = go.getArguments("-filter");
|
930
|
System.out.println("//// BEGIN class declarations"
|
931
|
+ (showarray? "" : " (excluding array classes)")
|
932
|
+ ((fpat != null && fpat.size() > 0)
|
933
|
? " (exclusion filter " + fpat.get(0) + ")"
|
934
|
: ""));
|
935
|
for(content c: handles.values()) {
|
936
|
if(c instanceof classdesc) {
|
937
|
classdesc cl = (classdesc)c;
|
938
|
if(showarray == false && cl.isArrayClass()) {
|
939
|
continue;
|
940
|
}
|
941
|
// Member classes will be displayed as part of their enclosing
|
942
|
// classes.
|
943
|
if(cl.isStaticMemberClass() || cl.isInnerClass()) {
|
944
|
continue;
|
945
|
}
|
946
|
if(fpat != null && fpat.size() > 0 && cl.name.matches(fpat.get(0))) {
|
947
|
continue;
|
948
|
}
|
949
|
dump_ClassDesc(0, cl, System.out, go.hasOption("-fixnames"));
|
950
|
System.out.println("");
|
951
|
}
|
952
|
}
|
953
|
System.out.println("//// END class declarations");
|
954
|
System.out.println("");
|
955
|
}
|
956
|
if(!go.hasOption("-noinstances")) {
|
957
|
System.out.println("//// BEGIN instance dump");
|
958
|
for(content c: handles.values()) {
|
959
|
if(c instanceof instance) {
|
960
|
instance i = (instance)c;
|
961
|
dump_Instance(0, i, System.out);
|
962
|
}
|
963
|
}
|
964
|
System.out.println("//// END instance dump");
|
965
|
System.out.println("");
|
966
|
}
|
967
|
}
|
968
|
|
969
|
|
970
|
/**
|
971
|
* <p>
|
972
|
* Connects member classes according to the rules specified by the JDK 1.1 Inner
|
973
|
* Classes Specification.
|
974
|
* </p>
|
975
|
*
|
976
|
* <pre>
|
977
|
* Inner classes:
|
978
|
* for each class C containing an object reference member R named this$N, do:
|
979
|
* if the name of C matches the pattern O$I
|
980
|
* AND the name O matches the name of an existing type T
|
981
|
* AND T is the exact type referred to by R, then:
|
982
|
* don't display the declaration of R in normal dumping,
|
983
|
* consider C to be an inner class of O named I
|
984
|
*
|
985
|
* Static member classes (after):
|
986
|
* for each class C matching the pattern O$I,
|
987
|
* where O is the name of a class in the same package
|
988
|
* AND C is not an inner class according to the above algorithm:
|
989
|
* consider C to be an inner class of O named I
|
990
|
* </pre>
|
991
|
*
|
992
|
* <p>
|
993
|
* This functions fills in the isInnerClass value in classdesc, the
|
994
|
* isInnerClassReference value in field, the isLocalInnerClass value in
|
995
|
* classdesc, and the isStaticMemberClass value in classdesc where necessary.
|
996
|
* </p>
|
997
|
*
|
998
|
* <p>
|
999
|
* A word on static classes: serializing a static member class S doesn't inherently
|
1000
|
* require serialization of its parent class P. Unlike inner classes, S doesn't
|
1001
|
* retain an instance of P, and therefore P's class description doesn't need to be
|
1002
|
* written. In these cases, if parent classes can be found, their static member
|
1003
|
* classes will be connected; but if they can't be found, the names will not be
|
1004
|
* changed and no ValidityException will be thrown.
|
1005
|
* </p>
|
1006
|
*
|
1007
|
* @throws ValidityException if the found values don't correspond to spec
|
1008
|
*/
|
1009
|
public void connectMemberClasses() throws IOException {
|
1010
|
HashMap<classdesc, String> newnames = new HashMap<classdesc, String>();
|
1011
|
HashMap<String, classdesc> classes = new HashMap<String, classdesc>();
|
1012
|
HashSet<String> classnames = new HashSet<String>();
|
1013
|
for(content c: handles.values()) {
|
1014
|
if(!(c instanceof classdesc)) {
|
1015
|
continue;
|
1016
|
}
|
1017
|
classdesc cd = (classdesc)c;
|
1018
|
classes.put(cd.name, cd);
|
1019
|
classnames.add(cd.name);
|
1020
|
}
|
1021
|
Pattern fpat = Pattern.compile("^this\\$(\\d+)$");
|
1022
|
Pattern clpat = Pattern.compile("^((?:[^\\$]+\\$)*[^\\$]+)\\$([^\\$]+)$");
|
1023
|
for(classdesc cd: classes.values()) {
|
1024
|
if(cd.classtype == classdesctype.PROXYCLASS) {
|
1025
|
continue;
|
1026
|
}
|
1027
|
for(field f: cd.fields) {
|
1028
|
if(f.type != fieldtype.OBJECT) {
|
1029
|
continue;
|
1030
|
}
|
1031
|
Matcher m = fpat.matcher(f.name);
|
1032
|
if(!m.matches()) {
|
1033
|
continue;
|
1034
|
}
|
1035
|
boolean islocal = false;
|
1036
|
Matcher clmat = clpat.matcher(cd.name);
|
1037
|
if(!clmat.matches()) {
|
1038
|
throw new ValidityException("inner class enclosing-class reference field exists, but class name doesn't match expected pattern: class " + cd.name + " field " + f.name);
|
1039
|
}
|
1040
|
String outer = clmat.group(1), inner = clmat.group(2);
|
1041
|
classdesc outercd = classes.get(outer);
|
1042
|
if(outercd == null) {
|
1043
|
throw new ValidityException("couldn't connect inner classes: outer class not found for field name " + f.name);
|
1044
|
}
|
1045
|
if(!outercd.name.equals(f.getJavaType())) {
|
1046
|
throw new ValidityException("outer class field type doesn't match field type name: " + f.classname.value + " outer class name " + outercd.name);
|
1047
|
}
|
1048
|
outercd.addInnerClass(cd);
|
1049
|
cd.setIsLocalInnerClass(islocal);
|
1050
|
cd.setIsInnerClass(true);
|
1051
|
f.setIsInnerClassReference(true);
|
1052
|
newnames.put(cd, inner);
|
1053
|
}
|
1054
|
}
|
1055
|
for(classdesc cd: classes.values()) {
|
1056
|
if(cd.classtype == classdesctype.PROXYCLASS) {
|
1057
|
continue;
|
1058
|
}
|
1059
|
if(cd.isInnerClass()) {
|
1060
|
continue;
|
1061
|
}
|
1062
|
Matcher clmat = clpat.matcher(cd.name);
|
1063
|
if(!clmat.matches()) {
|
1064
|
continue;
|
1065
|
}
|
1066
|
String outer = clmat.group(1), inner = clmat.group(2);
|
1067
|
classdesc outercd = classes.get(outer);
|
1068
|
if(outercd != null) {
|
1069
|
outercd.addInnerClass(cd);
|
1070
|
cd.setIsStaticMemberClass(true);
|
1071
|
newnames.put(cd, inner);
|
1072
|
}
|
1073
|
}
|
1074
|
for(classdesc ncd: newnames.keySet()) {
|
1075
|
String newname = newnames.get(ncd);
|
1076
|
if(classnames.contains(newname)) {
|
1077
|
throw new ValidityException("can't rename class from " + ncd.name + " to " + newname + " -- class already exists!");
|
1078
|
}
|
1079
|
for(classdesc cd: classes.values()) {
|
1080
|
if(cd.classtype == classdesctype.PROXYCLASS) {
|
1081
|
continue;
|
1082
|
}
|
1083
|
for(field f: cd.fields) {
|
1084
|
if(f.getJavaType().equals(ncd.name)) {
|
1085
|
f.setReferenceTypeName(newname);
|
1086
|
}
|
1087
|
}
|
1088
|
}
|
1089
|
if(classnames.remove(ncd.name) == false) {
|
1090
|
throw new ValidityException("tried to remove " + ncd.name + " from classnames cache, but couldn't find it!");
|
1091
|
}
|
1092
|
ncd.name = newname;
|
1093
|
if(classnames.add(newname) == false) {
|
1094
|
throw new ValidityException("can't rename class to " + newname + " -- class already exists!");
|
1095
|
}
|
1096
|
}
|
1097
|
}
|
1098
|
|
1099
|
/**
|
1100
|
* Decodes a class name according to the field-descriptor format in the jvm spec,
|
1101
|
* section 4.3.2.
|
1102
|
* @param fdesc name in field-descriptor format (Lfoo/bar/baz;)
|
1103
|
* @param convertSlashes true iff slashes should be replaced with periods (true for
|
1104
|
* "real" field-descriptor format; false for names in classdesc)
|
1105
|
* @return a fully-qualified class name
|
1106
|
* @throws ValidityException if the name isn't valid
|
1107
|
*/
|
1108
|
public static String decodeClassName(String fdesc, boolean convertSlashes) throws ValidityException {
|
1109
|
if(fdesc.charAt(0) != 'L' || fdesc.charAt(fdesc.length()-1) != ';' || fdesc.length() < 3) {
|
1110
|
throw new ValidityException("invalid name (not in field-descriptor format): " + fdesc);
|
1111
|
}
|
1112
|
String subs = fdesc.substring(1, fdesc.length()-1);
|
1113
|
if(convertSlashes) {
|
1114
|
return subs.replace('/', '.');
|
1115
|
}
|
1116
|
return subs;
|
1117
|
}
|
1118
|
|
1119
|
public static String hexnoprefix(long value) {
|
1120
|
return hexnoprefix(value, 2);
|
1121
|
}
|
1122
|
public static String hexnoprefix(long value, int len) {
|
1123
|
if(value < 0) {
|
1124
|
value = 256 + value;
|
1125
|
}
|
1126
|
String s = Long.toString(value, 16);
|
1127
|
while(s.length() < len) {
|
1128
|
s = "0" + s;
|
1129
|
}
|
1130
|
return s;
|
1131
|
}
|
1132
|
public static String hex(long value) {
|
1133
|
return "0x" + hexnoprefix(value);
|
1134
|
}
|
1135
|
public static void debugerr(String message) {
|
1136
|
System.err.println(message);
|
1137
|
}
|
1138
|
public void debug(String message) {
|
1139
|
if(debugEnabled) {
|
1140
|
System.out.println(message);
|
1141
|
}
|
1142
|
}
|
1143
|
|
1144
|
public static void main(String[] args) {
|
1145
|
HashMap<String, Integer> options = new HashMap<String, Integer>();
|
1146
|
Getopt go = new Getopt();
|
1147
|
go.addOption("-help", 0, "Show this list.");
|
1148
|
go.addOption("-debug", 0, "Write debug info generated during parsing to stdout.");
|
1149
|
go.addOption("-filter", 1, "Exclude classes that match the given String.matches() regex from class output.");
|
1150
|
go.addOption("-nocontent", 0, "Don't output descriptions of the content in the stream.");
|
1151
|
go.addOption("-noinstances", 0, "Don't output descriptions of every instance.");
|
1152
|
go.addOption("-showarrays", 0, "Show array class declarations (e.g. int[]).");
|
1153
|
go.addOption("-noconnect", 0, "Don't attempt to connect member classes to their enclosing classes.");
|
1154
|
go.addOption("-fixnames", 0, "In class names, replace illegal Java identifier characters with legal ones.");
|
1155
|
go.addOption("-noclasses", 0, "Don't output class declarations.");
|
1156
|
go.addOption("-blockdata", 1, "Write raw blockdata out to the specified file.");
|
1157
|
go.addOption("-blockdatamanifest", 1, "Write blockdata manifest out to the specified file.");
|
1158
|
try {
|
1159
|
go.parse(args);
|
1160
|
} catch (Getopt.OptionParseException ope) {
|
1161
|
System.err.println("argument error: " + ope.getMessage());
|
1162
|
System.out.println(go.getDescriptionString());
|
1163
|
System.exit(1);
|
1164
|
}
|
1165
|
if(go.hasOption("-help")) {
|
1166
|
System.out.println(go.getDescriptionString());
|
1167
|
System.exit(1);
|
1168
|
}
|
1169
|
List<String> fargs = go.getOtherArguments();
|
1170
|
if(fargs.size() < 1) {
|
1171
|
debugerr("args: [options] file1 [file2 .. fileN]");
|
1172
|
System.err.println("");
|
1173
|
System.err.println(go.getDescriptionString());
|
1174
|
System.exit(1);
|
1175
|
}
|
1176
|
for(String filename: fargs) {
|
1177
|
FileInputStream fis = null;
|
1178
|
try {
|
1179
|
fis = new FileInputStream(filename);
|
1180
|
jdeserialize jd = new jdeserialize(filename);
|
1181
|
if(go.hasOption("-debug")) {
|
1182
|
jd.debugEnabled = true;
|
1183
|
} else {
|
1184
|
jd.debugEnabled = false;
|
1185
|
}
|
1186
|
jd.run(fis, !go.hasOption("-noconnect"));
|
1187
|
jd.dump(go);
|
1188
|
} catch(EOFException eoe) {
|
1189
|
debugerr("EOF error while attempting to decode file " + filename + ": " + eoe.getMessage());
|
1190
|
eoe.printStackTrace();
|
1191
|
} catch(IOException ioe) {
|
1192
|
debugerr("error while attempting to decode file " + filename + ": " + ioe.getMessage());
|
1193
|
ioe.printStackTrace();
|
1194
|
} finally {
|
1195
|
if(fis != null) {
|
1196
|
try {
|
1197
|
fis.close();
|
1198
|
} catch (Exception ignore) { }
|
1199
|
}
|
1200
|
}
|
1201
|
}
|
1202
|
}
|
1203
|
}
|