blob: d7ed1ce9b0c3d6bfc2cfa0a6a883c359b4c427e0 [file] [log] [blame]
Stuart McCulloch3fdcd852011-10-17 10:31:43 +00001package aQute.lib.osgi;
2
3import java.io.*;
4import java.lang.annotation.*;
5import java.nio.*;
6import java.util.*;
7import java.util.regex.*;
8
9import aQute.bnd.annotation.*;
10import aQute.libg.generics.*;
11
12public class Clazz {
13
14 public class ClassConstant {
15 int cname;
16
17 public ClassConstant(int class_index) {
18 this.cname = class_index;
19 }
20
21 public String getName() {
22 return (String) pool[cname];
23 }
24 }
25
26 public static enum JAVA {
27 UNKNOWN(Integer.MAX_VALUE), OpenJDK7(51), J2S6(50), J2SE5(49), JDK1_4(48), JDK1_3(47), JDK1_2(
28 46), JDK1_1(45);
29
30 final int major;
31
32 JAVA(int major) {
33 this.major = major;
34 }
35
36 static JAVA format(int n) {
37 for (JAVA e : JAVA.values())
38 if (e.major == n)
39 return e;
40 return UNKNOWN;
41 }
42
43 public int getMajor() {
44 return major;
45 }
46
47 public boolean hasAnnotations() {
48 return major >= J2SE5.major;
49 }
50
51 public boolean hasGenerics() {
52 return major >= J2SE5.major;
53 }
54
55 public boolean hasEnums() {
56 return major >= J2SE5.major;
57 }
58 };
59
60 public static enum QUERY {
61 IMPLEMENTS, EXTENDS, IMPORTS, NAMED, ANY, VERSION, CONCRETE, ABSTRACT, PUBLIC, ANNOTATION, RUNTIMEANNOTATIONS, CLASSANNOTATIONS
62 };
63
64 public static EnumSet<QUERY> HAS_ARGUMENT = EnumSet.of(QUERY.IMPLEMENTS, QUERY.EXTENDS,
65 QUERY.IMPORTS, QUERY.NAMED,
66 QUERY.VERSION, QUERY.ANNOTATION);
67
68 /**
69 * <pre>
70 * ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its
71 * package.
72 * ACC_FINAL 0x0010 Declared final; no subclasses allowed.
73 * ACC_SUPER 0x0020 Treat superclass methods specially when invoked by the
74 * invokespecial instruction.
75 * ACC_INTERFACE 0x0200 Is an interface, not a
76 * class.
77 * ACC_ABSTRACT 0x0400 Declared abstract; may not be instantiated.
78 * </pre>
79 *
80 * @param mod
81 */
82 final static int ACC_PUBLIC = 0x0001; // Declared
83 // public;
84 // may
85 // be
86 // accessed
87 // from outside its package.
88 final static int ACC_FINAL = 0x0010; // Declared
89 // final;
90 // no
91 // subclasses
92 // allowed.
93 final static int ACC_SUPER = 0x0020; // Treat
94 // superclass
95 // methods
96 // specially when invoked by the
97 // invokespecial instruction.
98 final static int ACC_INTERFACE = 0x0200; // Is
99 // an
100 // interface,
101 // not
102 // a
103 // classs
104 final static int ACC_ABSTRACT = 0x0400; // Declared
105
106 // abstract;
107 // may
108 // not
109 // be
110
111 // instantiated.
112
113 final static int ACC_ENUM = 0x04000;
114
115 static protected class Assoc {
116 Assoc(byte tag, int a, int b) {
117 this.tag = tag;
118 this.a = a;
119 this.b = b;
120 }
121
122 byte tag;
123 int a;
124 int b;
125 }
126
127 static public class FieldDef implements Comparable<FieldDef> {
128 public FieldDef(int access, String clazz, String name, String descriptor) {
129 this.access = access;
130 this.clazz = clazz.replace('/', '.');
131 this.name = name;
132 this.descriptor = descriptor;
133 }
134
135 final public int access;
136 final public String clazz;
137 final public String name;
138 final public String descriptor;
139 public String signature;
140 public Object constant;
141
142 public boolean equals(Object other) {
143 if (!(other instanceof MethodDef))
144 return false;
145
146 FieldDef m = (FieldDef) other;
147 return clazz.equals(m.clazz) && name.equals(m.name) && descriptor.equals(m.descriptor);
148 }
149
150 public int hashCode() {
151 return clazz.hashCode() ^ name.hashCode() ^ descriptor.hashCode();
152 }
153
154 public int compareTo(FieldDef o) {
155 int result = clazz.compareTo(o.clazz);
156 if (result == 0) {
157 result = name.compareTo(o.name);
158 if (result == 0) {
159 result = descriptor.compareTo(o.descriptor);
160 }
161 }
162 return result;
163 }
164
165 public String getPretty() {
166 return name;
167 }
168
169 public String toString() {
170 return getPretty();
171 }
172
173 public boolean isEnum() {
174 return (access & ACC_ENUM) != 0;
175 }
176 }
177
178 static public class MethodDef extends FieldDef {
179 Pattern METHOD_DESCRIPTOR = Pattern.compile("\\((.*)\\)(.+)");
180
181 public MethodDef(int access, String clazz, String method, String descriptor) {
182 super(access, clazz, method, descriptor);
183 }
184
185 public boolean isConstructor() {
186 return name.equals("<init>") || name.equals("<clinit>");
187 }
188
189 public String getReturnType() {
190 String use = descriptor;
191 if (signature != null)
192 use = signature;
193
194 Matcher m = METHOD_DESCRIPTOR.matcher(use);
195 if (!m.matches())
196 throw new IllegalArgumentException("Not a valid method descriptor: " + descriptor);
197
198 String returnType = m.group(2);
199 return objectDescriptorToFQN(returnType);
200 }
201
202 public String getPretty() {
203
204 StringBuilder sb = new StringBuilder();
205 sb.append(descriptor.charAt(0));
206 int index = 1;
207 String del = "";
208 while (index < descriptor.length() && descriptor.charAt(index) != ')') {
209 sb.append(del);
210 index = printParameter(sb, descriptor, index);
211 del = ",";
212 }
213 sb.append(descriptor.charAt(index++));
214 StringBuilder sb2 = new StringBuilder();
215 if (isConstructor()) {
216 sb2.append(getShortName(clazz));
217 index++; // skip the V
218 } else {
219 printParameter(sb2, descriptor, index);
220 sb2.append(" ");
221 sb2.append(getShortName(clazz));
222 sb2.append(".");
223 sb2.append(name);
224 }
225 sb2.append(sb);
226 return sb2.toString();
227 }
228
229 private int printParameter(StringBuilder sb, CharSequence descriptor, int index) {
230 char c = descriptor.charAt(index++);
231 switch (c) {
232 case 'B':
233 sb.append("byte");
234 break;
235 case 'C':
236 sb.append("char");
237 break;
238 case 'D':
239 sb.append("double");
240 break;
241 case 'F':
242 sb.append("float");
243 break;
244 case 'I':
245 sb.append("int");
246 break;
247 case 'J':
248 sb.append("long");
249 break;
250 case 'S':
251 sb.append("short");
252 break;
253 case 'Z':
254 sb.append("boolean");
255 break;
256 case 'V':
257 sb.append("void");
258 break;
259 case 'L':
260 index = reference(sb, descriptor, index);
261 break;
262
263 case '[':
264 index = array(sb, descriptor, index);
265 break;
266 }
267 return index;
268 }
269
270 private int reference(StringBuilder sb, CharSequence descriptor, int index) {
271 int n = sb.length();
272 int lastSlash = n;
273 while (index < descriptor.length() && descriptor.charAt(index) != ';') {
274 char c = descriptor.charAt(index++);
275 if (c == '/') {
276 c = '.';
277 lastSlash = sb.length() + 1;
278 }
279 sb.append(c);
280 }
281 if (lastSlash != n) {
282 sb.delete(n, lastSlash);
283 }
284 return ++index;
285 }
286
287 private int array(StringBuilder sb, CharSequence descriptor, int index) {
288 int n = 1;
289 while (index < descriptor.length() && descriptor.charAt(index) == '[') {
290 index++;
291 }
292 index = printParameter(sb, descriptor, index);
293 while (n-- > 0) {
294 sb.append("[]");
295 }
296 return index;
297 }
298 }
299
300 final static byte SkipTable[] = { 0, // 0 non existent
301 -1, // 1 CONSTANT_utf8 UTF 8, handled in
302 // method
303 -1, // 2
304 4, // 3 CONSTANT_Integer
305 4, // 4 CONSTANT_Float
306 8, // 5 CONSTANT_Long (index +=2!)
307 8, // 6 CONSTANT_Double (index +=2!)
308 -1, // 7 CONSTANT_Class
309 2, // 8 CONSTANT_String
310 4, // 9 CONSTANT_FieldRef
311 4, // 10 CONSTANT_MethodRef
312 4, // 11 CONSTANT_InterfaceMethodRef
313 4, // 12 CONSTANT_NameAndType
314 };
315
316 boolean isAbstract;
317 boolean isPublic;
318 boolean isEnum;
319 boolean hasRuntimeAnnotations;
320 boolean hasClassAnnotations;
321
322 String className;
323 Object pool[];
324 int intPool[];
325 Set<String> imports = Create.set();
326 String path;
327 int minor = 0;
328 int major = 0;
329 int access = 0;
330 String sourceFile;
331 Set<String> xref;
332 Set<Integer> classes;
333 Set<Integer> descriptors;
334 Set<String> annotations;
335 int forName = 0;
336 int class$ = 0;
337 String[] interfaces;
338 String zuper;
339 ClassDataCollector cd = null;
340 Resource resource;
341 FieldDef last = null;
342
343 public Clazz(String path, Resource resource) {
344 this.path = path;
345 this.resource = resource;
346 }
347
348 public Set<String> parseClassFile() throws Exception {
349 return parseClassFileWithCollector(null);
350 }
351
352 public Set<String> parseClassFile(InputStream in) throws IOException {
353 return parseClassFile(in, null);
354 }
355
356 public Set<String> parseClassFileWithCollector(ClassDataCollector cd) throws Exception {
357 InputStream in = resource.openInputStream();
358 try {
359 return parseClassFile(in, cd);
360 } finally {
361 in.close();
362 }
363 }
364
365 public Set<String> parseClassFile(InputStream in, ClassDataCollector cd) throws IOException {
366 DataInputStream din = new DataInputStream(in);
367 try {
368 this.cd = cd;
369 return parseClassFile(din);
370 } finally {
371 cd = null;
372 din.close();
373 }
374 }
375
376 Set<String> parseClassFile(DataInputStream in) throws IOException {
377
378 xref = new HashSet<String>();
379 classes = new HashSet<Integer>();
380 descriptors = new HashSet<Integer>();
381
382 boolean crawl = cd != null; // Crawl the byte code if we have a
383 // collector
384 int magic = in.readInt();
385 if (magic != 0xCAFEBABE)
386 throw new IOException("Not a valid class file (no CAFEBABE header)");
387
388 minor = in.readUnsignedShort(); // minor version
389 major = in.readUnsignedShort(); // major version
390 int count = in.readUnsignedShort();
391 pool = new Object[count];
392 intPool = new int[count];
393
394 process: for (int poolIndex = 1; poolIndex < count; poolIndex++) {
395 byte tag = in.readByte();
396 switch (tag) {
397 case 0:
398 break process;
399 case 1:
400 constantUtf8(in, poolIndex);
401 break;
402
403 case 3:
404 constantInteger(in, poolIndex);
405 break;
406
407 case 4:
408 constantFloat(in, poolIndex);
409 break;
410
411 // For some insane optimization reason are
412 // the long and the double two entries in the
413 // constant pool. See 4.4.5
414 case 5:
415 constantLong(in, poolIndex);
416 poolIndex++;
417 break;
418
419 case 6:
420 constantDouble(in, poolIndex);
421 poolIndex++;
422 break;
423
424 case 7:
425 constantClass(in, poolIndex);
426 break;
427
428 case 8:
429 constantString(in, poolIndex);
430 break;
431
432 case 10: // Method ref
433 case 11: // Interface Method ref
434 methodRef(in, poolIndex);
435 break;
436
437 // Name and Type
438 case 12:
439 nameAndType(in, poolIndex, tag);
440 break;
441
442 // We get the skip count for each record type
443 // from the SkipTable. This will also automatically
444 // abort when
445 default:
446 if (tag == 2)
447 throw new IOException("Invalid tag " + tag);
448 in.skipBytes(SkipTable[tag]);
449 break;
450 }
451 }
452
453 pool(pool, intPool);
454 /*
455 * Parse after the constant pool, code thanks to Hans Christian
456 * Falkenberg
457 */
458
459 int access_flags = in.readUnsignedShort(); // access
460 isAbstract = (access_flags & ACC_ABSTRACT) != 0;
461 isPublic = (access_flags & ACC_PUBLIC) != 0;
462 isEnum = (access_flags & ACC_ENUM) != 0;
463
464 int this_class = in.readUnsignedShort();
465 className = (String) pool[intPool[this_class]];
466
467 try {
468
469 if (cd != null) {
470 if (!cd.classStart(access_flags, className))
471 return null;
472 }
473
474 int super_class = in.readUnsignedShort();
475 zuper = (String) pool[intPool[super_class]];
476 if (zuper != null) {
477 String pack = getPackage(zuper);
478 packageReference(pack);
479 if (cd != null)
480 cd.extendsClass(zuper);
481 }
482
483 int interfacesCount = in.readUnsignedShort();
484 if (interfacesCount > 0) {
485 interfaces = new String[interfacesCount];
486 for (int i = 0; i < interfacesCount; i++)
487 interfaces[i] = (String) pool[intPool[in.readUnsignedShort()]];
488 if (cd != null)
489 cd.implementsInterfaces(interfaces);
490 }
491
492 int fieldsCount = in.readUnsignedShort();
493 for (int i = 0; i < fieldsCount; i++) {
494 access_flags = in.readUnsignedShort(); // skip access flags
495 int name_index = in.readUnsignedShort();
496 int descriptor_index = in.readUnsignedShort();
497
498 // Java prior to 1.5 used a weird
499 // static variable to hold the com.X.class
500 // result construct. If it did not find it
501 // it would create a variable class$com$X
502 // that would be used to hold the class
503 // object gotten with Class.forName ...
504 // Stupidly, they did not actively use the
505 // class name for the field type, so bnd
506 // would not see a reference. We detect
507 // this case and add an artificial descriptor
508 String name = pool[name_index].toString(); // name_index
509 if (name.startsWith("class$")) {
510 crawl = true;
511 }
512 if (cd != null)
513 cd.field(last = new FieldDef(access_flags, className, name,
514 pool[descriptor_index].toString()));
515 descriptors.add(new Integer(descriptor_index));
516 doAttributes(in, ElementType.FIELD, false);
517 }
518
519 //
520 // Check if we have to crawl the code to find
521 // the ldc(_w) <string constant> invokestatic Class.forName
522 // if so, calculate the method ref index so we
523 // can do this efficiently
524 //
525 if (crawl) {
526 forName = findMethodReference("java/lang/Class", "forName",
527 "(Ljava/lang/String;)Ljava/lang/Class;");
528 class$ = findMethodReference(className, "class$",
529 "(Ljava/lang/String;)Ljava/lang/Class;");
530 } else if (major == 48) {
531 forName = findMethodReference("java/lang/Class", "forName",
532 "(Ljava/lang/String;)Ljava/lang/Class;");
533 if (forName > 0) {
534 crawl = true;
535 class$ = findMethodReference(className, "class$",
536 "(Ljava/lang/String;)Ljava/lang/Class;");
537 }
538 }
539
540 //
541 // Handle the methods
542 //
543 int methodCount = in.readUnsignedShort();
544 for (int i = 0; i < methodCount; i++) {
545 access_flags = in.readUnsignedShort();
546 int name_index = in.readUnsignedShort();
547 int descriptor_index = in.readUnsignedShort();
548 descriptors.add(new Integer(descriptor_index));
549 String name = pool[name_index].toString();
550 String descriptor = pool[descriptor_index].toString();
551 if (cd != null) {
552 MethodDef mdef = new MethodDef(access_flags, className, name, descriptor);
553 last = mdef;
554 cd.method(mdef);
555 }
556
557 if ("<init>".equals(name)) {
558 doAttributes(in, ElementType.CONSTRUCTOR, crawl);
559 } else {
560 doAttributes(in, ElementType.METHOD, crawl);
561 }
562 }
563
564 doAttributes(in, ElementType.TYPE, false);
565
566 //
567 // Now iterate over all classes we found and
568 // parse those as well. We skip duplicates
569 //
570
571 for (int n : classes) {
572 String clazz = (String) pool[n];
573 if (clazz.endsWith(";") || clazz.startsWith("["))
574 parseReference(clazz, 0);
575 else {
576
577 String pack = getPackage(clazz);
578 packageReference(pack);
579 }
580 }
581
582 //
583 // Parse all the descriptors we found
584 //
585
586 for (Iterator<Integer> e = descriptors.iterator(); e.hasNext();) {
587 Integer index = e.next();
588 String prototype = (String) pool[index.intValue()];
589 if (prototype != null)
590 parseDescriptor(prototype);
591 else
592 System.err.println("Unrecognized descriptor: " + index);
593 }
594 Set<String> xref = this.xref;
595 reset();
596 return xref;
597 } finally {
598 if (cd != null)
599 cd.classEnd();
600 }
601 }
602
603 private void constantFloat(DataInputStream in, int poolIndex) throws IOException {
604 if (cd != null)
605 pool[poolIndex] = in.readFloat(); // ALU
606 else
607 in.skipBytes(4);
608 }
609
610 private void constantInteger(DataInputStream in, int poolIndex) throws IOException {
611 intPool[poolIndex] = in.readInt();
612 if (cd != null)
613 pool[poolIndex] = intPool[poolIndex];
614 }
615
616 protected void pool(Object[] pool, int[] intPool) {
617 }
618
619 /**
620 * @param in
621 * @param poolIndex
622 * @param tag
623 * @throws IOException
624 */
625 protected void nameAndType(DataInputStream in, int poolIndex, byte tag) throws IOException {
626 int name_index = in.readUnsignedShort();
627 int descriptor_index = in.readUnsignedShort();
628 descriptors.add(new Integer(descriptor_index));
629 pool[poolIndex] = new Assoc(tag, name_index, descriptor_index);
630 }
631
632 /**
633 * @param in
634 * @param poolIndex
635 * @param tag
636 * @throws IOException
637 */
638 private void methodRef(DataInputStream in, int poolIndex) throws IOException {
639 int class_index = in.readUnsignedShort();
640 int name_and_type_index = in.readUnsignedShort();
641 pool[poolIndex] = new Assoc((byte) 10, class_index, name_and_type_index);
642 }
643
644 /**
645 * @param in
646 * @param poolIndex
647 * @throws IOException
648 */
649 private void constantString(DataInputStream in, int poolIndex) throws IOException {
650 int string_index = in.readUnsignedShort();
651 intPool[poolIndex] = string_index;
652 }
653
654 /**
655 * @param in
656 * @param poolIndex
657 * @throws IOException
658 */
659 protected void constantClass(DataInputStream in, int poolIndex) throws IOException {
660 int class_index = in.readUnsignedShort();
661 classes.add(new Integer(class_index));
662 intPool[poolIndex] = class_index;
663 ClassConstant c = new ClassConstant(class_index);
664 pool[poolIndex] = c;
665 }
666
667 /**
668 * @param in
669 * @throws IOException
670 */
671 protected void constantDouble(DataInputStream in, int poolIndex) throws IOException {
672 if (cd != null)
673 pool[poolIndex] = in.readDouble();
674 else
675 in.skipBytes(8);
676 }
677
678 /**
679 * @param in
680 * @throws IOException
681 */
682 protected void constantLong(DataInputStream in, int poolIndex) throws IOException {
683 if (cd != null) {
684 pool[poolIndex] = in.readLong();
685 } else
686 in.skipBytes(8);
687 }
688
689 /**
690 * @param in
691 * @param poolIndex
692 * @throws IOException
693 */
694 protected void constantUtf8(DataInputStream in, int poolIndex) throws IOException {
695 // CONSTANT_Utf8
696
697 String name = in.readUTF();
698 xref.add(name);
699 pool[poolIndex] = name;
700 }
701
702 /**
703 * Find a method reference in the pool that points to the given class,
704 * methodname and descriptor.
705 *
706 * @param clazz
707 * @param methodname
708 * @param descriptor
709 * @return index in constant pool
710 */
711 private int findMethodReference(String clazz, String methodname, String descriptor) {
712 for (int i = 1; i < pool.length; i++) {
713 if (pool[i] instanceof Assoc) {
714 Assoc methodref = (Assoc) pool[i];
715 if (methodref.tag == 10) {
716 // Method ref
717 int class_index = methodref.a;
718 int class_name_index = intPool[class_index];
719 if (clazz.equals(pool[class_name_index])) {
720 int name_and_type_index = methodref.b;
721 Assoc name_and_type = (Assoc) pool[name_and_type_index];
722 if (name_and_type.tag == 12) {
723 // Name and Type
724 int name_index = name_and_type.a;
725 int type_index = name_and_type.b;
726 if (methodname.equals(pool[name_index])) {
727 if (descriptor.equals(pool[type_index])) {
728 return i;
729 }
730 }
731 }
732 }
733 }
734 }
735 }
736 return -1;
737 }
738
739 /**
740 * Called for each attribute in the class, field, or method.
741 *
742 * @param in
743 * The stream
744 * @throws IOException
745 */
746 private void doAttributes(DataInputStream in, ElementType member, boolean crawl)
747 throws IOException {
748 int attributesCount = in.readUnsignedShort();
749 for (int j = 0; j < attributesCount; j++) {
750 // skip name CONSTANT_Utf8 pointer
751 doAttribute(in, member, crawl);
752 }
753 }
754
755 /**
756 * Process a single attribute, if not recognized, skip it.
757 *
758 * @param in
759 * the data stream
760 * @throws IOException
761 */
762 private void doAttribute(DataInputStream in, ElementType member, boolean crawl)
763 throws IOException {
764 int attribute_name_index = in.readUnsignedShort();
765 String attributeName = (String) pool[attribute_name_index];
766 long attribute_length = in.readInt();
767 attribute_length &= 0xFFFFFFFF;
768 if ("RuntimeVisibleAnnotations".equals(attributeName))
769 doAnnotations(in, member, RetentionPolicy.RUNTIME);
770 else if ("RuntimeVisibleParameterAnnotations".equals(attributeName))
771 doParameterAnnotations(in, member, RetentionPolicy.RUNTIME);
772 else if ("RuntimeInvisibleAnnotations".equals(attributeName))
773 doAnnotations(in, member, RetentionPolicy.CLASS);
774 else if ("RuntimeInvisibleParameterAnnotations".equals(attributeName))
775 doParameterAnnotations(in, member, RetentionPolicy.CLASS);
776 else if ("InnerClasses".equals(attributeName))
777 doInnerClasses(in);
778 else if ("EnclosingMethod".equals(attributeName))
779 doEnclosingMethod(in);
780 else if ("SourceFile".equals(attributeName))
781 doSourceFile(in);
782 else if ("Code".equals(attributeName) && crawl)
783 doCode(in);
784 else if ("Signature".equals(attributeName))
785 doSignature(in, member);
786 else if ("ConstantValue".equals(attributeName))
787 doConstantValue(in);
788 else {
789 if (attribute_length > 0x7FFFFFFF) {
790 throw new IllegalArgumentException("Attribute > 2Gb");
791 }
792 in.skipBytes((int) attribute_length);
793 }
794 }
795
796 /**
797 * <pre>
798 * EnclosingMethod_attribute {
799 * u2 attribute_name_index;
800 * u4 attribute_length;
801 * u2 class_index
802 * u2 method_index;
803 * }
804 * </pre>
805 *
806 *
807 * @param in
808 * @throws IOException
809 */
810 private void doEnclosingMethod(DataInputStream in) throws IOException {
811 int cIndex = in.readShort();
812 int mIndex = in.readShort();
813
814 if (cd != null) {
815 int nameIndex = intPool[cIndex];
816 String cName = (String) pool[nameIndex];
817
818 String mName = null;
819 String mDescriptor = null;
820
821 if (mIndex != 0) {
822 Assoc nameAndType = (Assoc) pool[mIndex];
823 mName = (String) pool[nameAndType.a];
824 mDescriptor = (String) pool[nameAndType.b];
825 }
826 cd.enclosingMethod(cName, mName, mDescriptor);
827 }
828 }
829
830 /**
831 * <pre>
832 * InnerClasses_attribute {
833 * u2 attribute_name_index;
834 * u4 attribute_length;
835 * u2 number_of_classes; {
836 * u2 inner_class_info_index;
837 * u2 outer_class_info_index;
838 * u2 inner_name_index;
839 * u2 inner_class_access_flags;
840 * } classes[number_of_classes];
841 * }
842 * </pre>
843 *
844 * @param in
845 * @throws IOException
846 */
847 private void doInnerClasses(DataInputStream in) throws IOException {
848 int number_of_classes = in.readShort();
849 for (int i = 0; i < number_of_classes; i++) {
850 int inner_class_info_index = in.readShort();
851 int outer_class_info_index = in.readShort();
852 int inner_name_index = in.readShort();
853 int inner_class_access_flags = in.readShort() & 0xFFFF;
854
855 if (cd != null) {
856 String innerClass = null;
857 String outerClass = null;
858 String innerName = null;
859
860 if (inner_class_info_index != 0) {
861 int nameIndex = intPool[inner_class_info_index];
862 innerClass = (String) pool[nameIndex];
863 }
864
865 if (outer_class_info_index != 0) {
866 int nameIndex = intPool[outer_class_info_index];
867 outerClass = (String) pool[nameIndex];
868 }
869
870 if (inner_name_index != 0)
871 innerName = (String) pool[inner_name_index];
872
873 cd.innerClass(innerClass, outerClass, innerName, inner_class_access_flags);
874 }
875 }
876 }
877
878 /**
879 * Handle a signature
880 *
881 * <pre>
882 * Signature_attribute {
883 * u2 attribute_name_index;
884 * u4 attribute_length;
885 * u2 signature_index;
886 * }
887 * </pre>
888 *
889 * @param member
890 */
891
892 void doSignature(DataInputStream in, ElementType member) throws IOException {
893 int signature_index = in.readUnsignedShort();
894 String signature = (String) pool[signature_index];
895
896 // System.out.println("Signature " + signature );
897
898 // The type signature is kind of weird,
899 // lets skip it for now. Seems to be some kind of
900 // type variable name index but it does not seem to
901 // conform to the language specification.
902 if (member != ElementType.TYPE)
903 parseDescriptor(signature);
904
905 if (last != null)
906 last.signature = signature;
907
908 if (cd != null)
909 cd.signature(signature);
910 }
911
912 /**
913 * Handle a constant value call the data collector with it
914 */
915 void doConstantValue(DataInputStream in) throws IOException {
916 int constantValue_index = in.readUnsignedShort();
917 if (cd == null)
918 return;
919
920 Object object = pool[constantValue_index];
921 if (object == null)
922 object = pool[intPool[constantValue_index]];
923
924 last.constant = object;
925 cd.constant(object);
926 }
927
928 /**
929 * <pre>
930 * Code_attribute {
931 * u2 attribute_name_index;
932 * u4 attribute_length;
933 * u2 max_stack;
934 * u2 max_locals;
935 * u4 code_length;
936 * u1 code[code_length];
937 * u2 exception_table_length;
938 * { u2 start_pc;
939 * u2 end_pc;
940 * u2 handler_pc;
941 * u2 catch_type;
942 * } exception_table[exception_table_length];
943 * u2 attributes_count;
944 * attribute_info attributes[attributes_count];
945 * }
946 * </pre>
947 *
948 * @param in
949 * @param pool
950 * @throws IOException
951 */
952 private void doCode(DataInputStream in) throws IOException {
953 /* int max_stack = */in.readUnsignedShort();
954 /* int max_locals = */in.readUnsignedShort();
955 int code_length = in.readInt();
956 byte code[] = new byte[code_length];
957 in.readFully(code);
958 crawl(code);
959 int exception_table_length = in.readUnsignedShort();
960 in.skipBytes(exception_table_length * 8);
961 doAttributes(in, ElementType.METHOD, false);
962 }
963
964 /**
965 * We must find Class.forName references ...
966 *
967 * @param code
968 */
969 protected void crawl(byte[] code) {
970 ByteBuffer bb = ByteBuffer.wrap(code);
971 bb.order(ByteOrder.BIG_ENDIAN);
972 int lastReference = -1;
973
974 while (bb.remaining() > 0) {
975 int instruction = 0xFF & bb.get();
976 switch (instruction) {
977 case OpCodes.ldc:
978 lastReference = 0xFF & bb.get();
979 break;
980
981 case OpCodes.ldc_w:
982 lastReference = 0xFFFF & bb.getShort();
983 break;
984
985 case OpCodes.invokespecial: {
986 int mref = 0xFFFF & bb.getShort();
987 if (cd != null)
988 cd.reference(getMethodDef(0, mref));
989 break;
990 }
991
992 case OpCodes.invokevirtual: {
993 int mref = 0xFFFF & bb.getShort();
994 if (cd != null)
995 cd.reference(getMethodDef(0, mref));
996 break;
997 }
998
999 case OpCodes.invokeinterface: {
1000 int mref = 0xFFFF & bb.getShort();
1001 if (cd != null)
1002 cd.reference(getMethodDef(0, mref));
1003 break;
1004 }
1005
1006 case OpCodes.invokestatic: {
1007 int methodref = 0xFFFF & bb.getShort();
1008 if (cd != null)
1009 cd.reference(getMethodDef(0, methodref));
1010
1011 if ((methodref == forName || methodref == class$) && lastReference != -1
1012 && pool[intPool[lastReference]] instanceof String) {
1013 String clazz = (String) pool[intPool[lastReference]];
1014 if (clazz.startsWith("[") || clazz.endsWith(";"))
1015 parseReference(clazz, 0);
1016 else {
1017 int n = clazz.lastIndexOf('.');
1018 if (n > 0)
1019 packageReference(clazz.substring(0, n));
1020 }
1021 }
1022 break;
1023 }
1024
1025 case OpCodes.tableswitch:
1026 // Skip to place divisible by 4
1027 while ((bb.position() & 0x3) != 0)
1028 bb.get();
1029 /* int deflt = */
1030 bb.getInt();
1031 int low = bb.getInt();
1032 int high = bb.getInt();
1033 bb.position(bb.position() + (high - low + 1) * 4);
1034 lastReference = -1;
1035 break;
1036
1037 case OpCodes.lookupswitch:
1038 // Skip to place divisible by 4
1039 while ((bb.position() & 0x3) != 0)
1040 bb.get();
1041 /* deflt = */
1042 bb.getInt();
1043 int npairs = bb.getInt();
1044 bb.position(bb.position() + npairs * 8);
1045 lastReference = -1;
1046 break;
1047
1048 default:
1049 lastReference = -1;
1050 bb.position(bb.position() + OpCodes.OFFSETS[instruction]);
1051 }
1052 }
1053 }
1054
1055 private void doSourceFile(DataInputStream in) throws IOException {
1056 int sourcefile_index = in.readUnsignedShort();
1057 this.sourceFile = pool[sourcefile_index].toString();
1058 }
1059
1060 private void doParameterAnnotations(DataInputStream in, ElementType member,
1061 RetentionPolicy policy) throws IOException {
1062 int num_parameters = in.readUnsignedByte();
1063 for (int p = 0; p < num_parameters; p++) {
1064 if (cd != null)
1065 cd.parameter(p);
1066 doAnnotations(in, member, policy);
1067 }
1068 }
1069
1070 private void doAnnotations(DataInputStream in, ElementType member, RetentionPolicy policy)
1071 throws IOException {
1072 int num_annotations = in.readUnsignedShort(); // # of annotations
1073 for (int a = 0; a < num_annotations; a++) {
1074 if (cd == null)
1075 doAnnotation(in, member, policy, false);
1076 else {
1077 Annotation annotion = doAnnotation(in, member, policy, true);
1078 cd.annotation(annotion);
1079 }
1080 }
1081 }
1082
1083 private Annotation doAnnotation(DataInputStream in, ElementType member, RetentionPolicy policy,
1084 boolean collect) throws IOException {
1085 int type_index = in.readUnsignedShort();
1086 if (annotations == null)
1087 annotations = new HashSet<String>();
1088
1089 annotations.add(pool[type_index].toString());
1090
1091 if (policy == RetentionPolicy.RUNTIME) {
1092 descriptors.add(new Integer(type_index));
1093 hasRuntimeAnnotations = true;
1094 } else {
1095 hasClassAnnotations = true;
1096 }
1097 String name = (String) pool[type_index];
1098 int num_element_value_pairs = in.readUnsignedShort();
1099 Map<String, Object> elements = null;
1100 for (int v = 0; v < num_element_value_pairs; v++) {
1101 int element_name_index = in.readUnsignedShort();
1102 String element = (String) pool[element_name_index];
1103 Object value = doElementValue(in, member, policy, collect);
1104 if (collect) {
1105 if (elements == null)
1106 elements = new LinkedHashMap<String, Object>();
1107 elements.put(element, value);
1108 }
1109 }
1110 if (collect)
1111 return new Annotation(name, elements, member, policy);
1112 else
1113 return null;
1114 }
1115
1116 private Object doElementValue(DataInputStream in, ElementType member, RetentionPolicy policy,
1117 boolean collect) throws IOException {
1118 char tag = (char) in.readUnsignedByte();
1119 switch (tag) {
1120 case 'B': // Byte
1121 case 'C': // Character
1122 case 'I': // Integer
1123 case 'S': // Short
1124 int const_value_index = in.readUnsignedShort();
1125 return intPool[const_value_index];
1126
1127 case 'D': // Double
1128 case 'F': // Float
1129 case 's': // String
1130 case 'J': // Long
1131 const_value_index = in.readUnsignedShort();
1132 return pool[const_value_index];
1133
1134 case 'Z': // Boolean
1135 const_value_index = in.readUnsignedShort();
1136 return pool[const_value_index] == null || pool[const_value_index].equals(0) ? false : true;
1137
1138 case 'e': // enum constant
1139 int type_name_index = in.readUnsignedShort();
1140 if (policy == RetentionPolicy.RUNTIME)
1141 descriptors.add(new Integer(type_name_index));
1142 int const_name_index = in.readUnsignedShort();
1143 return pool[const_name_index];
1144
1145 case 'c': // Class
1146 int class_info_index = in.readUnsignedShort();
1147 if (policy == RetentionPolicy.RUNTIME)
1148 descriptors.add(new Integer(class_info_index));
1149 return pool[class_info_index];
1150
1151 case '@': // Annotation type
1152 return doAnnotation(in, member, policy, collect);
1153
1154 case '[': // Array
1155 int num_values = in.readUnsignedShort();
1156 Object[] result = new Object[num_values];
1157 for (int i = 0; i < num_values; i++) {
1158 result[i] = doElementValue(in, member, policy, collect);
1159 }
1160 return result;
1161
1162 default:
1163 throw new IllegalArgumentException("Invalid value for Annotation ElementValue tag "
1164 + tag);
1165 }
1166 }
1167
1168 /**
1169 * Add a new package reference.
1170 *
1171 * @param pack
1172 * A '.' delimited package name
1173 */
1174 void packageReference(String pack) {
1175 imports.add(pack);
1176 }
1177
1178 /**
1179 * This method parses a descriptor and adds the package of the descriptor to
1180 * the referenced packages.
1181 *
1182 * The syntax of the descriptor is:
1183 *
1184 * <pre>
1185 * descriptor ::= ( '(' reference * ')' )? reference
1186 * reference ::= 'L' classname ( '&lt;' references '&gt;' )? ';' | 'B' | 'Z' | ... | '+' | '-' | '['
1187 * </pre>
1188 *
1189 * This methods uses heavy recursion to parse the descriptor and a roving
1190 * pointer to limit the creation of string objects.
1191 *
1192 * @param descriptor
1193 * The to be parsed descriptor
1194 * @param rover
1195 * The pointer to start at
1196 */
1197 public void parseDescriptor(String descriptor) {
1198 // Some descriptors are weird, they start with a generic
1199 // declaration that contains ':', not sure what they mean ...
1200 if (descriptor.charAt(0) == '<')
1201 return;
1202
1203 int rover = 0;
1204 if (descriptor.charAt(rover) == '(') {
1205 rover = parseReferences(descriptor, rover + 1, ')');
1206 rover++;
1207 }
1208 parseReferences(descriptor, rover, (char) 0);
1209 }
1210
1211 /**
1212 * Parse a sequence of references. A sequence ends with a given character or
1213 * when the string ends.
1214 *
1215 * @param descriptor
1216 * The whole descriptor.
1217 * @param rover
1218 * The index in the descriptor
1219 * @param delimiter
1220 * The end character or 0
1221 * @return the last index processed, one character after the delimeter
1222 */
1223 int parseReferences(String descriptor, int rover, char delimiter) {
1224 while (rover < descriptor.length() && descriptor.charAt(rover) != delimiter) {
1225 rover = parseReference(descriptor, rover);
1226 }
1227 return rover;
1228 }
1229
1230 /**
1231 * Parse a single reference. This can be a single character or an object
1232 * reference when it starts with 'L'.
1233 *
1234 * @param descriptor
1235 * The descriptor
1236 * @param rover
1237 * The place to start
1238 * @return The return index after the reference
1239 */
1240 int parseReference(String descriptor, int rover) {
1241
1242 char c = descriptor.charAt(rover);
1243 while (c == '[')
1244 c = descriptor.charAt(++rover);
1245
1246 if (c == '<') {
1247 rover = parseReferences(descriptor, rover + 1, '>');
1248 } else if (c == 'T') {
1249 // Type variable name
1250 rover++;
1251 while (descriptor.charAt(rover) != ';')
1252 rover++;
1253 } else if (c == 'L') {
1254 StringBuilder sb = new StringBuilder();
1255 rover++;
1256 int lastSlash = -1;
1257 while ((c = descriptor.charAt(rover)) != ';') {
1258 if (c == '<') {
1259 rover = parseReferences(descriptor, rover + 1, '>');
1260 } else if (c == '/') {
1261 lastSlash = sb.length();
1262 sb.append('.');
1263 } else
1264 sb.append(c);
1265 rover++;
1266 }
1267 if (cd != null)
1268 cd.addReference(sb.toString());
1269
1270 if (lastSlash > 0)
1271 packageReference(sb.substring(0, lastSlash));
1272 } else {
1273 if ("+-*BCDFIJSZV".indexOf(c) < 0)
1274 ;// System.out.println("Should not skip: " + c);
1275 }
1276
1277 // this skips a lot of characters
1278 // [, *, +, -, B, etc.
1279
1280 return rover + 1;
1281 }
1282
1283 public static String getPackage(String clazz) {
1284 int n = clazz.lastIndexOf('/');
1285 if (n < 0) {
1286 n = clazz.lastIndexOf('.');
1287 if (n < 0)
1288 return ".";
1289 }
1290 return clazz.substring(0, n).replace('/', '.');
1291 }
1292
1293 public Set<String> getReferred() {
1294 return imports;
1295 }
1296
1297 String getClassName() {
1298 if (className == null)
1299 return "NOCLASSNAME";
1300 return className;
1301 }
1302
1303 public String getPath() {
1304 return path;
1305 }
1306
1307 public String getSourceFile() {
1308 return sourceFile;
1309 }
1310
1311 /**
1312 * .class construct for different compilers
1313 *
1314 * sun 1.1 Detect static variable class$com$acme$MyClass 1.2 " 1.3 " 1.4 "
1315 * 1.5 ldc_w (class) 1.6 "
1316 *
1317 * eclipse 1.1 class$0, ldc (string), invokestatic Class.forName 1.2 " 1.3 "
1318 * 1.5 ldc (class) 1.6 "
1319 *
1320 * 1.5 and later is not an issue, sun pre 1.5 is easy to detect the static
1321 * variable that decodes the class name. For eclipse, the class$0 gives away
1322 * we have a reference encoded in a string.
1323 * compilerversions/compilerversions.jar contains test versions of all
1324 * versions/compilers.
1325 */
1326
1327 public void reset() {
1328 pool = null;
1329 intPool = null;
1330 xref = null;
1331 classes = null;
1332 descriptors = null;
1333 }
1334
1335 public boolean is(QUERY query, Instruction instr, Analyzer analyzer) throws Exception {
1336 switch (query) {
1337 case ANY:
1338 return true;
1339
1340 case NAMED:
1341 if (instr.matches(getClassName()))
1342 return !instr.isNegated();
1343 return false;
1344
1345 case VERSION:
1346 String v = major + "/" + minor;
1347 if (instr.matches(v))
1348 return !instr.isNegated();
1349 return false;
1350
1351 case IMPLEMENTS:
1352 for (int i = 0; interfaces != null && i < interfaces.length; i++) {
1353 if (instr.matches(interfaces[i]))
1354 return !instr.isNegated();
1355 }
1356 break;
1357
1358 case EXTENDS:
1359 if (zuper == null)
1360 return false;
1361
1362 if (instr.matches(zuper))
1363 return !instr.isNegated();
1364 break;
1365
1366 case PUBLIC:
1367 return !isPublic;
1368
1369 case CONCRETE:
1370 return !isAbstract;
1371
1372 case ANNOTATION:
1373 if (annotations == null)
1374 return false;
1375
1376 if (annotations.contains(instr.getPattern()))
1377 return true;
1378
1379 for (String annotation : annotations) {
1380 if (instr.matches(annotation))
1381 return !instr.isNegated();
1382 }
1383
1384 return false;
1385
1386 case RUNTIMEANNOTATIONS:
1387 return hasClassAnnotations;
1388 case CLASSANNOTATIONS:
1389 return hasClassAnnotations;
1390
1391 case ABSTRACT:
1392 return isAbstract;
1393
1394 case IMPORTS:
1395 for (String imp : imports) {
1396 if (instr.matches(imp.replace('.', '/')))
1397 return !instr.isNegated();
1398 }
1399 }
1400
1401 if (zuper == null)
1402 return false;
1403
1404 Clazz clazz = analyzer.findClass(zuper + ".class");
1405 if (clazz == null)
1406 return false;
1407
1408 return clazz.is(query, instr, analyzer);
1409 }
1410
1411 public String toString() {
1412 return getFQN();
1413 }
1414
1415 public String getFQN() {
1416 String s = getClassName().replace('/', '.');
1417 return s;
1418 }
1419
1420 /**
1421 * Return a list of packages implemented by this class.
1422 *
1423 * @param implemented
1424 * @param classspace
1425 * @param clazz
1426 * @throws Exception
1427 */
1428 @SuppressWarnings("deprecation") final static String USEPOLICY = toDescriptor(UsePolicy.class);
1429 final static String PROVIDERPOLICY = toDescriptor(ProviderType.class);
1430
1431 public static void getImplementedPackages(Set<String> implemented, Analyzer analyzer,
1432 Clazz clazz) throws Exception {
1433 if (clazz.interfaces != null) {
1434 for (String interf : clazz.interfaces) {
1435 interf = interf + ".class";
1436 Clazz c = analyzer.getClassspace().get(interf);
1437
1438 // If not found, actually parse the imported
1439 // class file to check for implementation policy.
1440 if (c == null)
1441 c = analyzer.findClass(interf);
1442
1443 if (c != null) {
1444 boolean consumer = false;
1445 Set<String> annotations = c.annotations;
1446 if (annotations != null)
1447 // Override if we marked the interface as a consumer
1448 // interface
1449 consumer = annotations.contains(USEPOLICY)
1450 || annotations.contains(PROVIDERPOLICY);
1451
1452 if (!consumer)
1453 implemented.add(getPackage(interf));
1454 getImplementedPackages(implemented, analyzer, c);
1455 } else
1456 implemented.add(getPackage(interf));
1457
1458 }
1459 }
1460 if (clazz.zuper != null) {
1461 Clazz c = analyzer.getClassspace().get(clazz.zuper);
1462 if (c != null) {
1463 getImplementedPackages(implemented, analyzer, c);
1464 }
1465 }
1466
1467 }
1468
1469 // String RNAME = "LaQute/bnd/annotation/UsePolicy;";
1470
1471 public static String toDescriptor(Class<?> clazz) {
1472 StringBuilder sb = new StringBuilder();
1473 sb.append('L');
1474 sb.append(clazz.getName().replace('.', '/'));
1475 sb.append(';');
1476 return sb.toString();
1477 }
1478
1479 MethodDef getMethodDef(int access, int methodRefPoolIndex) {
1480 Object o = pool[methodRefPoolIndex];
1481 if (o != null && o instanceof Assoc) {
1482 Assoc assoc = (Assoc) o;
1483 if (assoc.tag == 10) {
1484 int string_index = intPool[assoc.a];
1485 String className = (String) pool[string_index];
1486 int name_and_type_index = assoc.b;
1487 Assoc name_and_type = (Assoc) pool[name_and_type_index];
1488 if (name_and_type.tag == 12) {
1489 // Name and Type
1490 int name_index = name_and_type.a;
1491 int type_index = name_and_type.b;
1492 String method = (String) pool[name_index];
1493 String descriptor = (String) pool[type_index];
1494 return new MethodDef(access, className, method, descriptor);
1495 } else
1496 throw new IllegalArgumentException(
1497 "Invalid class file (or parsing is wrong), assoc is not type + name (12)");
1498 } else
1499 throw new IllegalArgumentException(
1500 "Invalid class file (or parsing is wrong), Assoc is not method ref! (10)");
1501 } else
1502 throw new IllegalArgumentException(
1503 "Invalid class file (or parsing is wrong), Not an assoc at a method ref");
1504 }
1505
1506 public static String getShortName(String cname) {
1507 int n = cname.lastIndexOf('.');
1508 if (n < 0)
1509 return cname;
1510 return cname.substring(n + 1, cname.length());
1511 }
1512
1513 public static String fqnToPath(String dotted) {
1514 return dotted.replace('.', '/') + ".class";
1515 }
1516
1517 public static String fqnToBinary(String dotted) {
1518 return "L" + dotted.replace('.', '/') + ";";
1519 }
1520
1521 public static String pathToFqn(String path) {
1522 return path.replace('/', '.').substring(0, path.length() - 6);
1523 }
1524
1525 public boolean isPublic() {
1526 return isPublic;
1527 }
1528
1529 public boolean isEnum() {
1530 return isEnum;
1531 }
1532
1533 public JAVA getFormat() {
1534 return JAVA.format(major);
1535
1536 }
1537
1538 public static String objectDescriptorToFQN(String string) {
1539 if (string.startsWith("L") && string.endsWith(";"))
1540 return string.substring(1, string.length() - 1).replace('/', '.');
1541
1542 switch (string.charAt(0)) {
1543 case 'V':
1544 return "void";
1545 case 'B':
1546 return "byte";
1547 case 'C':
1548 return "char";
1549 case 'I':
1550 return "int";
1551 case 'S':
1552 return "short";
1553 case 'D':
1554 return "double";
1555 case 'F':
1556 return "float";
1557 case 'J':
1558 return "long";
1559 case 'Z':
1560 return "boolean";
1561 case '[': // Array
1562 return objectDescriptorToFQN(string.substring(1)) + "[]";
1563 }
1564 throw new IllegalArgumentException("Invalid type character in descriptor " + string);
1565 }
1566
1567 public static String internalToFqn(String string) {
1568 return string.replace('/', '.');
1569 }
1570
1571 public static String unCamel(String id) {
1572 StringBuilder out = new StringBuilder();
1573 for (int i = 0; i < id.length(); i++) {
1574 char c = id.charAt(i);
1575 if (c == '_' || c == '$' || c == '.') {
1576 if (out.length() > 0 && !Character.isWhitespace(out.charAt(out.length() - 1)))
1577 out.append(' ');
1578 continue;
1579 }
1580
1581 int n = i;
1582 while (n < id.length() && Character.isUpperCase(id.charAt(n))) {
1583 n++;
1584 }
1585 if (n == i)
1586 out.append(id.charAt(i));
1587 else {
1588 boolean tolower = (n - i) == 1;
1589 if (i > 0 && !Character.isWhitespace(out.charAt(out.length() - 1)))
1590 out.append(' ');
1591
1592 for (; i < n;) {
1593 if (tolower)
1594 out.append(Character.toLowerCase(id.charAt(i)));
1595 else
1596 out.append(id.charAt(i));
1597 i++;
1598 }
1599 i--;
1600 }
1601 }
1602 if (id.startsWith("."))
1603 out.append(" *");
1604 out.replace(0, 1, Character.toUpperCase(out.charAt(0)) + "");
1605 return out.toString();
1606 }
1607
1608}