Moved annotations to separate project

This commit is contained in:
nathan 2015-02-02 13:12:29 +01:00
parent e3bad2a55a
commit e000c346b4
12 changed files with 0 additions and 2079 deletions

View File

@ -1,61 +0,0 @@
package dorkbox.util.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* for specifying the default report methods, without constantly creating new objects
*/
public class AnnotationDefaults {
public static final ReporterFunction<String> getTypeName = new ReporterFunction<String>() {
@Override
public String report(Cursor cursor) {
return cursor.getTypeName();
}
};
public static final ReporterFunction<Class<? extends Annotation>> getAnnotationType = new ReporterFunction<Class<? extends Annotation>>() {
@Override
public Class<? extends Annotation> report(Cursor cursor) {
return cursor.getAnnotationType();
}
};
public static final ReporterFunction<ElementType> getElementType = new ReporterFunction<ElementType>() {
@Override
public ElementType report(Cursor cursor) {
return cursor.getElementType();
}
};
public static final ReporterFunction<String> getMemberName = new ReporterFunction<String>() {
@Override
public String report(Cursor cursor) {
return cursor.getMemberName();
}
};
public static final ReporterFunction<Class<?>> getType = new ReporterFunction<Class<?>>() {
@Override
public Class<?> report(Cursor cursor) {
return cursor.getType();
}
};
public static final ReporterFunction<Constructor<?>> getConstructor = new ReporterFunction<Constructor<?>>() {
@Override
public Constructor<?> report(Cursor cursor) {
return cursor.getConstructor();
}
};
public static final ReporterFunction<Field> getField = new ReporterFunction<Field>() {
@Override
public Field report(Cursor cursor) {
return cursor.getField();
}
};
public static final ReporterFunction<Method> getMethod = new ReporterFunction<Method>() {
@Override
public Method report(Cursor cursor) {
return cursor.getMethod();
}
};
}

View File

@ -1,954 +0,0 @@
/* AnnotationDetector.java
*
* Created: 2011-10-10 (Year-Month-Day)
* Character encoding: UTF-8
*
****************************************** LICENSE *******************************************
*
* Copyright (c) 2011 - 2014 XIAM Solutions B.V. (http://www.xiam.nl)
*
* 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 dorkbox.util.annotation;
import java.io.DataInput;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.LoggerFactory;
/**
* {@code AnnotationDetector} reads Java Class Files ("*.class") and reports the
* found annotations via a simple, developer friendly API.
* <p>
* A Java Class File consists of a stream of 8-bit bytes. All 16-bit, 32-bit, and 64-bit
* quantities are constructed by reading in two, four, and eight consecutive 8-bit
* bytes, respectively. Multi byte data items are always stored in big-endian order,
* where the high bytes come first. In the Java platforms, this format is
* supported by interfaces {@link java.io.DataInput} and {@link java.io.DataOutput}.
* <p>
* A class file consists of a single ClassFile structure:
* <pre>
* ClassFile {
* u4 magic;
* u2 minor_version;
* u2 major_version;
* u2 constant_pool_count;
* cp_info constant_pool[constant_pool_count-1];
* u2 access_flags;
* u2 this_class;
* u2 super_class;
* u2 interfaces_count;
* u2 interfaces[interfaces_count];
* u2 fields_count;
* field_info fields[fields_count];
* u2 methods_count;
* method_info methods[methods_count];
* u2 attributes_count;
* attribute_info attributes[attributes_count];
* }
*
* Where:
* u1 unsigned byte {@link java.io.DataInput#readUnsignedByte()}
* u2 unsigned short {@link java.io.DataInput#readUnsignedShort()}
* u4 unsigned int {@link java.io.DataInput#readInt()}
*
* Annotations are stored as Attributes, named "RuntimeVisibleAnnotations" for
* {@link java.lang.annotation.RetentionPolicy#RUNTIME} and "RuntimeInvisibleAnnotations" for
* {@link java.lang.annotation.RetentionPolicy#CLASS}.
* </pre>
* References:
* <ul>
* <li><a href="http://en.wikipedia.org/wiki/Java_class_file">Java class file (Wikipedia)</a>
* (Gentle Introduction);
* <li><a href="http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html">Java
* VM Specification, Java SE 8 Edition (Chapter 4)</a> for the real work.
* <li><a href="http://stackoverflow.com/questions/259140">scanning java annotations at
* runtime</a>.
* </ul>
* <p>
* Similar projects / libraries:
* <ul>
* <li><a href="http://community.jboss.org/wiki/MCScanninglib">JBoss MC Scanning lib</a>;
* <li><a href="http://code.google.com/p/reflections/">Google Reflections</a>, in fact an
* improved version of <a href="http://scannotation.sourceforge.net/">scannotation</a>;
* <li><a href="https://github.com/ngocdaothanh/annovention">annovention</a>, improved version
* of the <a href="http://code.google.com/p/annovention">original Annovention</a> project.
* Available from maven: {@code tv.cntt:annovention:1.2};
* <li>If using the Spring Framework, use {@code ClassPathScanningCandidateComponentProvider}
* </ul>
* <p>
* All above mentioned projects make use of a byte code manipulation library (like BCEL,
* ASM or Javassist).
*
* @author <a href="mailto:rmuller@xiam.nl">Ronald K. Muller</a>
* @since annotation-detector 3.0.0
*/
public final class AnnotationDetector implements Builder, Cursor {
private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(AnnotationDetector.class);
// Constant Pool type tags
private static final int CP_UTF8 = 1;
private static final int CP_INTEGER = 3;
private static final int CP_FLOAT = 4;
private static final int CP_LONG = 5;
private static final int CP_DOUBLE = 6;
private static final int CP_CLASS = 7;
private static final int CP_STRING = 8;
private static final int CP_REF_FIELD = 9;
private static final int CP_REF_METHOD = 10;
private static final int CP_REF_INTERFACE = 11;
private static final int CP_NAME_AND_TYPE = 12;
private static final int CP_METHOD_HANDLE = 15; // Java VM SE 7
private static final int CP_METHOD_TYPE = 16; // Java VM SE 7
private static final int CP_INVOKE_DYNAMIC = 18; // Java VM SE 7
// AnnotationElementValue / Java raw types
private static final int BYTE = 'B';
private static final int CHAR = 'C';
private static final int DOUBLE = 'D';
private static final int FLOAT = 'F';
private static final int INT = 'I';
private static final int LONG = 'J';
private static final int SHORT = 'S';
private static final int BOOLEAN = 'Z';
private static final int ARRAY = '[';
// used for AnnotationElement only
private static final int STRING = 's';
private static final int ENUM = 'e';
private static final int CLASS = 'c';
private static final int ANNOTATION = '@';
private final ClassLoader loader;
// The buffer is reused during the life cycle of this AnnotationDetector instance
private final ClassFileBuffer cpBuffer = new ClassFileBuffer();
private final ClassIterator cfIterator;
// The Element Types to detect
private final Set<ElementType> elementTypes = EnumSet.of(ElementType.TYPE);
// Reusing the constantPool is not needed for better performance
private Object[] constantPool;
// The cached annotation types to report, maps raw Annotation type name to Class object
private Map<String, Class<? extends Annotation>> annotations;
private FilenameFilter filter;
private Reporter reporter;
// The current annotation reported
private Class<? extends Annotation> annotationType;
// The 'raw' name of the current interface or class being scanned and reported
// (using '/' instead of '.' in package name)
private String typeName;
// The current method or field (if any) being scanned
private String memberName;
// The current Element Type beinig reported
private ElementType elementType;
// The method descriptor of the currently reported annotated method in "raw"
// format (as it appears in the Java Class File). Example method descriptors:
// "()V" no arguments, return type void
// "(Ljava/lang/String;II)I" String, int, int as arguments, return type int
private String methodDescriptor;
private AnnotationDetector(ClassLoader loader, final File[] filesOrDirectories, ClassIterator iterator, final String[] pkgNameFilter) {
this.loader = loader;
if (iterator == null) {
this.cfIterator = new ClassFileIterator(filesOrDirectories, pkgNameFilter);
if (filesOrDirectories.length == 0) {
LOG.warn("No files or directories to scan!");
} else if (LOG.isTraceEnabled()) {
LOG.trace("Files and root directories scanned:\n{}",
Arrays.toString(filesOrDirectories).replace(", ", "\n"));
}
} else {
this.cfIterator = iterator;
if (LOG.isTraceEnabled()) {
LOG.trace("Class Files from the custom classfileiterator scanned.");
}
}
}
/**
* Factory method, starting point for the fluent interface.
* Only scan Class Files in the specified packages. If nothing is specified, all classes
* on the class path are scanned.
*/
public static Builder scanClassPath(final String... packageNames)
throws IOException {
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
return scanClassPath(loader, packageNames);
}
/**
* Factory method, starting point for the fluent interface.
* Only scan Class Files in the specified packages. If nothing is specified, all classes
* on the class path are scanned.
*/
public static Builder scanClassPath(ClassLoader loader, final String... packageNames)
throws IOException {
final String[] pkgNameFilter;
// DORKBOX added
boolean isCustomLoader = "dorkbox.classloader.ClassLoader" == loader.getClass().getName();
if (isCustomLoader) {
final List<URL> fileNames;
// scanning the classpath
if (packageNames.length == 0) {
pkgNameFilter = null;
List<String> asList = Arrays.asList(System.getProperty("java.class.path").split(File.pathSeparator));
fileNames = new ArrayList<URL>(asList.size());
for (String s : asList) {
File file = new File(s);
fileNames.add(file.toURI().toURL());
}
}
// scanning specific packages
else {
pkgNameFilter = new String[packageNames.length];
for (int i = 0; i < pkgNameFilter.length; ++i) {
pkgNameFilter[i] = packageNames[i].replace('.', '/');
if (!pkgNameFilter[i].endsWith("/")) {
pkgNameFilter[i] = pkgNameFilter[i].concat("/");
}
}
fileNames = new ArrayList<URL>();
for (final String packageName : pkgNameFilter) {
final Enumeration<URL> resourceEnum = loader.getResources(packageName);
while (resourceEnum.hasMoreElements()) {
final URL url = resourceEnum.nextElement();
fileNames.add(url);
}
}
}
return new AnnotationDetector(loader, null, new CustomClassloaderIterator(fileNames, packageNames), pkgNameFilter);
} else {
final Set<File> files = new HashSet<File>();
if (packageNames.length == 0) {
pkgNameFilter = null;
final String[] fileNames = System.getProperty("java.class.path").split(File.pathSeparator);
for (int i = 0; i < fileNames.length; ++i) {
files.add(new File(fileNames[i]));
}
} else {
pkgNameFilter = new String[packageNames.length];
for (int i = 0; i < pkgNameFilter.length; ++i) {
pkgNameFilter[i] = packageNames[i].replace('.', '/');
if (!pkgNameFilter[i].endsWith("/")) {
pkgNameFilter[i] = pkgNameFilter[i].concat("/");
}
}
for (final String packageName : pkgNameFilter) {
addFiles(loader, packageName, files);
}
}
return new AnnotationDetector(loader, files.toArray(new File[files.size()]), null, pkgNameFilter);
}
}
/**
* Factory method, starting point for the fluent interface.
* Scan all files specified by the classFileIterator.
*/
public static Builder scan(ClassLoader loader, final ClassIterator iterator) {
return new AnnotationDetector(loader, null, iterator, null);
}
/**
* Factory method, starting point for the fluent interface.
* Scan all files in the specified jar files and directories.
*/
public static Builder scanFiles(ClassLoader loader, final File... filesOrDirectories) {
return new AnnotationDetector(loader, filesOrDirectories, null, null);
}
/**
* Factory method, starting point for the fluent interface.
* Scan all files in the specified jar files and directories.
*/
public static Builder scanFiles(final File... filesOrDirectories) {
return new AnnotationDetector(Thread.currentThread().getContextClassLoader(), filesOrDirectories, null, null);
}
/**
* See {@link Builder#forAnnotations(java.lang.Class...) }.
*/
@Override
public Builder forAnnotations(final Class<? extends Annotation> annotation) {
this.annotations = new HashMap<String, Class<? extends Annotation>>(1);
// map "raw" type names to Class object
this.annotations.put("L" + annotation.getName().replace('.', '/') + ";", annotation);
return this;
}
/**
* See {@link Builder#forAnnotations(java.lang.Class...) }.
*/
@SuppressWarnings("unchecked")
@Override
public Builder forAnnotations(final Class<? extends Annotation>... annotations) {
this.annotations = new HashMap<String, Class<? extends Annotation>>(annotations.length);
// map "raw" type names to Class object
for (int i = 0; i < annotations.length; ++i) {
this.annotations.put("L" + annotations[i].getName().replace('.', '/') + ";", annotations[i]);
}
return this;
}
/**
* See {@link Builder#on(java.lang.annotation.ElementType...) }.
*/
@Override
public Builder on(final ElementType type) {
if (type == null) {
throw new IllegalArgumentException("At least one Element Type must be specified");
}
this.elementTypes.clear();
switch (type) {
case TYPE:
case CONSTRUCTOR:
case METHOD:
case FIELD:
this.elementTypes.add(type);
break;
default:
throw new IllegalArgumentException("Unsupported: " + type);
}
return this;
}
/**
* See {@link Builder#on(java.lang.annotation.ElementType...) }.
*/
@Override
public Builder on(final ElementType... types) {
if (types.length == 0) {
throw new IllegalArgumentException("At least one Element Type must be specified");
}
this.elementTypes.clear();
for (ElementType t : types) {
switch (t) {
case TYPE:
case CONSTRUCTOR:
case METHOD:
case FIELD:
this.elementTypes.add(t);
break;
default:
throw new IllegalArgumentException("Unsupported: " + t);
}
}
return this;
}
/**
* See {@link Builder#filter(java.io.FilenameFilter) }.
*/
@Override
public Builder filter(final FilenameFilter filter) {
if (filter == null) {
throw new NullPointerException("'filter' may not be null");
}
this.filter = filter;
return this;
}
/**
* See {@link Builder#report(dorkbox.util.annotation.AnnotationDetector.Reporter) }.
*/
@Override
public void report(final Reporter reporter) throws IOException {
this.reporter = reporter;
detect(this.cfIterator);
}
/**
* See {@link Builder#collect(dorkbox.util.annotation.AnnotationDetector.ReporterFunction) }.
*/
@Override
public <T> List<T> collect(final ReporterFunction<T> reporter) throws IOException {
final List<T> list = new ArrayList<T>();
this.reporter = new Reporter() {
@Override
public void report(Cursor cursor) {
list.add(reporter.report(cursor));
}
};
detect(this.cfIterator);
return list;
}
/**
* See {@link Cursor#getTypeName() }.
*/
@Override
public String getTypeName() {
return this.typeName.replace('/', '.');
}
/**
* See {@link Cursor#getAnnotationType() }.
*/
@Override
public Class<? extends Annotation> getAnnotationType() {
return this.annotationType;
}
/**
* See {@link Cursor#getElementType() }.
*/
@Override
public ElementType getElementType() {
return this.elementType;
}
/**
* See {@link Cursor#getMemberName() }.
*/
@Override
public String getMemberName() {
return this.memberName;
}
/**
* See {@link Cursor#getType() }.
*/
@Override
public Class<?> getType() {
return loadClass(this.loader, getTypeName());
}
/**
* See {@link Cursor#getField() }.
*/
@Override
public Field getField() {
if (this.elementType != ElementType.FIELD) {
throw new IllegalStateException(
"Illegal to call getField() when " + this.elementType + " is reported");
}
try {
return getType().getDeclaredField(this.memberName);
} catch (NoSuchFieldException ex) {
throw assertionError(
"Cannot find Field '%s' for type %s", this.memberName, getTypeName());
}
}
/**
* See {@link Cursor#getConstructor() }.
*/
@Override
public Constructor<?> getConstructor() {
if (this.elementType != ElementType.CONSTRUCTOR) {
throw new IllegalStateException(
"Illegal to call getMethod() when " + this.elementType + " is reported");
}
try {
final Class<?>[] parameterTypes = parseArguments(this.methodDescriptor);
return getType().getConstructor(parameterTypes);
} catch (NoSuchMethodException ex) {
throw assertionError(
"Cannot find Contructor '%s(...)' for type %s", this.memberName, getTypeName());
}
}
/**
* See {@link Cursor#getMethod() }.
*/
@Override
public Method getMethod() {
if (this.elementType != ElementType.METHOD) {
throw new IllegalStateException(
"Illegal to call getMethod() when " + this.elementType + " is reported");
}
try {
final Class<?>[] parameterTypes = parseArguments(this.methodDescriptor);
return getType().getDeclaredMethod(this.memberName, parameterTypes);
} catch (NoSuchMethodException ex) {
throw assertionError(
"Cannot find Method '%s(...)' for type %s", this.memberName, getTypeName());
}
}
/**
* See {@link Cursor#getAnnotation(java.lang.Class) }.
*/
@Override
public <T extends Annotation> T getAnnotation(final Class<T> annotationClass) {
if (!annotationClass.equals(this.annotationType)) {
throw new IllegalStateException("Illegal to call getAnnotation() when " +
this.annotationType.getName() + " is reported");
}
final AnnotatedElement ae;
switch (this.elementType) {
case TYPE:
ae = getType();
break;
case FIELD:
ae = getField();
break;
case METHOD:
ae = getMethod();
break;
default:
throw new AssertionError(this.elementType);
}
return ae.getAnnotation(annotationClass);
}
// private
private static void addFiles(ClassLoader loader, String resourceName, Set<File> files)
throws IOException {
final Enumeration<URL> resourceEnum = loader.getResources(resourceName);
while (resourceEnum.hasMoreElements()) {
final URL url = resourceEnum.nextElement();
if (LOG.isTraceEnabled()) {
LOG.trace("Resource URL: {}", url);
}
// Handle JBoss VFS URL's which look like (example package 'nl.dvelop'):
// vfs:/foo/bar/website.war/WEB-INF/classes/nl/dvelop/
// vfs:/foo/bar/website.war/WEB-INF/lib/dwebcore-0.0.1.jar/nl/dvelop/
String protocol = url.getProtocol();
final boolean isVfs = "vfs".equals(protocol);
if ("file".equals(protocol) || isVfs) {
final File dir = toFile(url);
if (dir.isDirectory()) {
files.add(dir);
} else if (isVfs) {
//Jar file via JBoss VFS protocol - strip package name
String jarPath = dir.getPath();
final int idx = jarPath.indexOf(".jar");
if (idx > -1) {
jarPath = jarPath.substring(0, idx + 4);
final File jarFile = new File(jarPath);
if (jarFile.isFile()) {
files.add(jarFile);
}
}
} else {
throw assertionError("Not a recognized file URL: %s", url);
}
} else {
// Resource in Jar File
final File jarFile =
toFile(((JarURLConnection)url.openConnection()).getJarFileURL());
if (jarFile.isFile()) {
files.add(jarFile);
} else {
throw assertionError("Not a File: %s", jarFile);
}
}
}
}
private static File toFile(final URL url) throws IOException {
// only correct way to convert the URL to a File object, also see issue #16
// Do not use URLDecoder
try {
return new File(url.toURI());
} catch (URISyntaxException ex) {
throw new IOException(ex.getMessage());
}
}
private void detect(final ClassIterator iterator) throws IOException {
InputStream stream;
boolean mustEndInClass = iterator instanceof ClassFileIterator;
while ((stream = iterator.next(this.filter)) != null) {
try {
this.cpBuffer.readFrom(stream);
String name = iterator.getName();
// SOME files can actually have CAFEBABE (binary files), but are NOT CLASSFILES! Explicitly define this!
if (hasCafebabe(this.cpBuffer)) {
if (mustEndInClass && !name.endsWith(".class")) {
continue;
}
if (LOG.isTraceEnabled()) {
LOG.trace("Class File: {}", name);
}
read(this.cpBuffer);
} // else ignore
} finally {
// closing InputStream from ZIP Entry is handled by ZipFileIterator
if (iterator.isFile()) {
stream.close();
}
}
}
}
private boolean hasCafebabe(final ClassFileBuffer buffer) throws IOException {
return buffer.size() > 4 && buffer.readInt() == 0xCAFEBABE;
}
/**
* Inspect the given (Java) class file in streaming mode.
*/
private void read(final DataInput di) throws IOException {
readVersion(di);
readConstantPoolEntries(di);
readAccessFlags(di);
readThisClass(di);
readSuperClass(di);
readInterfaces(di);
readFields(di);
readMethods(di);
readAttributes(di, ElementType.TYPE);
}
private void readVersion(final DataInput di) throws IOException {
// sequence: minor version, major version (argument_index is 1-based)
if (LOG.isTraceEnabled()) {
int minor = di.readUnsignedShort();
int maj = di.readUnsignedShort();
LOG.trace("Java Class version {}.{}", maj, minor);
} else {
di.skipBytes(4);
}
}
private void readConstantPoolEntries(final DataInput di) throws IOException {
final int count = di.readUnsignedShort();
this.constantPool = new Object[count];
for (int i = 1; i < count; ++i) {
if (readConstantPoolEntry(di, i)) {
// double slot
++i;
}
}
}
/**
* Return {@code true} if a double slot is read (in case of Double or Long constant).
*/
private boolean readConstantPoolEntry(final DataInput di, final int index)
throws IOException {
final int tag = di.readUnsignedByte();
switch (tag) {
case CP_METHOD_TYPE:
di.skipBytes(2); // readUnsignedShort()
return false;
case CP_METHOD_HANDLE:
di.skipBytes(3);
return false;
case CP_INTEGER:
case CP_FLOAT:
case CP_REF_FIELD:
case CP_REF_METHOD:
case CP_REF_INTERFACE:
case CP_NAME_AND_TYPE:
case CP_INVOKE_DYNAMIC:
di.skipBytes(4); // readInt() / readFloat() / readUnsignedShort() * 2
return false;
case CP_LONG:
case CP_DOUBLE:
di.skipBytes(8); // readLong() / readDouble()
return true;
case CP_UTF8:
this.constantPool[index] = di.readUTF();
return false;
case CP_CLASS:
case CP_STRING:
// reference to CP_UTF8 entry. The referenced index can have a higher number!
this.constantPool[index] = di.readUnsignedShort();
return false;
default:
throw new ClassFormatError(
"Unkown tag value for constant pool entry: " + tag);
}
}
private void readAccessFlags(final DataInput di) throws IOException {
di.skipBytes(2); // u2
}
private void readThisClass(final DataInput di) throws IOException {
this.typeName = resolveUtf8(di);
}
private void readSuperClass(final DataInput di) throws IOException {
di.skipBytes(2); // u2
}
private void readInterfaces(final DataInput di) throws IOException {
final int count = di.readUnsignedShort();
di.skipBytes(count * 2); // count * u2
}
private void readFields(final DataInput di) throws IOException {
final int count = di.readUnsignedShort();
for (int i = 0; i < count; ++i) {
readAccessFlags(di);
this.memberName = resolveUtf8(di);
// decriptor is Field type in raw format, we do not need it, so skip
//final String descriptor = resolveUtf8(di);
di.skipBytes(2);
LOG.trace("Field: {}", this.memberName);
readAttributes(di, ElementType.FIELD);
}
}
private void readMethods(final DataInput di) throws IOException {
final int count = di.readUnsignedShort();
for (int i = 0; i < count; ++i) {
readAccessFlags(di);
this.memberName = resolveUtf8(di);
this.methodDescriptor = resolveUtf8(di);
LOG.trace("Method: {}", this.memberName);
readAttributes(di, "<init>".equals(this.memberName) ? ElementType.CONSTRUCTOR : ElementType.METHOD);
}
}
private void readAttributes(final DataInput di, final ElementType reporterType)
throws IOException {
final int count = di.readUnsignedShort();
for (int i = 0; i < count; ++i) {
final String name = resolveUtf8(di);
// in bytes, use this to skip the attribute info block
final int length = di.readInt();
if (this.elementTypes.contains(reporterType) &&
("RuntimeVisibleAnnotations".equals(name) ||
"RuntimeInvisibleAnnotations".equals(name))) {
LOG.trace("Attribute: {}", name);
readAnnotations(di, reporterType);
} else {
LOG.trace("Attribute: {} (ignored)", name);
di.skipBytes(length);
}
}
}
private void readAnnotations(final DataInput di, final ElementType elementType)
throws IOException {
// the number of Runtime(In)VisibleAnnotations
final int count = di.readUnsignedShort();
for (int i = 0; i < count; ++i) {
final String rawTypeName = readAnnotation(di);
this.annotationType = this.annotations.get(rawTypeName);
if (this.annotationType == null) {
LOG.trace("Annotation: {} (ignored)", rawTypeName);
continue;
}
LOG.trace("Annotation: ''{}'' on type ''{}'', member ''{}'' (reported)",
this.annotationType.getName(), getTypeName(), getMemberName());
this.elementType = elementType;
this.reporter.report(this);
}
}
private String readAnnotation(final DataInput di) throws IOException {
final String rawTypeName = resolveUtf8(di);
// num_element_value_pairs
final int count = di.readUnsignedShort();
for (int i = 0; i < count; ++i) {
if (LOG.isTraceEnabled()) {
LOG.trace("Anntotation Element: {}", resolveUtf8(di));
} else {
di.skipBytes(2);
}
readAnnotationElementValue(di);
}
return rawTypeName;
}
private void readAnnotationElementValue(final DataInput di) throws IOException {
final int tag = di.readUnsignedByte();
switch (tag) {
case BYTE:
case CHAR:
case DOUBLE:
case FLOAT:
case INT:
case LONG:
case SHORT:
case BOOLEAN:
case STRING:
di.skipBytes(2);
break;
case ENUM:
di.skipBytes(4); // 2 * u2
break;
case CLASS:
di.skipBytes(2);
break;
case ANNOTATION:
readAnnotation(di);
break;
case ARRAY:
final int count = di.readUnsignedShort();
for (int i = 0; i < count; ++i) {
readAnnotationElementValue(di);
}
break;
default:
throw new ClassFormatError("Not a valid annotation element type tag: 0x" +
Integer.toHexString(tag));
}
}
/**
* Look up the String value, identified by the u2 index value from constant pool
* (direct or indirect).
*/
private String resolveUtf8(final DataInput di) throws IOException {
final int index = di.readUnsignedShort();
final Object value = this.constantPool[index];
final String s;
if (value instanceof Integer) {
s = (String)this.constantPool[(Integer)value];
} else {
s = (String)value;
}
return s;
}
/**
* Return the method arguments of the currently reported annotated method as a
* {@code Class} array.
*/
// incorrect detection of dereferencing possible null pointer
// TODO: https://github.com/checkstyle/checkstyle/issues/14 fixed in 5.8?
private Class<?>[] parseArguments(final String descriptor) {
final int n = descriptor.length();
// "minimal" descriptor: no arguments: "()V", first character is always '('
if (n < 3 || descriptor.charAt(0) != '(') {
throw unparseable(descriptor, "Wrong format");
}
List<Class<?>> args = null;
for (int i = 1; i < n; ++i) {
char c = descriptor.charAt(i);
if (i == 1) {
if (c == ')') {
return new Class<?>[0];
} else {
args = new LinkedList<Class<?>>();
}
}
assert args != null;
int j;
switch (c) {
case 'V':
args.add(void.class);
break;
case 'Z':
args.add(boolean.class);
break;
case 'C':
args.add(char.class);
break;
case 'B':
args.add(byte.class);
break;
case 'S':
args.add(short.class);
break;
case 'I':
args.add(int.class);
break;
case 'F':
args.add(float.class);
break;
case 'J':
args.add(long.class);
break;
case 'D':
args.add(double.class);
break;
case '[':
j = i;
do {
c = descriptor.charAt(++j);
} while (c == '['); // multi dimensional array
if (c == 'L') {
j = descriptor.indexOf(';', i + 1);
}
args.add(loadClass(this.loader, descriptor.substring(i, j + 1)));
i = j;
break;
case 'L':
j = descriptor.indexOf(';', i + 1);
args.add(loadClass(this.loader, descriptor.substring(i + 1, j)));
i = j;
break;
case ')':
// end of argument type list, stop parsing
return args.toArray(new Class<?>[args.size()]);
default:
throw unparseable(descriptor, "Not a recognoized type: " + c);
}
}
throw unparseable(descriptor, "No closing parenthesis");
}
/**
* Load the class, but do not initialize it.
*/
private static Class<?> loadClass(ClassLoader loader, final String rawClassName) {
final String typeName = rawClassName.replace('/', '.');
try {
return Class.forName(typeName, false, loader);
} catch (ClassNotFoundException ex) {
throw assertionError(
"Cannot load type '%s', scanned file not on class path? (%s)", typeName, ex);
}
}
/**
* The method descriptor must always be parseable, so if not an AssertionError is thrown.
*/
private static AssertionError unparseable(final String descriptor, final String cause) {
return assertionError(
"Unparseble method descriptor: '%s' (cause: %s)", descriptor, cause);
}
private static AssertionError assertionError(String message, Object... args) {
return new AssertionError(String.format(message, args));
}
}

View File

@ -1,119 +0,0 @@
/* Builder.java
*
* Created: 2014-06-15 (Year-Month-Day)
* Character encoding: UTF-8
*
****************************************** LICENSE *******************************************
*
* Copyright (c) 2014 XIAM Solutions B.V. (http://www.xiam.nl)
*
* 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 dorkbox.util.annotation;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.util.List;
/**
* {@code Builder} offers a fluent API for using {@link AnnotationDetector}.
* Its only role is to offer a more clean API.
*
* @author <a href="mailto:rmuller@xiam.nl">Ronald K. Muller</a>
* @since annotation-detector 3.1.0
*/
public interface Builder {
/**
* Specify the annotation types to report.
*/
@SuppressWarnings("unchecked")
Builder forAnnotations(final Class<? extends Annotation>... annotations);
/**
* Specify the annotation types to report.
*/
Builder forAnnotations(Class<? extends Annotation> annotation);
/**
* Specify the Element Types to scan. If this method is not called,
* {@link ElementType#TYPE} is used as default.
* <p>
* Valid types are:
* <ul>
* <li>{@link ElementType#TYPE}
* <li>{@link ElementType#METHOD}
* <li>{@link ElementType#FIELD}
* </ul>
* An {@code IllegalArgumentException} is thrown if another Element Type is specified or
* no types are specified.
*/
Builder on(final ElementType type);
/**
* Specify the Element Types to scan. If this method is not called,
* {@link ElementType#TYPE} is used as default.
* <p>
* Valid types are:
* <ul>
* <li>{@link ElementType#TYPE}
* <li>{@link ElementType#METHOD}
* <li>{@link ElementType#FIELD}
* </ul>
* An {@code IllegalArgumentException} is thrown if another Element Type is specified or
* no types are specified.
*/
Builder on(final ElementType... types);
/**
* Filter the scanned Class Files based on its name and the directory or jar file it is
* stored.
* <p>
* If the Class File is stored as a single file in the file system the {@code File}
* argument in {@link FilenameFilter#accept(java.io.File, java.lang.String) } is the
* absolute path to the root directory scanned.
* <p>
* If the Class File is stored in a jar file the {@code File} argument in
* {@link FilenameFilter#accept(java.io.File, java.lang.String)} is the absolute path of
* the jar file.
* <p>
* The {@code String} argument is the full name of the ClassFile in native format,
* including package name, like {@code eu/infomas/annotation/AnnotationDetector$1.class}.
* <p>
* Note that all non-Class Files are already filtered and not seen by the filter.
*
* @param filter The filter, never {@code null}
*/
Builder filter(final FilenameFilter filter);
/**
* Report the detected annotations to the specified {@code Reporter} instance.
*
* @see Reporter#report(dorkbox.util.annotations.Cursor)
* @see #collect(dorkbox.util.annotations.ReporterFunction)
*/
void report(final Reporter reporter) throws IOException;
/**
* Report the detected annotations to the specified {@code ReporterFunction} instance and
* collect the returned values of
* {@link ReporterFunction#report(dorkbox.util.annotations.Cursor) }.
* The collected values are returned as a {@code List}.
*
* @see #report(dorkbox.util.annotations.Reporter)
*/
<T> List<T> collect(final ReporterFunction<T> reporter) throws IOException;
}

View File

@ -1,240 +0,0 @@
/* ClassFileBuffer.java
*
* Created: 2011-10-10 (Year-Month-Day)
* Character encoding: UTF-8
*
****************************************** LICENSE *******************************************
*
* Copyright (c) 2011 - 2013 XIAM Solutions B.V. (http://www.xiam.nl)
*
* 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 dorkbox.util.annotation;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
/**
* {@code ClassFileBuffer} is used by {@link AnnotationDetector} to efficiently read Java
* ClassFile files from an {@link InputStream} and parse the content via the {@link DataInput}
* interface.
* <p>
* Note that Java ClassFile files can grow really big,
* {@code com.sun.corba.se.impl.logging.ORBUtilSystemException} is 128.2 kb!
*
* @author <a href="mailto:rmuller@xiam.nl">Ronald K. Muller</a>
* @since annotation-detector 3.0.0
*/
final class ClassFileBuffer implements DataInput {
private byte[] buffer;
private int size; // the number of significant bytes read
private int pointer; // the "read pointer"
/**
* Create a new, empty {@code ClassFileBuffer} with the default initial capacity (8 kb).
*/
ClassFileBuffer() {
this(8 * 1024);
}
/**
* Create a new, empty {@code ClassFileBuffer} with the specified initial capacity.
* The initial capacity must be greater than zero. The internal buffer will grow
* automatically when a higher capacity is required. However, buffer resizing occurs
* extra overhead. So in good initial capacity is important in performance critical
* situations.
*/
ClassFileBuffer(final int initialCapacity) {
if (initialCapacity < 1) {
throw new IllegalArgumentException("initialCapacity < 1: " + initialCapacity);
}
this.buffer = new byte[initialCapacity];
}
/**
* Clear and fill the buffer of this {@code ClassFileBuffer} with the
* supplied byte stream.
* The read pointer is reset to the start of the byte array.
*/
public void readFrom(final InputStream in) throws IOException {
this.pointer = 0;
this.size = 0;
int n;
do {
n = in.read(this.buffer, this.size, this.buffer.length - this.size);
if (n > 0) {
this.size += n;
}
resizeIfNeeded();
} while (n >= 0);
}
/**
* Sets the file-pointer offset, measured from the beginning of this file,
* at which the next read or write occurs.
*/
public void seek(final int position) throws IOException {
if (position < 0) {
throw new IllegalArgumentException("position < 0: " + position);
}
if (position > this.size) {
throw new EOFException();
}
this.pointer = position;
}
/**
* Return the size (in bytes) of this Java ClassFile file.
*/
public int size() {
return this.size;
}
// DataInput
@Override
public void readFully(final byte[] bytes) throws IOException {
readFully(bytes, 0, bytes.length);
}
@Override
public void readFully(final byte[] bytes, final int offset, final int length)
throws IOException {
if (length < 0 || offset < 0 || offset + length > bytes.length) {
throw new IndexOutOfBoundsException();
}
if (this.pointer + length > this.size) {
throw new EOFException();
}
System.arraycopy(this.buffer, this.pointer, bytes, offset, length);
this.pointer += length;
}
@Override
public int skipBytes(final int n) throws IOException {
seek(this.pointer + n);
return n;
}
@Override
public byte readByte() throws IOException {
if (this.pointer >= this.size) {
throw new EOFException();
}
return this.buffer[this.pointer++];
}
@Override
public boolean readBoolean() throws IOException {
return readByte() != 0;
}
@Override
public int readUnsignedByte() throws IOException {
if (this.pointer >= this.size) {
throw new EOFException();
}
return read();
}
@Override
public int readUnsignedShort() throws IOException {
if (this.pointer + 2 > this.size) {
throw new EOFException();
}
return (read() << 8) + read();
}
@Override
public short readShort() throws IOException {
return (short)readUnsignedShort();
}
@Override
public char readChar() throws IOException {
return (char)readUnsignedShort();
}
@Override
public int readInt() throws IOException {
if (this.pointer + 4 > this.size) {
throw new EOFException();
}
return (read() << 24) +
(read() << 16) +
(read() << 8) +
read();
}
@Override
public long readLong() throws IOException {
if (this.pointer + 8 > this.size) {
throw new EOFException();
}
return ((long)read() << 56) +
((long)read() << 48) +
((long)read() << 40) +
((long)read() << 32) +
(read() << 24) +
(read() << 16) +
(read() << 8) +
read();
}
@Override
public float readFloat() throws IOException {
return Float.intBitsToFloat(readInt());
}
@Override
public double readDouble() throws IOException {
return Double.longBitsToDouble(readLong());
}
/**
* This methods throws an {@link UnsupportedOperationException} because the method
* is deprecated and not used in the context of this implementation.
*
* @deprecated Does not support UTF-8, use readUTF() instead
*/
@Override
@Deprecated
public String readLine() throws IOException {
throw new UnsupportedOperationException("readLine() is deprecated and not supported");
}
@Override
public String readUTF() throws IOException {
return DataInputStream.readUTF(this);
}
// private
private int read() {
return this.buffer[this.pointer++] & 0xff;
}
private void resizeIfNeeded() {
if (this.size >= this.buffer.length) {
final byte[] newBuffer = new byte[this.buffer.length * 2];
System.arraycopy(this.buffer, 0, newBuffer, 0, this.buffer.length);
this.buffer = newBuffer;
}
}
}

View File

@ -1,135 +0,0 @@
/* ClassFileIterator.java
*
* Created: 2011-10-10 (Year-Month-Day)
* Character encoding: UTF-8
*
****************************************** LICENSE *******************************************
*
* Copyright (c) 2011 - 2013 XIAM Solutions B.V. (http://www.xiam.nl)
*
* 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 dorkbox.util.annotation;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
/**
* {@code ClassFileIterator} is used to iterate over all Java ClassFile files available within
* a specific context.
* <p>
* For every Java ClassFile ({@code .class}) an {@link InputStream} is returned.
*
* @author <a href="mailto:rmuller@xiam.nl">Ronald K. Muller</a>
* @since annotation-detector 3.0.0
*/
public class ClassFileIterator implements ClassIterator {
private FileIterator fileIter;
protected final String[] pkgNameFilter;
private ZipFileIterator zipIter;
private boolean isFile;
/**
* Create a new {@code ClassFileIterator} returning all Java ClassFile files available
* from the specified files and/or directories, including sub directories.
* <p>
* If the (optional) package filter is defined, only class files staring with one of the
* defined package names are returned.
* NOTE: package names must be defined in the native format (using '/' instead of '.').
*/
protected ClassFileIterator(final String[] pkgNameFilter) {
this.pkgNameFilter = pkgNameFilter;
}
/**
* Create a new {@code ClassFileIterator} returning all Java ClassFile files available
* from the specified files and/or directories, including sub directories.
* <p>
* If the (optional) package filter is defined, only class files staring with one of the
* defined package names are returned.
* NOTE: package names must be defined in the native format (using '/' instead of '.').
*/
protected ClassFileIterator(final File[] filesOrDirectories, final String[] pkgNameFilter) {
this.fileIter = new FileIterator(filesOrDirectories);
this.pkgNameFilter = pkgNameFilter;
}
/**
* Return the name of the Java ClassFile returned from the last call to {@link #next()}.
* The name is either the path name of a file or the name of an ZIP/JAR file entry.
*/
@Override
public String getName() {
// Both getPath() and getName() are very light weight method calls
return this.zipIter == null ?
this.fileIter.getFile().getPath() :
this.zipIter.getEntry().getName();
}
/**
* Return {@code true} if the current {@link InputStream} is reading from a plain
* {@link File}.
* Return {@code false} if the current {@link InputStream} is reading from a
* ZIP File Entry.
*/
@Override
public boolean isFile() {
return this.isFile;
}
/**
* Return the next Java ClassFile as an {@code InputStream}.
* <p>
* NOTICE: Client code MUST close the returned {@code InputStream}!
*/
@Override
public InputStream next(final FilenameFilter filter) throws IOException {
while (true) {
if (this.zipIter == null) {
final File file = this.fileIter.next();
if (file == null) {
return null;
} else {
final String path = file.getPath();
if (path.endsWith(".class") && (filter == null ||
filter.accept(this.fileIter.getRootFile(), this.fileIter.relativize(path)))) {
this.isFile = true;
return new FileInputStream(file);
} else if (this.fileIter.isRootFile() && endsWithIgnoreCase(path, ".jar")) {
this.zipIter = new ZipFileIterator(file, this.pkgNameFilter);
} // else just ignore
}
} else {
final InputStream is = this.zipIter.next(filter);
if (is == null) {
this.zipIter = null;
} else {
this.isFile = false;
return is;
}
}
}
}
// private
private static boolean endsWithIgnoreCase(final String value, final String suffix) {
final int n = suffix.length();
return value.regionMatches(true, value.length() - n, suffix, 0, n);
}
}

View File

@ -1,60 +0,0 @@
/* ClassFileIterator.java
*
* Created: 2011-10-10 (Year-Month-Day)
* Character encoding: UTF-8
*
****************************************** LICENSE *******************************************
*
* Copyright (c) 2011 - 2013 XIAM Solutions B.V. (http://www.xiam.nl)
*
* 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 dorkbox.util.annotation;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
/**
* {@code ClassFileIterator} is used to iterate over all Java ClassFile files available within
* a specific context.
* <p>
* For every Java ClassFile ({@code .class}) an {@link InputStream} is returned.
*
* @author <a href="mailto:rmuller@xiam.nl">Ronald K. Muller</a>
* @since annotation-detector 3.0.0
*/
public interface ClassIterator {
/**
* Return the name of the Java ClassFile returned from the last call to {@link #next()}.
* The name is either the path name of a file or the name of an ZIP/JAR file entry.
*/
String getName();
/**
* Return {@code true} if the current {@link InputStream} is reading from a plain
* {@link File}.
* Return {@code false} if the current {@link InputStream} is reading from a
* ZIP File Entry.
*/
boolean isFile();
/**
* Return the next Java ClassFile as an {@code InputStream}.
* <p>
* NOTICE: Client code MUST close the returned {@code InputStream}!
*/
InputStream next(final FilenameFilter filter) throws IOException;
}

View File

@ -1,84 +0,0 @@
/* Cursor.java
*
* Created: 2014-06-15 (Year-Month-Day)
* Character encoding: UTF-8
*
****************************************** LICENSE *******************************************
*
* Copyright (c) 2014 XIAM Solutions B.V. (http://www.xiam.nl)
*
* 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 dorkbox.util.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* {@code Cursor} offers a "cursor interface" for working with {@link AnnotationDetector}.
*
* @author <a href="mailto:rmuller@xiam.nl">Ronald K. Muller</a>
* @since annotation-detector 3.1.0
*/
public interface Cursor {
/**
* Return the type name of the currently reported Java Class File.
*/
String getTypeName();
/**
* Return the Annotation Type currently reported.
*/
Class<? extends Annotation> getAnnotationType();
/**
* Return the {@code ElementType} of the currently reported {@code Annotation}.
*/
ElementType getElementType();
/**
* Return the member name of the currently reported {@code Annotation}.
* In case of an annotation on type level, "&lt;clinit&gt;" is reported.
*/
String getMemberName();
/**
* Return the {@link Class type} of the currently reported Java Class File.
*/
Class<?> getType();
/**
* Return the {@link Constructor} instance of the currently reported annotated Constructor.
*/
Constructor<?> getConstructor();
/**
* Return the {@link Field} instance of the currently reported annotated Field.
*/
Field getField();
/**
* Return the {@link Method} instance of the currently reported annotated Method.
*/
Method getMethod();
/**
* Return the {@code Annotation} of the reported Annotated Element.
*/
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
}

View File

@ -1,91 +0,0 @@
package dorkbox.util.annotation;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import dorkbox.util.FileUtil;
public class CustomClassloaderIterator implements ClassIterator {
private volatile Iterator<URL> loaderFilesIterator;
private ClassFileIterator classFileIterator;
// have to support
// 1 - scanning the classpath
// 2 - scanning a specific package
public CustomClassloaderIterator(List<URL> fileNames, String[] packageNames) throws IOException {
// if ANY of our filenames DO NOT start with "box", we have to add it as a file, so our iterator picks it up (and if dir, it's childred)
Set<File> files = new HashSet<File>();
Iterator<URL> iterator = fileNames.iterator();
while (iterator.hasNext()) {
URL url = iterator.next();
if (!url.getProtocol().equals("box")) {
try {
File file = FileUtil.normalize(new File(url.toURI()));
files.add(file);
iterator.remove();
} catch (URISyntaxException ex) {
throw new IOException(ex.getMessage());
}
}
}
if (files.isEmpty()) {
this.classFileIterator = null;
} else {
this.classFileIterator = new ClassFileIterator(files.toArray(new File[0]), packageNames);
}
this.loaderFilesIterator = fileNames.iterator();
}
@Override
public String getName() {
// not needed
return null;
}
@Override
public boolean isFile() {
if (this.classFileIterator != null) {
return this.classFileIterator.isFile();
}
return false;
}
@Override
public InputStream next(FilenameFilter filter) throws IOException {
if (this.classFileIterator != null) {
while (true) {
InputStream next = this.classFileIterator.next(filter);
if (next == null) {
this.classFileIterator = null;
} else {
String name = this.classFileIterator.getName();
if (name.endsWith(".class")) {
return next;
}
}
}
}
if (this.loaderFilesIterator.hasNext()) {
URL next = this.loaderFilesIterator.next();
return next.openStream();
}
return null;
}
}

View File

@ -1,144 +0,0 @@
/* FileIterator.java
*
* Created: 2011-10-10 (Year-Month-Day)
* Character encoding: UTF-8
*
****************************************** LICENSE *******************************************
*
* Copyright (c) 2011 - 2013 XIAM Solutions B.V. (http://www.xiam.nl)
*
* 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 dorkbox.util.annotation;
import java.io.File;
import java.util.Deque;
import java.util.LinkedList;
import java.util.NoSuchElementException;
/**
* {@code FileIterator} enables iteration over all files in a directory and all its sub
* directories.
* <p>
* Usage:
* <pre>
* FileIterator iter = new FileIterator(new File("./src"));
* File f;
* while ((f = iter.next()) != null) {
* // do something with f
* assert f == iter.getCurrent();
* }
* </pre>
*
* @author <a href="mailto:rmuller@xiam.nl">Ronald K. Muller</a>
* @since annotation-detector 3.0.0
*/
final class FileIterator {
private final Deque<File> stack = new LinkedList<File>();
private int rootCount;
private File currentRoot;
private File current;
/**
* Create a new {@code FileIterator} using the specified 'filesOrDirectories' as root.
* <p>
* If 'filesOrDirectories' contains a file, the iterator just returns that single file.
* If 'filesOrDirectories' contains a directory, all files in that directory
* and its sub directories are returned (depth first).
*
* @param filesOrDirectories Zero or more {@link File} objects, which are iterated
* in the specified order (depth first)
*/
FileIterator(final File... filesOrDirectories) {
addReverse(filesOrDirectories);
this.rootCount = this.stack.size();
}
/**
* Return the last returned file or {@code null} if no more files are available.
*
* @see #next()
*/
File getFile() {
return this.current;
}
File getRootFile() {
return this.currentRoot;
}
/**
* Relativize the absolute full (file) 'path' against the current root file.
* <p>
* Example:<br/>
* Let current root be "/path/to/dir".
* Then {@code relativize("/path/to/dir/with/file.ext")} equals "with/file.ext" (without
* leading '/').
* <p>
* Note: the paths are not canonicalized!
*/
String relativize(final String path) {
assert path.startsWith(this.currentRoot.getPath());
return path.substring(this.currentRoot.getPath().length() + 1);
}
/**
* Return {@code true} if the current file is one of the files originally
* specified as one of the constructor file parameters, i.e. is a root file
* or directory.
*/
boolean isRootFile() {
if (this.current == null) {
throw new NoSuchElementException();
}
return this.stack.size() < this.rootCount;
}
/**
* Return the next {@link File} object or {@code null} if no more files are
* available.
*
* @see #getFile()
*/
File next() {
if (this.stack.isEmpty()) {
this.current = null;
return null;
} else {
this.current = this.stack.removeLast();
if (this.current.isDirectory()) {
if (this.stack.size() < this.rootCount) {
this.rootCount = this.stack.size();
this.currentRoot = this.current;
}
addReverse(this.current.listFiles());
return next();
} else {
return this.current;
}
}
}
// private
/**
* Add the specified files in reverse order.
*/
private void addReverse(final File[] files) {
for (int i = files.length - 1; i >= 0; --i) {
this.stack.add(files[i]);
}
}
}

View File

@ -1,46 +0,0 @@
/* Reporter.java
*
* Created: 2014-06-15 (Year-Month-Day)
* Character encoding: UTF-8
*
****************************************** LICENSE *******************************************
*
* Copyright (c) 2014 XIAM Solutions B.V. (http://www.xiam.nl)
*
* 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 dorkbox.util.annotation;
/**
* {@code Reporter} is used to report the detected annotations.
* <p>
* This interface is a so called "Single Abstract Method" (SAM) or "Functional Interface", so
* can be used as a Lambda in Java 8 (see examples).
*
* @see Builder#report(dorkbox.util.annotation.Reporter)
*
* @author <a href="mailto:rmuller@xiam.nl">Ronald K. Muller</a>
* @since annotation-detector 3.1.0
*/
public interface Reporter {
/**
* This method is called when an {@code Annotation} is detected. Invoke methods on the
* provided {@code Cursor} reference to get more specific information about the
* {@code Annotation}.
*
*/
void report(Cursor cursor);
}

View File

@ -1,40 +0,0 @@
/* ReporterFunction.java
*
* Created: 2014-06-15 (Year-Month-Day)
* Character encoding: UTF-8
*
****************************************** LICENSE *******************************************
*
* Copyright (c) 2014 XIAM Solutions B.V. (http://www.xiam.nl)
*
* 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 dorkbox.util.annotation;
/**
* {@code ReporterFunction} is used to report the detected annotations.
*
* @see Builder#collect(dorkbox.util.annotation.ReporterFunction)
*
* @author <a href="mailto:rmuller@xiam.nl">Ronald K. Muller</a>
* @since annotation-detector 3.1.0
*/
public interface ReporterFunction<T> {
/**
* This method is called when an {@code Annotation} is detected.
* Invoke methods on the {@code Cursor} to get more specific information about the
* {@code Annotation}.
*/
T report(Cursor cursor);
}

View File

@ -1,105 +0,0 @@
/* ZipFileIterator.java
*
* Created: 2011-10-10 (Year-Month-Day)
* Character encoding: UTF-8
*
****************************************** LICENSE *******************************************
*
* Copyright (c) 2011 - 2013 XIAM Solutions B.V. (http://www.xiam.nl)
*
* 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 dorkbox.util.annotation;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* {@code ZipFileIterator} is used to iterate over all entries in a given {@code zip} or
* {@code jar} file and returning the {@link InputStream} of these entries.
* <p>
* It is possible to specify an (optional) entry name filter.
* <p>
* The most efficient way of iterating is used, see benchmark in test classes.
*
* @author <a href="mailto:rmuller@xiam.nl">Ronald K. Muller</a>
* @since annotation-detector 3.0.0
*/
final class ZipFileIterator {
private final File file;
private final ZipFile zipFile;
private final String[] entryNameFilter;
private final Enumeration<? extends ZipEntry> entries;
private ZipEntry current;
/**
* Create a new {@code ZipFileIterator} instance.
*
* @param zipFile The ZIP file used to iterate over all entries
* @param entryNameFilter (optional) file name filter. Only entry names starting with
* one of the specified names in the filter are returned
*/
ZipFileIterator(final File file, final String[] entryNameFilter) throws IOException {
this.file = file;
this.zipFile = new ZipFile(file);
this.entryNameFilter = entryNameFilter;
this.entries = this.zipFile.entries();
}
public ZipEntry getEntry() {
return this.current;
}
public InputStream next(final FilenameFilter filter) throws IOException {
while (this.entries.hasMoreElements()) {
this.current = this.entries.nextElement();
if (filter == null || accept(this.current, filter)) {
return this.zipFile.getInputStream(this.current);
}
}
// no more entries in this ZipFile, so close ZipFile
try {
// zipFile is never null here
this.zipFile.close();
} catch (IOException ex) {
// suppress IOException, otherwise close() is called twice
}
return null;
}
private boolean accept(final ZipEntry entry, final FilenameFilter filter) {
if (entry.isDirectory()) {
return false;
}
final String name = entry.getName();
if (name.endsWith(".class") && filter.accept(this.file, name)) {
if (this.entryNameFilter == null) {
return true;
}
for (final String entryName : this.entryNameFilter) {
if (name.startsWith(entryName)) {
return true;
}
}
}
return false;
}
}