/* * 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('.', '/') + ";"; } 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 (int i = 0; i < _annos.length; i++) if (name.equals(_annos[i])) 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; } }