All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scijava.annotations.ByteCodeAnalyzer Maven / Gradle / Ivy

Go to download

SciJava Common is a shared library for SciJava software. It provides a plugin framework, with an extensible mechanism for service discovery, backed by its own annotation processor, so that plugins can be loaded dynamically. It is used by downstream projects in the SciJava ecosystem, such as ImageJ and SCIFIO.

There is a newer version: 2.99.0
Show newest version
/*
 * #%L
 * SciJava Common shared library for SciJava software.
 * %%
 * Copyright (C) 2009 - 2017 Board of Regents of the University of
 * Wisconsin-Madison, Broad Institute of MIT and Harvard, Max Planck
 * Institute of Molecular Cell Biology and Genetics, University of
 * Konstanz, and KNIME GmbH.
 * %%
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * #L%
 */

package org.scijava.annotations;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.TreeMap;

/**
 * An analyzer to parse {@code @Plugin} annotations inside a {@code .class}
 * file without loading the class. The idea is to inspect the classfile to parse
 * the annotation attributes.
 * 
 * @author Johannes Schindelin
 */
class ByteCodeAnalyzer {

	private byte[] buffer;
	private int[] poolOffsets;
	private int endOffset;
	private Attribute[] attributes;

	private ByteCodeAnalyzer(final byte[] buffer) {
		this.buffer = buffer;
		if ((int) getU4(0) != 0xcafebabe) throw new RuntimeException("No class");
		getConstantPoolOffsets();
		// skip interfaces
		endOffset += 8 + 2 * getU2(endOffset + 6);
		// skip fields
		int fieldCount = getU2(endOffset);
		endOffset += 2;
		for (int i = 0; i < fieldCount; i++) {
			endOffset = skipAttributes(endOffset + 6);
		}
		// skip methods
		int methodCount = getU2(endOffset);
		endOffset += 2;
		for (int i = 0; i < methodCount; i++) {
			endOffset = skipAttributes(endOffset + 6);
		}
		getAllAttributes();
	}

	private String getStringConstant(final int index) {
		return getString(poolOffsets[index - 1]);
	}

	private long getIntegerConstant(final int index) {
		final int offset = poolOffsets[index - 1];
		if (getU1(offset) != 3) throw new RuntimeException("Constant " + index +
			" does not refer to an integer");
		return getU4(offset + 1);
	}

	private long getLongConstant(final int index) {
		final int offset = poolOffsets[index - 1];
		if (getU1(offset) != 5) throw new RuntimeException("Constant " + index +
			" does not refer to a long");
		return (getU4(offset + 1) << 32) | getU4(offset + 5);
	}

	private float getFloatConstant(final int index) {
		final int offset = poolOffsets[index - 1];
		if (getU1(offset) != 4) throw new RuntimeException("Constant " + index +
			" does not refer to a float");
		return Float.intBitsToFloat((int) getU4(offset + 1));
	}

	private double getDoubleConstant(final int index) {
		final int offset = poolOffsets[index - 1];
		if (getU1(offset) != 6) throw new RuntimeException("Constant " + index +
			" does not refer to a double");
		return Double.longBitsToDouble((getU4(offset + 1) << 32) |
			getU4(offset + 5));
	}

	// See https://en.wikipedia.org/wiki/Java_class_file#The_constant_pool for the 
	// meaning of the offsets behind these numbers 
	private void getConstantPoolOffsets() {
		final int poolCount = getU2(8) - 1;
		poolOffsets = new int[poolCount];
		int offset = 10;
		for (int i = 0; i < poolCount; i++) {
			poolOffsets[i] = offset;
			final int tag = getU1(offset);
			if (tag == 7 || tag == 8 || tag == 16) offset += 3;
			else if (tag == 15) offset += 4;
			else if (tag == 3 || tag == 4 || tag == 9 || tag == 10 
					|| tag == 11 || tag == 12 || tag == 18) offset += 5;
			else if (tag == 5 || tag == 6) {
				poolOffsets[++i] = offset;
				offset += 9;
			}
			else if (tag == 1) offset += 3 + getU2(offset + 1);
			else throw new RuntimeException("Unknown tag" + " " + tag);
		}
		endOffset = offset;
	}

	private int getU1(final int offset) {
		return getU1(buffer, offset);
	}

	private int getU2(final int offset) {
		return getU2(buffer, offset);
	}

	private long getU4(final int offset) {
		return getU4(buffer, offset);
	}

	private static int getU1(final byte[] buffer, final int offset) {
		return buffer[offset] & 0xff;
	}

	private static int getU2(final byte[] buffer, final int offset) {
		return getU1(buffer, offset) << 8 | getU1(buffer, offset + 1);
	}

	private static long getU4(final byte[] buffer, final int offset) {
		return ((long) getU2(buffer, offset)) << 16 | getU2(buffer, offset + 2);
	}

	private String getString(final int offset) {
		try {
			return new String(buffer, offset + 3, getU2(offset + 1), "UTF-8");
		}
		catch (final Exception e) {
			return "";
		}
	}

	private int skipAttributes(int offset) {
		int count = getU2(offset);
		offset += 2;
		for (int i = 0; i < count; i++) {
			offset += 6 + getU4(offset + 2);
		}
		return offset;
	}

	private void getAllAttributes() {
		attributes = getAttributes(endOffset);
	}

	private Attribute[] getAttributes(final int offset) {
		final Attribute[] result = new Attribute[getU2(offset)];
		for (int i = 0; i < result.length; i++)
			result[i] =
				new Attribute(i == 0 ? offset + 2 : result[i - 1].attributeEndOffset);
		return result;
	}

	private class Attribute {

		int nameIndex;
		byte[] attribute;
		int attributeEndOffset;

		private Attribute(final int offset) {
			nameIndex = getU2(offset);
			attribute = new byte[(int) getU4(offset + 2)];
			System.arraycopy(buffer, offset + 6, attribute, 0, attribute.length);
			attributeEndOffset = offset + 6 + attribute.length;
		}

		private String getName() {
			return getStringConstant(nameIndex);
		}
	}

	private Map> getAnnotations() {
		final Map> annotations =
			new TreeMap<>();
		for (final Attribute attr : attributes) {
			if ("RuntimeVisibleAnnotations".equals(attr.getName())) {
				final byte[] buf = attr.attribute;
				int count = getU2(buf, 0);
				int offset = 2;
				for (int i = 0; i < count; i++) {
					final String className =
						raw2className(getStringConstant(getU2(buf, offset)));
					offset += 2;
					final Map values =
						new TreeMap<>();
					annotations.put(className, values);
					offset = parseAnnotationValues(buf, offset, values);
				}
			}
		}
		return annotations;
	}

	private int parseAnnotationValues(final byte[] buf, int offset,
		final Map values)
	{
		int count = getU2(buf, offset);
		offset += 2;
		for (int i = 0; i < count; i++) {
			final String key = getStringConstant(getU2(buf, offset));
			offset += 2;
			offset = parseAnnotationValue(buf, offset, values, key);
		}
		return offset;
	}

	private int parseAnnotationValue(byte[] buf, int offset,
		Map map, String key)
	{
		Object value;
		switch (getU1(buf, offset++)) {
			case 'Z':
				value = Boolean.valueOf(getIntegerConstant(getU2(buf, offset)) != 0);
				offset += 2;
				break;
			case 'B':
				value = Byte.valueOf((byte) getIntegerConstant(getU2(buf, offset)));
				offset += 2;
				break;
			case 'C':
				value =
					Character.valueOf((char) getIntegerConstant(getU2(buf, offset)));
				offset += 2;
				break;
			case 'S':
				value =
					Short.valueOf((short) getIntegerConstant(getU2(buf, offset)));
				offset += 2;
				break;
			case 'I':
				value =
					Integer.valueOf((int) getIntegerConstant(getU2(buf, offset)));
				offset += 2;
				break;
			case 'J':
				value = Long.valueOf(getLongConstant(getU2(buf, offset)));
				offset += 2;
				break;
			case 'F':
				value = Float.valueOf(getFloatConstant(getU2(buf, offset)));
				offset += 2;
				break;
			case 'D':
				value = Double.valueOf(getDoubleConstant(getU2(buf, offset)));
				offset += 2;
				break;
			case 's':
				value = getStringConstant(getU2(buf, offset));
				offset += 2;
				break;
			case 'c':
				value = raw2className(getStringConstant(getU2(buf, offset)));
				offset += 2;
				break;
			case '[': {
				final Object[] array = new Object[getU2(buf, offset)];
				offset += 2;
				for (int i = 0; i < array.length; i++) {
					offset = parseAnnotationValue(buf, offset, map, key);
					array[i] = map.get(key);
				}
				value = array;
				break;
			}
			case 'e': {
				final Map enumValue =
					new TreeMap<>();
				enumValue.put("enum", raw2className(getStringConstant(getU2(buf,
					offset))));
				offset += 2;
				enumValue.put("value", getStringConstant(getU2(buf, offset)));
				offset += 2;
				value = enumValue;
				break;
			}
			case '@': {
				// skipping annotation type
				offset += 2;
				final Map values = new TreeMap<>();
				offset = parseAnnotationValues(buf, offset, values);
				value = values;
				break;
			}
			default:
				throw new RuntimeException("Unhandled annotation value type: " +
					(char) getU1(buf, offset - 1));
		}
		if (value == null) {
			throw new NullPointerException();
		}
		map.put(key, value);
		return offset;
	}

	private static String raw2className(final String rawName) {
		if (!rawName.startsWith("L") || !rawName.endsWith(";")) {
			throw new RuntimeException("Invalid raw class name: " + rawName);
		}
		return rawName.substring(1, rawName.length() - 1).replace('/', '.');
	}

	private static byte[] readFile(final File file) throws IOException {
		final InputStream in = new FileInputStream(file);
		final ByteArrayOutputStream out = new ByteArrayOutputStream();
		final byte[] buffer = new byte[16384];
		for (;;) {
			int count = in.read(buffer);
			if (count < 0) break;
			out.write(buffer, 0, count);
		}
		in.close();
		out.close();
		return out.toByteArray();
	}

	static Map> getAnnotations(File file)
		throws IOException
	{
		return new ByteCodeAnalyzer(readFile(file)).getAnnotations();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy