Skip to content

Commit e9b395c

Browse files
committed
Teach the ByteCodeAnalyzer to extract annotations from .class files
The result is a map suitable for use by the annotation indexer. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
1 parent 7dc3286 commit e9b395c

File tree

1 file changed

+151
-1
lines changed

1 file changed

+151
-1
lines changed

src/main/java/org/scijava/annotations/ByteCodeAnalyzer.java

Lines changed: 151 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@
3737
import java.io.IOException;
3838
import java.io.InputStream;
3939
import java.util.Iterator;
40-
import java.util.LinkedHashMap;
4140
import java.util.Map;
41+
import java.util.TreeMap;
4242

4343
/**
4444
* An analyzer to parse {@code &#x40;Plugin} annotations inside a {@code .class}
@@ -440,10 +440,160 @@ public String toString() {
440440
}
441441
}
442442

443+
private Map<String, Map<String, Object>> getAnnotations() {
444+
final Map<String, Map<String, Object>> annotations =
445+
new TreeMap<String, Map<String, Object>>();
446+
for (final Attribute attr : attributes) {
447+
if ("RuntimeVisibleAnnotations".equals(attr.getName())) {
448+
final byte[] buffer = attr.attribute;
449+
int count = getU2(buffer, 0);
450+
int offset = 2;
451+
for (int i = 0; i < count; i++) {
452+
final String className =
453+
raw2className(getStringConstant(getU2(buffer, offset)));
454+
offset += 2;
455+
final Map<String, Object> values =
456+
new TreeMap<String, Object>();
457+
annotations.put(className, values);
458+
offset = parseAnnotationValues(buffer, offset, values);
459+
}
460+
}
461+
}
462+
return annotations;
463+
}
464+
465+
private int parseAnnotationValues(final byte[] buffer, int offset,
466+
final Map<String, Object> values)
467+
{
468+
int count = getU2(buffer, offset);
469+
offset += 2;
470+
for (int i = 0; i < count; i++) {
471+
final String key = getStringConstant(getU2(buffer, offset));
472+
offset += 2;
473+
offset = parseAnnotationValue(buffer, offset, values, key);
474+
}
475+
return offset;
476+
}
477+
478+
private int parseAnnotationValue(byte[] buffer, int offset,
479+
Map<String, Object> map, String key)
480+
{
481+
Object value;
482+
switch (getU1(buffer, offset++)) {
483+
case 'Z':
484+
value = Boolean.valueOf(getIntegerConstant(getU2(buffer, offset)) != 0);
485+
offset += 2;
486+
break;
487+
case 'B':
488+
value = Byte.valueOf((byte) getIntegerConstant(getU2(buffer, offset)));
489+
offset += 2;
490+
break;
491+
case 'C':
492+
value =
493+
Character.valueOf((char) getIntegerConstant(getU2(buffer, offset)));
494+
offset += 2;
495+
break;
496+
case 'S':
497+
value =
498+
Short.valueOf((short) getIntegerConstant(getU2(buffer, offset)));
499+
offset += 2;
500+
break;
501+
case 'I':
502+
value =
503+
Integer.valueOf((int) getIntegerConstant(getU2(buffer, offset)));
504+
offset += 2;
505+
break;
506+
case 'J':
507+
value = Long.valueOf(getLongConstant(getU2(buffer, offset)));
508+
offset += 2;
509+
break;
510+
case 'F':
511+
value = Float.valueOf(getFloatConstant(getU2(buffer, offset)));
512+
offset += 2;
513+
break;
514+
case 'D':
515+
value = Double.valueOf(getDoubleConstant(getU2(buffer, offset)));
516+
offset += 2;
517+
break;
518+
case 's':
519+
value = getStringConstant(getU2(buffer, offset));
520+
offset += 2;
521+
break;
522+
case 'c':
523+
value = raw2className(getStringConstant(getU2(buffer, offset)));
524+
offset += 2;
525+
break;
526+
case '[': {
527+
final Object[] array = new Object[getU2(buffer, offset)];
528+
offset += 2;
529+
for (int i = 0; i < array.length; i++) {
530+
offset = parseAnnotationValue(buffer, offset, map, key);
531+
array[i] = map.get(key);
532+
}
533+
value = array;
534+
break;
535+
}
536+
case 'e': {
537+
final Map<String, Object> enumValue =
538+
new TreeMap<String, Object>();
539+
enumValue.put("enum", raw2className(getStringConstant(getU2(buffer,
540+
offset))));
541+
offset += 2;
542+
enumValue.put("value", getStringConstant(getU2(buffer, offset)));
543+
offset += 2;
544+
value = enumValue;
545+
break;
546+
}
547+
case '@': {
548+
// skipping annotation type
549+
offset += 2;
550+
final Map<String, Object> values = new TreeMap<String, Object>();
551+
offset = parseAnnotationValues(buffer, offset, values);
552+
value = values;
553+
break;
554+
}
555+
default:
556+
throw new RuntimeException("Unhandled annotation value type: " +
557+
(char) getU1(buffer, offset - 1));
558+
}
559+
if (value == null) {
560+
throw new NullPointerException();
561+
}
562+
map.put(key, value);
563+
return offset;
564+
}
565+
566+
private static String raw2className(final String rawName) {
567+
if (!rawName.startsWith("L") || !rawName.endsWith(";")) {
568+
throw new RuntimeException("Invalid raw class name: " + rawName);
569+
}
570+
return rawName.substring(1, rawName.length() - 1).replace('/', '.');
571+
}
572+
443573
private String toString(final Attribute[] attributes) {
444574
String result = "";
445575
for (final Attribute attribute : attributes)
446576
result += (result.equals("") ? "(" : ";") + attribute;
447577
return result.equals("") ? "" : result + ")";
448578
}
579+
580+
private static byte[] readFile(final File file) throws IOException {
581+
final InputStream in = new FileInputStream(file);
582+
final ByteArrayOutputStream out = new ByteArrayOutputStream();
583+
final byte[] buffer = new byte[16384];
584+
for (;;) {
585+
int count = in.read(buffer);
586+
if (count < 0) break;
587+
out.write(buffer, 0, count);
588+
}
589+
in.close();
590+
out.close();
591+
return out.toByteArray();
592+
}
593+
594+
static Map<String, Map<String, Object>> getAnnotations(File file)
595+
throws IOException
596+
{
597+
return new ByteCodeAnalyzer(readFile(file)).getAnnotations();
598+
}
449599
}

0 commit comments

Comments
 (0)