Initial import of annotations project

master
nathan 2015-02-02 12:54:51 +01:00
commit 20913ad79c
18 changed files with 2409 additions and 0 deletions

8
.classpath Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="/Dependencies/logging/slf4j-api-1.7.5.jar" sourcepath="/Dependencies/logging/slf4j-api-1.7.5-sources.zip"/>
<classpathentry kind="lib" path="libs/dorkboxUtil_v1.0.jar"/>
<classpathentry kind="output" path="classes"/>
</classpath>

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/classes/

17
.project Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>Annotations</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

33
LICENSE Normal file
View File

@ -0,0 +1,33 @@
- Dorkbox Annotations - Apache 2.0 License
https://github.com/dorkbox
Copyright 2014, dorkbox, llc
- AnnotationDetector - Apache 2.0 License
https://github.com/rmuller/infomas-asl
Copyright 2011 - 2014, XIAM Solutions B.V. (http://www.xiam.nl)
- Dorkbox Utils - Apache 2.0 License
https://github.com/dorkbox
Copyright 2010, dorkbox, llc
- FilenameUtils.java (normalize + dependencies) - Apache 2.0 License
http://commons.apache.org/proper/commons-io/
Copyright 2013, ASF
Kevin A. Burton
Scott Sanders
Daniel Rall
Christoph.Reck
Peter Donald
Jeff Turner
Matthew Hawthorne
Martin Cooper
Jeremias Maerki
Stephen Colebourne
- SLF4J - MIT License
http://www.slf4j.org
Copyright 2004-2008, QOS.ch

218
LICENSE.Apachev2 Normal file
View File

@ -0,0 +1,218 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

21
LICENSE.MIT Normal file
View File

@ -0,0 +1,21 @@
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,76 @@
/*
* Copyright 2014 XIAM Solutions B.V. The Netherlands (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;
/**
* 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

@ -0,0 +1,954 @@
/* 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

@ -0,0 +1,119 @@
/* 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

@ -0,0 +1,240 @@
/* 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

@ -0,0 +1,135 @@
/* 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

@ -0,0 +1,62 @@
/* 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.
*
* Modified 2014, dorkbox, llc
*/
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

@ -0,0 +1,84 @@
/* 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

@ -0,0 +1,106 @@
/*
* Copyright 2014 dorkbox, llc
*
* 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.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

@ -0,0 +1,144 @@
/* 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

@ -0,0 +1,46 @@
/* 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

@ -0,0 +1,40 @@
/* 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

@ -0,0 +1,105 @@
/* 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;
}
}