001: package jdepend.framework;
002:
003: import java.io.*;
004: import java.util.*;
005:
006: /**
007: * The <code>ClassFileParser</code> class is responsible for
008: * parsing a Java class file to create a <code>JavaClass</code>
009: * instance.
010: *
011: * @author <b>Mike Clark</b>
012: * @author Clarkware Consulting, Inc.
013: */
014:
015: public class ClassFileParser extends AbstractParser {
016:
017: public static final int JAVA_MAGIC = 0xCAFEBABE;
018: public static final int CONSTANT_UTF8 = 1;
019: public static final int CONSTANT_UNICODE = 2;
020: public static final int CONSTANT_INTEGER = 3;
021: public static final int CONSTANT_FLOAT = 4;
022: public static final int CONSTANT_LONG = 5;
023: public static final int CONSTANT_DOUBLE = 6;
024: public static final int CONSTANT_CLASS = 7;
025: public static final int CONSTANT_STRING = 8;
026: public static final int CONSTANT_FIELD = 9;
027: public static final int CONSTANT_METHOD = 10;
028: public static final int CONSTANT_INTERFACEMETHOD = 11;
029: public static final int CONSTANT_NAMEANDTYPE = 12;
030: public static final char CLASS_DESCRIPTOR = 'L';
031: public static final int ACC_INTERFACE = 0x200;
032: public static final int ACC_ABSTRACT = 0x400;
033:
034: private String fileName;
035: private String className;
036: private String super ClassName;
037: private String interfaceNames[];
038: private boolean isAbstract;
039: private JavaClass jClass;
040: private Constant[] constantPool;
041: private FieldOrMethodInfo[] fields;
042: private FieldOrMethodInfo[] methods;
043: private AttributeInfo[] attributes;
044: private DataInputStream in;
045:
046: public ClassFileParser() {
047: this (new PackageFilter());
048: }
049:
050: public ClassFileParser(PackageFilter filter) {
051: super (filter);
052: reset();
053: }
054:
055: private void reset() {
056: className = null;
057: super ClassName = null;
058: interfaceNames = new String[0];
059: isAbstract = false;
060:
061: jClass = null;
062: constantPool = new Constant[1];
063: fields = new FieldOrMethodInfo[0];
064: methods = new FieldOrMethodInfo[0];
065: attributes = new AttributeInfo[0];
066: }
067:
068: /**
069: * Registered parser listeners are informed that the resulting
070: * <code>JavaClass</code> was parsed.
071: */
072: public JavaClass parse(File classFile) throws IOException {
073:
074: this .fileName = classFile.getCanonicalPath();
075:
076: debug("\nParsing " + fileName + "...");
077:
078: FileInputStream in = null;
079:
080: try {
081:
082: in = new FileInputStream(classFile);
083:
084: return parse(in);
085:
086: } finally {
087: if (in != null) {
088: try {
089: in.close();
090: } catch (IOException ioe) {
091: ioe.printStackTrace();
092: }
093: }
094: }
095: }
096:
097: public JavaClass parse(InputStream is) throws IOException {
098:
099: reset();
100:
101: jClass = new JavaClass("Unknown");
102:
103: in = new DataInputStream(is);
104:
105: int magic = parseMagic();
106:
107: int minorVersion = parseMinorVersion();
108: int majorVersion = parseMajorVersion();
109:
110: constantPool = parseConstantPool();
111:
112: parseAccessFlags();
113:
114: className = parseClassName();
115:
116: super ClassName = parseSuperClassName();
117:
118: interfaceNames = parseInterfaces();
119:
120: fields = parseFields();
121:
122: methods = parseMethods();
123:
124: parseAttributes();
125:
126: addClassConstantReferences();
127:
128: onParsedJavaClass(jClass);
129:
130: return jClass;
131: }
132:
133: private int parseMagic() throws IOException {
134: int magic = in.readInt();
135: if (magic != JAVA_MAGIC) {
136: throw new IOException("Invalid class file: " + fileName);
137: }
138:
139: return magic;
140: }
141:
142: private int parseMinorVersion() throws IOException {
143: return in.readUnsignedShort();
144: }
145:
146: private int parseMajorVersion() throws IOException {
147: return in.readUnsignedShort();
148: }
149:
150: private Constant[] parseConstantPool() throws IOException {
151: int constantPoolSize = in.readUnsignedShort();
152:
153: Constant[] pool = new Constant[constantPoolSize];
154:
155: for (int i = 1; i < constantPoolSize; i++) {
156:
157: Constant constant = parseNextConstant();
158:
159: pool[i] = constant;
160:
161: //
162: // 8-byte constants use two constant pool entries
163: //
164: if (constant.getTag() == CONSTANT_DOUBLE
165: || constant.getTag() == CONSTANT_LONG) {
166: i++;
167: }
168: }
169:
170: return pool;
171: }
172:
173: private void parseAccessFlags() throws IOException {
174: int accessFlags = in.readUnsignedShort();
175:
176: boolean isAbstract = ((accessFlags & ACC_ABSTRACT) != 0);
177: boolean isInterface = ((accessFlags & ACC_INTERFACE) != 0);
178:
179: this .isAbstract = isAbstract || isInterface;
180: jClass.isAbstract(this .isAbstract);
181:
182: debug("Parser: abstract = " + this .isAbstract);
183: }
184:
185: private String parseClassName() throws IOException {
186: int entryIndex = in.readUnsignedShort();
187: String className = getClassConstantName(entryIndex);
188: jClass.setName(className);
189: jClass.setPackageName(getPackageName(className));
190:
191: debug("Parser: class name = " + className);
192: debug("Parser: package name = " + getPackageName(className));
193:
194: return className;
195: }
196:
197: private String parseSuperClassName() throws IOException {
198: int entryIndex = in.readUnsignedShort();
199: String super ClassName = getClassConstantName(entryIndex);
200: addImport(getPackageName(super ClassName));
201:
202: debug("Parser: super class name = " + super ClassName);
203:
204: return super ClassName;
205: }
206:
207: private String[] parseInterfaces() throws IOException {
208: int interfacesCount = in.readUnsignedShort();
209: String[] interfaceNames = new String[interfacesCount];
210: for (int i = 0; i < interfacesCount; i++) {
211: int entryIndex = in.readUnsignedShort();
212: interfaceNames[i] = getClassConstantName(entryIndex);
213: addImport(getPackageName(interfaceNames[i]));
214:
215: debug("Parser: interface = " + interfaceNames[i]);
216: }
217:
218: return interfaceNames;
219: }
220:
221: private FieldOrMethodInfo[] parseFields() throws IOException {
222: int fieldsCount = in.readUnsignedShort();
223: FieldOrMethodInfo[] fields = new FieldOrMethodInfo[fieldsCount];
224: for (int i = 0; i < fieldsCount; i++) {
225: fields[i] = parseFieldOrMethodInfo();
226: String descriptor = toUTF8(fields[i].getDescriptorIndex());
227: debug("Parser: field descriptor = " + descriptor);
228: String[] types = descriptorToTypes(descriptor);
229: for (int t = 0; t < types.length; t++) {
230: addImport(getPackageName(types[t]));
231: debug("Parser: field type = " + types[t]);
232: }
233: }
234:
235: return fields;
236: }
237:
238: private FieldOrMethodInfo[] parseMethods() throws IOException {
239: int methodsCount = in.readUnsignedShort();
240: FieldOrMethodInfo[] methods = new FieldOrMethodInfo[methodsCount];
241: for (int i = 0; i < methodsCount; i++) {
242: methods[i] = parseFieldOrMethodInfo();
243: String descriptor = toUTF8(methods[i].getDescriptorIndex());
244: debug("Parser: method descriptor = " + descriptor);
245: String[] types = descriptorToTypes(descriptor);
246: for (int t = 0; t < types.length; t++) {
247: if (types[t].length() > 0) {
248: addImport(getPackageName(types[t]));
249: debug("Parser: method type = " + types[t]);
250: }
251: }
252: }
253:
254: return methods;
255: }
256:
257: private Constant parseNextConstant() throws IOException {
258:
259: Constant result;
260:
261: byte tag = in.readByte();
262:
263: switch (tag) {
264:
265: case (ClassFileParser.CONSTANT_CLASS):
266: case (ClassFileParser.CONSTANT_STRING):
267: result = new Constant(tag, in.readUnsignedShort());
268: break;
269: case (ClassFileParser.CONSTANT_FIELD):
270: case (ClassFileParser.CONSTANT_METHOD):
271: case (ClassFileParser.CONSTANT_INTERFACEMETHOD):
272: case (ClassFileParser.CONSTANT_NAMEANDTYPE):
273: result = new Constant(tag, in.readUnsignedShort(), in
274: .readUnsignedShort());
275: break;
276: case (ClassFileParser.CONSTANT_INTEGER):
277: result = new Constant(tag, new Integer(in.readInt()));
278: break;
279: case (ClassFileParser.CONSTANT_FLOAT):
280: result = new Constant(tag, new Float(in.readFloat()));
281: break;
282: case (ClassFileParser.CONSTANT_LONG):
283: result = new Constant(tag, new Long(in.readLong()));
284: break;
285: case (ClassFileParser.CONSTANT_DOUBLE):
286: result = new Constant(tag, new Double(in.readDouble()));
287: break;
288: case (ClassFileParser.CONSTANT_UTF8):
289: result = new Constant(tag, in.readUTF());
290: break;
291: default:
292: throw new IOException("Unknown constant: " + tag);
293: }
294:
295: return result;
296: }
297:
298: private FieldOrMethodInfo parseFieldOrMethodInfo()
299: throws IOException {
300:
301: FieldOrMethodInfo result = new FieldOrMethodInfo(in
302: .readUnsignedShort(), in.readUnsignedShort(), in
303: .readUnsignedShort());
304:
305: int attributesCount = in.readUnsignedShort();
306: for (int a = 0; a < attributesCount; a++) {
307: parseAttribute();
308: }
309:
310: return result;
311: }
312:
313: private void parseAttributes() throws IOException {
314: int attributesCount = in.readUnsignedShort();
315: attributes = new AttributeInfo[attributesCount];
316:
317: for (int i = 0; i < attributesCount; i++) {
318: attributes[i] = parseAttribute();
319:
320: // Section 4.7.7 of VM Spec - Class File Format
321: if (attributes[i].getName() != null) {
322: if (attributes[i].getName().equals("SourceFile")) {
323: byte[] b = attributes[i].getValue();
324: int b0 = b[0] < 0 ? b[0] + 256 : b[0];
325: int b1 = b[1] < 0 ? b[1] + 256 : b[1];
326: int pe = b0 * 256 + b1;
327:
328: String descriptor = toUTF8(pe);
329: jClass.setSourceFile(descriptor);
330: }
331: }
332: }
333: }
334:
335: private AttributeInfo parseAttribute() throws IOException {
336: AttributeInfo result = new AttributeInfo();
337:
338: int nameIndex = in.readUnsignedShort();
339: if (nameIndex != -1) {
340: result.setName(toUTF8(nameIndex));
341: }
342:
343: int attributeLength = in.readInt();
344: byte[] value = new byte[attributeLength];
345: for (int b = 0; b < attributeLength; b++) {
346: value[b] = in.readByte();
347: }
348:
349: result.setValue(value);
350: return result;
351: }
352:
353: private Constant getConstantPoolEntry(int entryIndex)
354: throws IOException {
355:
356: if (entryIndex < 0 || entryIndex >= constantPool.length) {
357: throw new IOException("Illegal constant pool index : "
358: + entryIndex);
359: }
360:
361: return constantPool[entryIndex];
362: }
363:
364: private void addClassConstantReferences() throws IOException {
365: for (int j = 1; j < constantPool.length; j++) {
366: if (constantPool[j].getTag() == CONSTANT_CLASS) {
367: String name = toUTF8(constantPool[j].getNameIndex());
368: addImport(getPackageName(name));
369:
370: debug("Parser: class type = " + slashesToDots(name));
371: }
372:
373: if (constantPool[j].getTag() == CONSTANT_DOUBLE
374: || constantPool[j].getTag() == CONSTANT_LONG) {
375: j++;
376: }
377: }
378: }
379:
380: private String getClassConstantName(int entryIndex)
381: throws IOException {
382:
383: Constant entry = getConstantPoolEntry(entryIndex);
384: if (entry == null) {
385: return "";
386: }
387: return slashesToDots(toUTF8(entry.getNameIndex()));
388: }
389:
390: private String toUTF8(int entryIndex) throws IOException {
391: Constant entry = getConstantPoolEntry(entryIndex);
392: if (entry.getTag() == CONSTANT_UTF8) {
393: return (String) entry.getValue();
394: }
395:
396: throw new IOException(
397: "Constant pool entry is not a UTF8 type: " + entryIndex);
398: }
399:
400: private void addImport(String importPackage) {
401: if ((importPackage != null)
402: && (getFilter().accept(importPackage))) {
403: jClass.addImportedPackage(new JavaPackage(importPackage));
404: }
405: }
406:
407: private String slashesToDots(String s) {
408: return s.replace('/', '.');
409: }
410:
411: private String getPackageName(String s) {
412: if ((s.length() > 0) && (s.charAt(0) == '[')) {
413: String types[] = descriptorToTypes(s);
414: if (types.length == 0) {
415: return null; // primitives
416: }
417:
418: s = types[0];
419: }
420:
421: s = slashesToDots(s);
422: int index = s.lastIndexOf(".");
423: if (index > 0) {
424: return s.substring(0, index);
425: }
426:
427: return "Default";
428: }
429:
430: private String[] descriptorToTypes(String descriptor) {
431:
432: int typesCount = 0;
433: for (int index = 0; index < descriptor.length(); index++) {
434: if (descriptor.charAt(index) == ';') {
435: typesCount++;
436: }
437: }
438:
439: String types[] = new String[typesCount];
440:
441: int typeIndex = 0;
442: for (int index = 0; index < descriptor.length(); index++) {
443:
444: int startIndex = descriptor
445: .indexOf(CLASS_DESCRIPTOR, index);
446: if (startIndex < 0) {
447: break;
448: }
449:
450: index = descriptor.indexOf(';', startIndex + 1);
451: types[typeIndex++] = descriptor.substring(startIndex + 1,
452: index);
453: }
454:
455: return types;
456: }
457:
458: class Constant {
459:
460: private byte _tag;
461:
462: private int _nameIndex;
463:
464: private int _typeIndex;
465:
466: private Object _value;
467:
468: Constant(byte tag, int nameIndex) {
469: this (tag, nameIndex, -1);
470: }
471:
472: Constant(byte tag, Object value) {
473: this (tag, -1, -1);
474: _value = value;
475: }
476:
477: Constant(byte tag, int nameIndex, int typeIndex) {
478: _tag = tag;
479: _nameIndex = nameIndex;
480: _typeIndex = typeIndex;
481: _value = null;
482: }
483:
484: byte getTag() {
485: return _tag;
486: }
487:
488: int getNameIndex() {
489: return _nameIndex;
490: }
491:
492: int getTypeIndex() {
493: return _typeIndex;
494: }
495:
496: Object getValue() {
497: return _value;
498: }
499:
500: public String toString() {
501:
502: StringBuffer s = new StringBuffer("");
503:
504: s.append("tag: " + getTag());
505:
506: if (getNameIndex() > -1) {
507: s.append(" nameIndex: " + getNameIndex());
508: }
509:
510: if (getTypeIndex() > -1) {
511: s.append(" typeIndex: " + getTypeIndex());
512: }
513:
514: if (getValue() != null) {
515: s.append(" value: " + getValue());
516: }
517:
518: return s.toString();
519: }
520: }
521:
522: class FieldOrMethodInfo {
523:
524: private int _accessFlags;
525:
526: private int _nameIndex;
527:
528: private int _descriptorIndex;
529:
530: FieldOrMethodInfo(int accessFlags, int nameIndex,
531: int descriptorIndex) {
532:
533: _accessFlags = accessFlags;
534: _nameIndex = nameIndex;
535: _descriptorIndex = descriptorIndex;
536: }
537:
538: int accessFlags() {
539: return _accessFlags;
540: }
541:
542: int getNameIndex() {
543: return _nameIndex;
544: }
545:
546: int getDescriptorIndex() {
547: return _descriptorIndex;
548: }
549:
550: public String toString() {
551: StringBuffer s = new StringBuffer("");
552:
553: try {
554:
555: s.append("\n name (#" + getNameIndex() + ") = "
556: + toUTF8(getNameIndex()));
557:
558: s.append("\n signature (#" + getDescriptorIndex()
559: + ") = " + toUTF8(getDescriptorIndex()));
560:
561: String[] types = descriptorToTypes(toUTF8(getDescriptorIndex()));
562: for (int t = 0; t < types.length; t++) {
563: s.append("\n type = " + types[t]);
564: }
565:
566: } catch (Exception e) {
567: e.printStackTrace();
568: }
569:
570: return s.toString();
571: }
572: }
573:
574: class AttributeInfo {
575:
576: private String name;
577:
578: private byte[] value;
579:
580: public void setName(String name) {
581: this .name = name;
582: }
583:
584: public String getName() {
585: return this .name;
586: }
587:
588: public void setValue(byte[] value) {
589: this .value = value;
590: }
591:
592: public byte[] getValue() {
593: return this .value;
594: }
595: }
596:
597: /**
598: * Returns a string representation of this object.
599: *
600: * @return String representation.
601: */
602: public String toString() {
603:
604: StringBuffer s = new StringBuffer();
605:
606: try {
607:
608: s.append("\n" + className + ":\n");
609:
610: s.append("\nConstants:\n");
611: for (int i = 1; i < constantPool.length; i++) {
612: Constant entry = getConstantPoolEntry(i);
613: s.append(" " + i + ". " + entry.toString() + "\n");
614: if (entry.getTag() == CONSTANT_DOUBLE
615: || entry.getTag() == CONSTANT_LONG) {
616: i++;
617: }
618: }
619:
620: s.append("\nClass Name: " + className + "\n");
621: s.append("Super Name: " + super ClassName + "\n\n");
622:
623: s.append(interfaceNames.length + " interfaces\n");
624: for (int i = 0; i < interfaceNames.length; i++) {
625: s.append(" " + interfaceNames[i] + "\n");
626: }
627:
628: s.append("\n" + fields.length + " fields\n");
629: for (int i = 0; i < fields.length; i++) {
630: s.append(fields[i].toString() + "\n");
631: }
632:
633: s.append("\n" + methods.length + " methods\n");
634: for (int i = 0; i < methods.length; i++) {
635: s.append(methods[i].toString() + "\n");
636: }
637:
638: s.append("\nDependencies:\n");
639: Iterator imports = jClass.getImportedPackages().iterator();
640: while (imports.hasNext()) {
641: JavaPackage jPackage = (JavaPackage) imports.next();
642: s.append(" " + jPackage.getName() + "\n");
643: }
644:
645: } catch (Exception e) {
646: e.printStackTrace();
647: }
648:
649: return s.toString();
650: }
651:
652: /**
653: * Test main.
654: */
655: public static void main(String args[]) {
656: try {
657:
658: ClassFileParser.DEBUG = true;
659:
660: if (args.length <= 0) {
661: System.err
662: .println("usage: ClassFileParser <class-file>");
663: System.exit(0);
664: }
665:
666: ClassFileParser parser = new ClassFileParser();
667:
668: parser.parse(new File(args[0]));
669:
670: System.err.println(parser.toString());
671:
672: } catch (Exception e) {
673: System.err.println(e.getMessage());
674: }
675: }
676: }
|