Skip to content

Commit 4543593

Browse files
committed
Merge pull request #62 from scijava/read-annotations-directly
Read annotations directly from the .class files
2 parents f7e8606 + 7977f2e commit 4543593

File tree

5 files changed

+403
-20
lines changed

5 files changed

+403
-20
lines changed

src/it/apt-test/src/main/java/org/scijava/annotation/its/CustomAnnotation.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
public @interface CustomAnnotation {
4949

5050
String greeting() default "Hello, World!";
51+
int number() default 123;
5152

5253
}
5354

Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
/*
2+
* #%L
3+
* ImageJ software for multidimensional image processing and analysis.
4+
* %%
5+
* Copyright (C) 2009 - 2014 Board of Regents of the University of
6+
* Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
7+
* Institute of Molecular Cell Biology and Genetics.
8+
* %%
9+
* Redistribution and use in source and binary forms, with or without
10+
* modification, are permitted provided that the following conditions are met:
11+
*
12+
* 1. Redistributions of source code must retain the above copyright notice,
13+
* this list of conditions and the following disclaimer.
14+
* 2. Redistributions in binary form must reproduce the above copyright notice,
15+
* this list of conditions and the following disclaimer in the documentation
16+
* and/or other materials provided with the distribution.
17+
*
18+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
22+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28+
* POSSIBILITY OF SUCH DAMAGE.
29+
* #L%
30+
*/
31+
32+
package org.scijava.annotations;
33+
34+
import java.io.ByteArrayOutputStream;
35+
import java.io.File;
36+
import java.io.FileInputStream;
37+
import java.io.IOException;
38+
import java.io.InputStream;
39+
import java.util.Map;
40+
import java.util.TreeMap;
41+
42+
/**
43+
* An analyzer to parse {@code @Plugin} annotations inside a {@code .class}
44+
* file without loading the class. The idea is to inspect the classfile to parse
45+
* the annotation attributes.
46+
*
47+
* @author Johannes Schindelin
48+
*/
49+
class ByteCodeAnalyzer {
50+
51+
private byte[] buffer;
52+
private int[] poolOffsets;
53+
private int endOffset;
54+
private Attribute[] attributes;
55+
56+
private ByteCodeAnalyzer(final byte[] buffer) {
57+
this.buffer = buffer;
58+
if ((int) getU4(0) != 0xcafebabe) throw new RuntimeException("No class");
59+
getConstantPoolOffsets();
60+
// skip interfaces
61+
endOffset += 8 + 2 * getU2(endOffset + 6);
62+
// skip fields
63+
int fieldCount = getU2(endOffset);
64+
endOffset += 2;
65+
for (int i = 0; i < fieldCount; i++) {
66+
endOffset = skipAttributes(endOffset + 6);
67+
}
68+
// skip methods
69+
int methodCount = getU2(endOffset);
70+
endOffset += 2;
71+
for (int i = 0; i < methodCount; i++) {
72+
endOffset = skipAttributes(endOffset + 6);
73+
}
74+
getAllAttributes();
75+
}
76+
77+
private String getStringConstant(final int index) {
78+
return getString(poolOffsets[index - 1]);
79+
}
80+
81+
private long getIntegerConstant(final int index) {
82+
final int offset = poolOffsets[index - 1];
83+
if (getU1(offset) != 3) throw new RuntimeException("Constant " + index +
84+
" does not refer to an integer");
85+
return getU4(offset + 1);
86+
}
87+
88+
private long getLongConstant(final int index) {
89+
final int offset = poolOffsets[index - 1];
90+
if (getU1(offset) != 5) throw new RuntimeException("Constant " + index +
91+
" does not refer to a long");
92+
return (getU4(offset + 1) << 32) | getU4(offset + 5);
93+
}
94+
95+
private float getFloatConstant(final int index) {
96+
final int offset = poolOffsets[index - 1];
97+
if (getU1(offset) != 4) throw new RuntimeException("Constant " + index +
98+
" does not refer to a float");
99+
return Float.intBitsToFloat((int) getU4(offset + 1));
100+
}
101+
102+
private double getDoubleConstant(final int index) {
103+
final int offset = poolOffsets[index - 1];
104+
if (getU1(offset) != 6) throw new RuntimeException("Constant " + index +
105+
" does not refer to a double");
106+
return Double.longBitsToDouble((getU4(offset + 1) << 32) |
107+
getU4(offset + 5));
108+
}
109+
110+
private void getConstantPoolOffsets() {
111+
final int poolCount = getU2(8) - 1;
112+
poolOffsets = new int[poolCount];
113+
int offset = 10;
114+
for (int i = 0; i < poolCount; i++) {
115+
poolOffsets[i] = offset;
116+
final int tag = getU1(offset);
117+
if (tag == 7 || tag == 8) offset += 3;
118+
else if (tag == 9 || tag == 10 || tag == 11 || tag == 3 || tag == 4 ||
119+
tag == 12) offset += 5;
120+
else if (tag == 5 || tag == 6) {
121+
poolOffsets[++i] = offset;
122+
offset += 9;
123+
}
124+
else if (tag == 1) offset += 3 + getU2(offset + 1);
125+
else throw new RuntimeException("Unknown tag" + " " + tag);
126+
}
127+
endOffset = offset;
128+
}
129+
130+
private int getU1(final int offset) {
131+
return getU1(buffer, offset);
132+
}
133+
134+
private int getU2(final int offset) {
135+
return getU2(buffer, offset);
136+
}
137+
138+
private long getU4(final int offset) {
139+
return getU4(buffer, offset);
140+
}
141+
142+
private static int getU1(final byte[] buffer, final int offset) {
143+
return buffer[offset] & 0xff;
144+
}
145+
146+
private static int getU2(final byte[] buffer, final int offset) {
147+
return getU1(buffer, offset) << 8 | getU1(buffer, offset + 1);
148+
}
149+
150+
private static long getU4(final byte[] buffer, final int offset) {
151+
return ((long) getU2(buffer, offset)) << 16 | getU2(buffer, offset + 2);
152+
}
153+
154+
private String getString(final int offset) {
155+
try {
156+
return new String(buffer, offset + 3, getU2(offset + 1), "UTF-8");
157+
}
158+
catch (final Exception e) {
159+
return "";
160+
}
161+
}
162+
163+
private int skipAttributes(int offset) {
164+
int count = getU2(offset);
165+
offset += 2;
166+
for (int i = 0; i < count; i++) {
167+
offset += 6 + getU4(offset + 2);
168+
}
169+
return offset;
170+
}
171+
172+
private void getAllAttributes() {
173+
attributes = getAttributes(endOffset);
174+
}
175+
176+
private Attribute[] getAttributes(final int offset) {
177+
final Attribute[] result = new Attribute[getU2(offset)];
178+
for (int i = 0; i < result.length; i++)
179+
result[i] =
180+
new Attribute(i == 0 ? offset + 2 : result[i - 1].attributeEndOffset);
181+
return result;
182+
}
183+
184+
private class Attribute {
185+
186+
int nameIndex;
187+
byte[] attribute;
188+
int attributeEndOffset;
189+
190+
private Attribute(final int offset) {
191+
nameIndex = getU2(offset);
192+
attribute = new byte[(int) getU4(offset + 2)];
193+
System.arraycopy(buffer, offset + 6, attribute, 0, attribute.length);
194+
attributeEndOffset = offset + 6 + attribute.length;
195+
}
196+
197+
private String getName() {
198+
return getStringConstant(nameIndex);
199+
}
200+
}
201+
202+
private Map<String, Map<String, Object>> getAnnotations() {
203+
final Map<String, Map<String, Object>> annotations =
204+
new TreeMap<String, Map<String, Object>>();
205+
for (final Attribute attr : attributes) {
206+
if ("RuntimeVisibleAnnotations".equals(attr.getName())) {
207+
final byte[] buffer = attr.attribute;
208+
int count = getU2(buffer, 0);
209+
int offset = 2;
210+
for (int i = 0; i < count; i++) {
211+
final String className =
212+
raw2className(getStringConstant(getU2(buffer, offset)));
213+
offset += 2;
214+
final Map<String, Object> values =
215+
new TreeMap<String, Object>();
216+
annotations.put(className, values);
217+
offset = parseAnnotationValues(buffer, offset, values);
218+
}
219+
}
220+
}
221+
return annotations;
222+
}
223+
224+
private int parseAnnotationValues(final byte[] buffer, int offset,
225+
final Map<String, Object> values)
226+
{
227+
int count = getU2(buffer, offset);
228+
offset += 2;
229+
for (int i = 0; i < count; i++) {
230+
final String key = getStringConstant(getU2(buffer, offset));
231+
offset += 2;
232+
offset = parseAnnotationValue(buffer, offset, values, key);
233+
}
234+
return offset;
235+
}
236+
237+
private int parseAnnotationValue(byte[] buffer, int offset,
238+
Map<String, Object> map, String key)
239+
{
240+
Object value;
241+
switch (getU1(buffer, offset++)) {
242+
case 'Z':
243+
value = Boolean.valueOf(getIntegerConstant(getU2(buffer, offset)) != 0);
244+
offset += 2;
245+
break;
246+
case 'B':
247+
value = Byte.valueOf((byte) getIntegerConstant(getU2(buffer, offset)));
248+
offset += 2;
249+
break;
250+
case 'C':
251+
value =
252+
Character.valueOf((char) getIntegerConstant(getU2(buffer, offset)));
253+
offset += 2;
254+
break;
255+
case 'S':
256+
value =
257+
Short.valueOf((short) getIntegerConstant(getU2(buffer, offset)));
258+
offset += 2;
259+
break;
260+
case 'I':
261+
value =
262+
Integer.valueOf((int) getIntegerConstant(getU2(buffer, offset)));
263+
offset += 2;
264+
break;
265+
case 'J':
266+
value = Long.valueOf(getLongConstant(getU2(buffer, offset)));
267+
offset += 2;
268+
break;
269+
case 'F':
270+
value = Float.valueOf(getFloatConstant(getU2(buffer, offset)));
271+
offset += 2;
272+
break;
273+
case 'D':
274+
value = Double.valueOf(getDoubleConstant(getU2(buffer, offset)));
275+
offset += 2;
276+
break;
277+
case 's':
278+
value = getStringConstant(getU2(buffer, offset));
279+
offset += 2;
280+
break;
281+
case 'c':
282+
value = raw2className(getStringConstant(getU2(buffer, offset)));
283+
offset += 2;
284+
break;
285+
case '[': {
286+
final Object[] array = new Object[getU2(buffer, offset)];
287+
offset += 2;
288+
for (int i = 0; i < array.length; i++) {
289+
offset = parseAnnotationValue(buffer, offset, map, key);
290+
array[i] = map.get(key);
291+
}
292+
value = array;
293+
break;
294+
}
295+
case 'e': {
296+
final Map<String, Object> enumValue =
297+
new TreeMap<String, Object>();
298+
enumValue.put("enum", raw2className(getStringConstant(getU2(buffer,
299+
offset))));
300+
offset += 2;
301+
enumValue.put("value", getStringConstant(getU2(buffer, offset)));
302+
offset += 2;
303+
value = enumValue;
304+
break;
305+
}
306+
case '@': {
307+
// skipping annotation type
308+
offset += 2;
309+
final Map<String, Object> values = new TreeMap<String, Object>();
310+
offset = parseAnnotationValues(buffer, offset, values);
311+
value = values;
312+
break;
313+
}
314+
default:
315+
throw new RuntimeException("Unhandled annotation value type: " +
316+
(char) getU1(buffer, offset - 1));
317+
}
318+
if (value == null) {
319+
throw new NullPointerException();
320+
}
321+
map.put(key, value);
322+
return offset;
323+
}
324+
325+
private static String raw2className(final String rawName) {
326+
if (!rawName.startsWith("L") || !rawName.endsWith(";")) {
327+
throw new RuntimeException("Invalid raw class name: " + rawName);
328+
}
329+
return rawName.substring(1, rawName.length() - 1).replace('/', '.');
330+
}
331+
332+
private static byte[] readFile(final File file) throws IOException {
333+
final InputStream in = new FileInputStream(file);
334+
final ByteArrayOutputStream out = new ByteArrayOutputStream();
335+
final byte[] buffer = new byte[16384];
336+
for (;;) {
337+
int count = in.read(buffer);
338+
if (count < 0) break;
339+
out.write(buffer, 0, count);
340+
}
341+
in.close();
342+
out.close();
343+
return out.toByteArray();
344+
}
345+
346+
static Map<String, Map<String, Object>> getAnnotations(File file)
347+
throws IOException
348+
{
349+
return new ByteCodeAnalyzer(readFile(file)).getAnnotations();
350+
}
351+
}

0 commit comments

Comments
 (0)