blob: 4dbe4ec90b30f6d3107c680ad4ab82fda57872ec [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.openjpa.lib.meta;
import java.io.IOException;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.util.Localizer;
import serp.bytecode.lowlevel.ConstantPoolTable;
/**
* Filter that looks for classes with one of a set of annotations.
* See JDK 1.5 JVM spec for details on annotation bytecode:<br />
* java.sun.com/docs/books/vmspec/2nd-edition/ClassFileFormat-final-draft.pdf
*
* @author Abe White
*/
public class ClassAnnotationMetaDataFilter implements MetaDataFilter {
private final String[] _annos;
private static final Localizer _loc = Localizer.forPackage
(ClassAnnotationMetaDataFilter.class);
private Log _log = null;
/**
* Constructor; supply annotation to match against.
*/
public ClassAnnotationMetaDataFilter(Class<?> anno) {
this(new Class[]{ anno });
}
/**
* Constructor; supply annotations to match against.
*/
public ClassAnnotationMetaDataFilter(Class<?>[] annos) {
_annos = new String[annos.length];
for (int i = 0; i < annos.length; i++)
_annos[i] = "L" + annos[i].getName().replace('.', '/') + ";";
}
@Override
public boolean matches(Resource rsrc) throws IOException {
if (_annos.length == 0 || !rsrc.getName().endsWith(".class"))
return false;
try {
ConstantPoolTable table = new ConstantPoolTable(rsrc.getContent());
int idx = table.getEndIndex();
idx += 6; // skip access, cls, super
// skip interfaces
int interfaces = table.readUnsignedShort(idx);
idx += 2 + interfaces * 2;
// skip fields and methods
int fields = table.readUnsignedShort(idx);
idx += 2;
for (int i = 0; i < fields; i++)
idx += skipFieldOrMethod(table, idx);
int methods = table.readUnsignedShort(idx);
idx += 2;
for (int i = 0; i < methods; i++)
idx += skipFieldOrMethod(table, idx);
// look for annotation attrs
int attrs = table.readUnsignedShort(idx);
idx += 2;
int name;
for (int i = 0; i < attrs; i++) {
name = table.readUnsignedShort(idx);
idx += 2;
if ("RuntimeVisibleAnnotations".equals(table.readString
(table.get(name))))
return matchAnnotations(table, idx + 4);
idx += 4 + table.readInt(idx);
}
} catch (ArrayIndexOutOfBoundsException e) {
/*
* This ArrayIndexOutOfBoundsException indicates an incorrectly
* formed .class file. We will eat the exception, log a trace
* message (if a log exists), and return "false" to indicate there
* was no match.
*/
Error cfe = new ClassFormatError(rsrc.getName());
cfe.initCause(e);
if (_log != null && _log.isTraceEnabled())
_log.trace(_loc.get("class-arg", rsrc.getName()), cfe);
}
return false;
}
/**
* Return whether the given annotations match our candidates.
*/
private boolean matchAnnotations(ConstantPoolTable table, int idx) {
int annos = table.readUnsignedShort(idx);
idx += 2;
int type;
int props;
for (int i = 0; i < annos; i++) {
type = table.readUnsignedShort(idx);
idx += 2;
if (matchAnnotation(table.readString(table.get(type))))
return true;
props = table.readUnsignedShort(idx);
idx += 2;
for (int j = 0; j < props; j++) {
idx += 2; // name
idx += skipAnnotationPropertyValue(table, idx);
}
}
return false;
}
/**
* Return whether the given annotation matches our candidates.
*/
private boolean matchAnnotation(String name) {
for (String anno : _annos)
if (name.equals(anno))
return true;
return false;
}
/**
* Skip an annotation property value, returning the number of bytes skipped.
*/
private static int skipAnnotationPropertyValue(ConstantPoolTable table,
int idx) {
int skipped = 0;
switch (table.readByte(idx + skipped++)) {
case 'Z': // bool
case 'B': // byte
case 'C': // char
case 'D': // double
case 'F': // float
case 'I': // int
case 'J': // long
case 'S': // short
case 's': // string
case 'c': // class
skipped += 2;
break;
case 'e': // enum ptr
skipped += 4;
break;
case '[': // array
int size = table.readUnsignedShort(idx + skipped);
skipped += 2;
for (int i = 0; i < size; i++)
skipped +=
skipAnnotationPropertyValue(table, idx + skipped);
break;
case '@': // anno
skipped += 2; // type
int props = table.readUnsignedShort(idx + skipped);
skipped += 2;
for (int j = 0; j < props; j++) {
skipped += 2; // name
skipped +=
skipAnnotationPropertyValue(table, idx + skipped);
}
break;
}
return skipped;
}
/**
* Skip the current field or method, returning the number of bytes skipped.
*/
private static int skipFieldOrMethod(ConstantPoolTable table, int idx) {
int attrs = table.readUnsignedShort(idx + 6);
int skipped = 8;
int len;
for (int i = 0; i < attrs; i++) {
len = table.readInt(idx + skipped + 2);
skipped += 6 + len;
}
return skipped;
}
public Log getLog() {
return _log;
}
public void setLog(Log _log) {
this._log = _log;
}
}