/*
* Copyright 2010 Lincoln Baxter, III
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.ocpsoft.rewrite.annotation.scan;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.util.LinkedHashSet;
import java.util.Set;
import org.ocpsoft.logging.Logger;
/**
* <p>
* This class reads Java class files and checks whether they contain references to specific annotations. This allows to
* check classes for the presence of annotations before instantiating them.
* </p>
*
* <p>
* The filter is inspired by the <code>ClassByteCodeAnnotationFilter</code> of Apache MyFaces 2.0. Many thanks go out to
* Leonardo Uribe for his great work on this class.
* </p>
*
* @see http://docs.oracle.com/javase/specs/jvms/se5.0/html/ClassFile.doc.html
* @see http://en.wikipedia.org/wiki/Class_%28file_format%29
*
* @author Christian Kaltepoth
*/
public class ByteCodeFilter
{
private final static Logger log = Logger.getLogger(ByteCodeFilter.class);
/**
* The magic to identify class files.
*/
private final static int CLASS_FILE_MAGIC = 0xCAFEBABE;
/*
* Tag values for the Constant Pool:
* http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html#20080
*/
private final static int CONSTANT_Class = 7;
private final static int CONSTANT_Fieldref = 9;
private final static int CONSTANT_Methodref = 10;
private final static int CONSTANT_InterfaceMethodref = 11;
private final static int CONSTANT_String = 8;
private final static int CONSTANT_Integer = 3;
private final static int CONSTANT_Float = 4;
private final static int CONSTANT_Long = 5;
private final static int CONSTANT_Double = 6;
private final static int CONSTANT_NameAndType = 12;
private final static int CONSTANT_Utf8 = 1;
private static final int CONSTANT_MethodHandle = 15;
private static final int CONSTANT_MethodType = 16;
private static final int CONSTANT_InvokeDynamic = 18;
/**
* The strings to look for in the constants table
*/
private final Set<String> fieldDescriptors = new LinkedHashSet<String>();
/**
* The filter must be initialized with a list of types to look for when scanning the class files. If a class files
* contains a reference to one of these types, the filter will accept the class.
*
* @param types A Set of types to look for
*/
public ByteCodeFilter(Set<Class<? extends Annotation>> types)
{
for (Class<? extends Annotation> type : types) {
fieldDescriptors.add("L" + type.getName().replace('.', '/'));
}
}
/**
* <p>
* Checks whether that supplied {@link InputStream} contains a Java class file that might contain references to
* specific annotation types.
* </p>
* <p>
* The caller of this method is responsible to close the supplied {@link InputStream}. This method won't do it!
* </p>
*
* @param classFileStream The stream to read the class file from.
* @return <code>true</code> for files that contain at least one reference to one of the "interesting" annotations
* @throws IOException for any kind of IO problem
*/
@SuppressWarnings("unused")
public boolean accept(InputStream classFileStream) throws IOException
{
// open a DataInputStream
DataInputStream in = new DataInputStream(classFileStream);
// read magic and abort if it doesn't match
int magic = in.readInt();
if (magic != CLASS_FILE_MAGIC)
{
if (log.isDebugEnabled())
{
log.debug("Magic not found! Not a valid class file!");
}
return false;
}
// check for at least JDK 1.5
int minor = in.readUnsignedShort();
int major = in.readUnsignedShort();
if (major < 49)
{
// JDK 1.4 or less
if (log.isTraceEnabled())
{
log.trace("Not a JDK5 class! It cannot contain annotations!");
}
return false;
}
// this values is equal to the number entries in the constants pool + 1
int constantPoolEntries = in.readUnsignedShort() - 1;
// loop over all entries in the constants pool
for (int i = 0; i < constantPoolEntries; i++)
{
// the tag to identify the record type
int tag = in.readUnsignedByte();
// process record according to its type
switch (tag)
{
case CONSTANT_Class:
/*
* CONSTANT_Class_info {
* u1 tag;
* u2 name_index;
* }
*/
in.readUnsignedShort();
break;
case CONSTANT_Fieldref:
case CONSTANT_Methodref:
case CONSTANT_InterfaceMethodref:
/*
* CONSTANT_[Fieldref|Methodref|InterfaceMethodref]_info {
* u1 tag;
* u2 class_index;
* u2 name_and_type_index;
* }
*/
in.readUnsignedShort();
in.readUnsignedShort();
break;
case CONSTANT_String:
/*
* CONSTANT_String_info {
* u1 tag;
* u2 string_index;
* }
*/
in.readUnsignedShort();
break;
case CONSTANT_Integer:
case CONSTANT_Float:
/*
* CONSTANT_[Integer|Float]_info {
* u1 tag;
* u4 bytes;
* }
*/
in.readInt();
break;
case CONSTANT_Long:
case CONSTANT_Double:
/*
* CONSTANT_Long_info {
* u1 tag;
* u4 high_bytes;
* u4 low_bytes;
* }
*/
in.readLong();
/*
* We must increase the constant pool index because this tag
* type takes two entries
*/
i++;
break;
case CONSTANT_NameAndType:
/*
* CONSTANT_NameAndType_info {
* u1 tag;
* u2 name_index;
* u2 descriptor_index;
* }
*/
in.readUnsignedShort();
in.readUnsignedShort();
break;
case CONSTANT_Utf8:
/*
* CONSTANT_Utf8_info {
* u1 tag;
* u2 length;
* u1 bytes[length];
* }
*/
String str = in.readUTF();
// check if this string sounds interesting
if (containsFieldDescriptor(str))
{
if (log.isTraceEnabled())
{
log.trace("Found interesting annotation reference in constant pool: " + str);
}
return true;
}
break;
case CONSTANT_MethodHandle:
/*
* CONSTANT_MethodHandle_info {
* u1 tag;
* u1 reference_kind;
* u2 reference_index;
* }
*/
in.readByte();
in.readShort();
break;
case CONSTANT_MethodType:
/*
* CONSTANT_MethodType_info {
* u1 tag;
* u2 descriptor_index;
* }
*/
in.readShort();
break;
case CONSTANT_InvokeDynamic:
/*
* CONSTANT_InvokeDynamic_info {
* u1 tag;
* u2 bootstrap_method_attr_index;
* u2 name_and_type_index;
* }
*/
in.readShort();
in.readShort();
break;
default:
/*
* Unknown tag! Should not happen! We will scan the class in this case.
*/
if (log.isDebugEnabled())
{
log.debug("Unknown constant pool tag found: " + tag);
}
return true;
}
}
return false;
}
/**
* Returns true if the given string contains a field descriptor of one of the annotations we are looking for.
*/
private boolean containsFieldDescriptor(String str)
{
for (String descriptor : fieldDescriptors) {
if (str.contains(descriptor)) {
return true;
}
}
return false;
}
@Override
public String toString()
{
return "ByteCodeFilter [fieldDescriptors=" + fieldDescriptors + "]";
}
}