1 |
5c81df14
|
@havlijan17
|
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 |
|
|
}
|