From 58b6663aae5313b41167d92851981ca549cbb461 Mon Sep 17 00:00:00 2001 From: patriot1burke Date: Thu, 10 Jan 2008 19:50:10 +0000 Subject: [PATCH 1/1] initial commit --- annotation-db.ipr | 363 ++++++++++++++++++ pom.xml | 53 +++ .../java/org/scannotation/AnnotationDB.java | 301 +++++++++++++++ .../classpath/ClasspathUrlFinder.java | 190 +++++++++ .../classpath/DirectoryIteratorFactory.java | 13 + .../scannotation/classpath/FileIterator.java | 68 ++++ .../FileProtocolIteratorFactory.java | 26 ++ .../org/scannotation/classpath/Filter.java | 10 + .../classpath/InputStreamWrapper.java | 72 ++++ .../classpath/IteratorFactory.java | 43 +++ .../scannotation/classpath/JarIterator.java | 77 ++++ .../classpath/StreamIterator.java | 25 ++ .../scannotation/classpath/WarUrlFinder.java | 74 ++++ .../test/ClassWithFieldAnnotation.java | 10 + .../java/org/scannotation/test/CrossRef.java | 12 + .../InterfaceWithParameterAnnotations.java | 10 + .../scannotation/test/SimpleAnnotation.java | 15 + .../java/org/scannotation/test/TestSmoke.java | 227 +++++++++++ 18 files changed, 1589 insertions(+) create mode 100644 annotation-db.ipr create mode 100644 pom.xml create mode 100644 src/main/java/org/scannotation/AnnotationDB.java create mode 100644 src/main/java/org/scannotation/classpath/ClasspathUrlFinder.java create mode 100644 src/main/java/org/scannotation/classpath/DirectoryIteratorFactory.java create mode 100644 src/main/java/org/scannotation/classpath/FileIterator.java create mode 100644 src/main/java/org/scannotation/classpath/FileProtocolIteratorFactory.java create mode 100644 src/main/java/org/scannotation/classpath/Filter.java create mode 100644 src/main/java/org/scannotation/classpath/InputStreamWrapper.java create mode 100644 src/main/java/org/scannotation/classpath/IteratorFactory.java create mode 100644 src/main/java/org/scannotation/classpath/JarIterator.java create mode 100644 src/main/java/org/scannotation/classpath/StreamIterator.java create mode 100644 src/main/java/org/scannotation/classpath/WarUrlFinder.java create mode 100644 src/test/java/org/scannotation/test/ClassWithFieldAnnotation.java create mode 100644 src/test/java/org/scannotation/test/CrossRef.java create mode 100644 src/test/java/org/scannotation/test/InterfaceWithParameterAnnotations.java create mode 100644 src/test/java/org/scannotation/test/SimpleAnnotation.java create mode 100644 src/test/java/org/scannotation/test/TestSmoke.java diff --git a/annotation-db.ipr b/annotation-db.ipr new file mode 100644 index 0000000..2c15589 --- /dev/null +++ b/annotation-db.ipr @@ -0,0 +1,363 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..353ce83 --- /dev/null +++ b/pom.xml @@ -0,0 +1,53 @@ + + 4.0.0 + org.scannotation + annotation-db + jar + 1.0 + resteasy-jsr311 + http://maven.apache.org + + + jboss + http://repository.jboss.org/maven2 + + + + + junit + junit + 4.0 + test + + + org.resteasy + titan-cruise + 1.0 + test + + + javassist + javassist + 3.6.0.GA + + + javax.servlet + servlet-api + 2.5 + provided + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.5 + 1.5 + + + + + diff --git a/src/main/java/org/scannotation/AnnotationDB.java b/src/main/java/org/scannotation/AnnotationDB.java new file mode 100644 index 0000000..da19ea2 --- /dev/null +++ b/src/main/java/org/scannotation/AnnotationDB.java @@ -0,0 +1,301 @@ +package org.scannotation; + +import javassist.bytecode.AnnotationsAttribute; +import javassist.bytecode.ClassFile; +import javassist.bytecode.FieldInfo; +import javassist.bytecode.MethodInfo; +import javassist.bytecode.ParameterAnnotationsAttribute; +import javassist.bytecode.annotation.Annotation; +import org.scannotation.classpath.Filter; +import org.scannotation.classpath.IteratorFactory; +import org.scannotation.classpath.StreamIterator; + +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.io.Serializable; +import java.net.URL; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class AnnotationDB implements Serializable +{ + protected Map> annotationIndex = new HashMap>(); + protected Map> implementsIndex = new HashMap>(); + protected Map> classIndex = new HashMap>(); + + protected transient boolean scanClassAnnotations = true; + protected transient boolean scanMethodAnnotations = true; + protected transient boolean scanParameterAnnotations = true; + protected transient boolean scanFieldAnnotations = true; + + public class CrossReferenceException extends RuntimeException + { + private Map> unresolved; + + public CrossReferenceException(Map> unresolved) + { + this.unresolved = unresolved; + } + + public Map> getUnresolved() + { + return unresolved; + } + } + + /** + * Sometimes you want to see if a particular class implements an interface with certain annotations + * After you have loaded all your classpaths with the scanArchive() method, call this method to cross reference + * a class's implemented interfaces. The cross references will be added to the annotationIndex and + * classIndex indexes + * + * @param ignoredPackages var arg list of packages to ignore + * @throws CrossReferenceException a RuntimeException thrown if referenced interfaces haven't been scanned + */ + public void crossReferenceImplementedInterfaces(String... ignoredPackages) throws CrossReferenceException + { + Map> unresolved = new HashMap>(); + for (String clazz : implementsIndex.keySet()) + { + Set intfs = implementsIndex.get(clazz); + for (String intf : intfs) + { + if (intf.startsWith("java.") || intf.startsWith("javax.")) continue; + boolean ignoreInterface = false; + for (String ignored : ignoredPackages) + { + if (intf.startsWith(ignored + ".")) + { + ignoreInterface = true; + break; + } + } + if (ignoreInterface) continue; + + Set unresolvedInterfaces = new HashSet(); + Set xrefAnnotations = classIndex.get(intf); + if (xrefAnnotations == null) + { + unresolvedInterfaces.add(intf); + unresolved.put(clazz, unresolvedInterfaces); + } + Set classAnnotations = classIndex.get(clazz); + classAnnotations.addAll(xrefAnnotations); + for (String annotation : xrefAnnotations) + { + Set classes = annotationIndex.get(annotation); + classes.add(clazz); + } + } + } + + } + + public Map> getAnnotationIndex() + { + return annotationIndex; + } + + public Map> getClassIndex() + { + return classIndex; + } + + public void setScanClassAnnotations(boolean scanClassAnnotations) + { + this.scanClassAnnotations = scanClassAnnotations; + } + + public void setScanMethodAnnotations(boolean scanMethodAnnotations) + { + this.scanMethodAnnotations = scanMethodAnnotations; + } + + public void setScanParameterAnnotations(boolean scanParameterAnnotations) + { + this.scanParameterAnnotations = scanParameterAnnotations; + } + + public void setScanFieldAnnotations(boolean scanFieldAnnotations) + { + this.scanFieldAnnotations = scanFieldAnnotations; + } + + + /** + * Scan a url that represents an "archive" this is a classpath directory or jar file + * + * @param url + * @throws IOException + */ + public void scanArchives(URL... urls) throws IOException + { + for (URL url : urls) + { + Filter filter = new Filter() + { + public boolean accepts(String filename) + { + return filename.endsWith(".class"); + } + }; + + StreamIterator it = IteratorFactory.create(url, filter); + + InputStream stream; + while ((stream = it.next()) != null) scanClass(stream); + } + + } + + /** + * Can a .class file for annotations + * + * @param bits + * @throws IOException + */ + public void scanClass(InputStream bits) throws IOException + { + DataInputStream dstream = new DataInputStream(new BufferedInputStream(bits)); + ClassFile cf = null; + try + { + cf = new ClassFile(dstream); + classIndex.put(cf.getName(), new HashSet()); + if (scanClassAnnotations) ; + scanClass(cf); + if (scanMethodAnnotations || scanParameterAnnotations) scanMethods(cf); + if (scanFieldAnnotations) scanFields(cf); + + // create an index of interfaces the class implements + if (cf.getInterfaces() != null) + { + Set intfs = new HashSet(); + for (String intf : cf.getInterfaces()) intfs.add(intf); + implementsIndex.put(cf.getName(), intfs); + } + + } + finally + { + dstream.close(); + bits.close(); + } + } + + protected void scanClass(ClassFile cf) + { + String className = cf.getName(); + AnnotationsAttribute visible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.visibleTag); + AnnotationsAttribute invisible = (AnnotationsAttribute) cf.getAttribute(AnnotationsAttribute.invisibleTag); + + if (visible != null) populate(visible.getAnnotations(), className); + if (invisible != null) populate(invisible.getAnnotations(), className); + } + + /** + * Scanns both the method and its parameters for annotations. + * + * @param cf + */ + protected void scanMethods(ClassFile cf) + { + List methods = cf.getMethods(); + if (methods == null) return; + for (Object obj : methods) + { + MethodInfo method = (MethodInfo) obj; + if (scanMethodAnnotations) + { + AnnotationsAttribute visible = (AnnotationsAttribute) method.getAttribute(AnnotationsAttribute.visibleTag); + AnnotationsAttribute invisible = (AnnotationsAttribute) method.getAttribute(AnnotationsAttribute.invisibleTag); + + if (visible != null) populate(visible.getAnnotations(), cf.getName()); + if (invisible != null) populate(invisible.getAnnotations(), cf.getName()); + } + if (scanParameterAnnotations) + { + ParameterAnnotationsAttribute paramsVisible = (ParameterAnnotationsAttribute) method.getAttribute(ParameterAnnotationsAttribute.visibleTag); + ParameterAnnotationsAttribute paramsInvisible = (ParameterAnnotationsAttribute) method.getAttribute(ParameterAnnotationsAttribute.invisibleTag); + + if (paramsVisible != null && paramsVisible.getAnnotations() != null) + { + for (Annotation[] anns : paramsVisible.getAnnotations()) + { + populate(anns, cf.getName()); + } + } + if (paramsInvisible != null && paramsInvisible.getAnnotations() != null) + { + for (Annotation[] anns : paramsInvisible.getAnnotations()) + { + populate(anns, cf.getName()); + } + } + } + } + } + + protected void scanFields(ClassFile cf) + { + List fields = cf.getFields(); + if (fields == null) return; + for (Object obj : fields) + { + FieldInfo field = (FieldInfo) obj; + AnnotationsAttribute visible = (AnnotationsAttribute) field.getAttribute(AnnotationsAttribute.visibleTag); + AnnotationsAttribute invisible = (AnnotationsAttribute) field.getAttribute(AnnotationsAttribute.invisibleTag); + + if (visible != null) populate(visible.getAnnotations(), cf.getName()); + if (invisible != null) populate(invisible.getAnnotations(), cf.getName()); + + } + + } + + protected void populate(Annotation[] annotations, String className) + { + if (annotations == null) return; + Set classAnnotations = classIndex.get(className); + for (Annotation ann : annotations) + { + Set classes = annotationIndex.get(ann.getTypeName()); + if (classes == null) + { + classes = new HashSet(); + annotationIndex.put(ann.getTypeName(), classes); + } + classes.add(className); + classAnnotations.add(ann.getTypeName()); + } + } + + public void outputAnnotationDB(PrintWriter writer) + { + for (String ann : annotationIndex.keySet()) + { + writer.print(ann); + writer.print(": "); + Set classes = annotationIndex.get(ann); + Iterator it = classes.iterator(); + while (it.hasNext()) + { + writer.print(it.next()); + if (it.hasNext()) writer.print(", "); + } + writer.println(); + } + } + +} diff --git a/src/main/java/org/scannotation/classpath/ClasspathUrlFinder.java b/src/main/java/org/scannotation/classpath/ClasspathUrlFinder.java new file mode 100644 index 0000000..f56be22 --- /dev/null +++ b/src/main/java/org/scannotation/classpath/ClasspathUrlFinder.java @@ -0,0 +1,190 @@ +package org.scannotation.classpath; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.StringTokenizer; + +/** + * Various functions to locate URLs to scan + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class ClasspathUrlFinder +{ + + /** + * Find the classpath URLs for a specific classpath resource. The classpath URL is extracted + * from loader.getResources() using the baseResource. + * + * @param baseResource + * @return + */ + public static URL[] findResourceBases(String baseResource, ClassLoader loader) + { + ArrayList list = new ArrayList(); + try + { + Enumeration urls = loader.getResources(baseResource); + while (urls.hasMoreElements()) + { + URL url = urls.nextElement(); + list.add(findResourceBase(url, baseResource)); + } + } + catch (IOException e) + { + throw new RuntimeException(e); + } + return list.toArray(new URL[list.size()]); + } + + /** + * Find the classpath URLs for a specific classpath resource. The classpath URL is extracted + * from loader.getResources() using the baseResource. + * + * @param baseResource + * @return + */ + public static URL[] findResourceBases(String baseResource) + { + return findResourceBases(baseResource, Thread.currentThread().getContextClassLoader()); + } + + private static URL findResourceBase(URL url, String baseResource) + { + String urlString = url.toString(); + int idx = urlString.lastIndexOf(baseResource); + urlString = urlString.substring(0, idx); + URL deployUrl = null; + try + { + deployUrl = new URL(urlString); + } + catch (MalformedURLException e) + { + throw new RuntimeException(e); + } + return deployUrl; + } + + /** + * Find the classpath URL for a specific classpath resource. The classpath URL is extracted + * from Thread.currentThread().getContextClassLoader().getResource() using the baseResource. + * + * @param baseResource + * @return + */ + public static URL findResourceBase(String baseResource) + { + return findResourceBase(baseResource, Thread.currentThread().getContextClassLoader()); + } + + /** + * Find the classpath URL for a specific classpath resource. The classpath URL is extracted + * from loader.getResource() using the baseResource. + * + * @param baseResource + * @param loader + * @return + */ + public static URL findResourceBase(String baseResource, ClassLoader loader) + { + URL url = loader.getResource(baseResource); + return findResourceBase(url, baseResource); + } + + /** + * Find the classpath for the particular class + * + * @param clazz + * @return + */ + public static URL findClassBase(Class clazz) + { + String resource = clazz.getName().replace('.', '/') + ".class"; + return findResourceBase(resource, clazz.getClassLoader()); + } + + /** + * Uses the java.class.path system property to obtain a list of URLs that represent the CLASSPATH + * + * @return + */ + public static URL[] findClassPaths() + { + List list = new ArrayList(); + String classpath = System.getProperty("java.class.path"); + StringTokenizer tokenizer = new StringTokenizer(classpath, File.pathSeparator); + + while (tokenizer.hasMoreTokens()) + { + String path = tokenizer.nextToken(); + File fp = new File(path); + if (!fp.exists()) throw new RuntimeException("File in java.class.path does not exist: " + fp); + try + { + list.add(fp.toURL()); + } + catch (MalformedURLException e) + { + throw new RuntimeException(e); + } + } + return list.toArray(new URL[list.size()]); + } + + /** + * Uses the java.class.path system property to obtain a list of URLs that represent the CLASSPATH + *

+ * paths is used as a filter to only include paths that have the specific relative file within it + * + * @param paths comma list of files that should exist in a particular path + * @return + */ + public static URL[] findClassPaths(String... paths) + { + ArrayList list = new ArrayList(); + + String classpath = System.getProperty("java.class.path"); + StringTokenizer tokenizer = new StringTokenizer(classpath, File.pathSeparator); + for (int i = 0; i < paths.length; i++) + { + paths[i] = paths[i].trim(); + } + + while (tokenizer.hasMoreTokens()) + { + String path = tokenizer.nextToken().trim(); + boolean found = false; + for (String wantedPath : paths) + { + if (path.endsWith(File.separator + wantedPath)) + { + found = true; + break; + } + } + if (!found) continue; + File fp = new File(path); + if (!fp.exists()) throw new RuntimeException("File in java.class.path does not exists: " + fp); + try + { + list.add(fp.toURL()); + } + catch (MalformedURLException e) + { + throw new RuntimeException(e); + } + } + return list.toArray(new URL[list.size()]); + } + + +} + diff --git a/src/main/java/org/scannotation/classpath/DirectoryIteratorFactory.java b/src/main/java/org/scannotation/classpath/DirectoryIteratorFactory.java new file mode 100644 index 0000000..7239d1b --- /dev/null +++ b/src/main/java/org/scannotation/classpath/DirectoryIteratorFactory.java @@ -0,0 +1,13 @@ +package org.scannotation.classpath; + +import java.io.IOException; +import java.net.URL; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface DirectoryIteratorFactory +{ + StreamIterator create(URL url, Filter filter) throws IOException; +} diff --git a/src/main/java/org/scannotation/classpath/FileIterator.java b/src/main/java/org/scannotation/classpath/FileIterator.java new file mode 100644 index 0000000..b69709c --- /dev/null +++ b/src/main/java/org/scannotation/classpath/FileIterator.java @@ -0,0 +1,68 @@ +package org.scannotation.classpath; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class FileIterator implements StreamIterator +{ + private ArrayList files; + private int index = 0; + + public FileIterator(File file, Filter filter) + { + files = new ArrayList(); + try + { + create(files, file, filter); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + protected static void create(List list, File dir, Filter filter) throws Exception + { + File[] files = dir.listFiles(); + for (int i = 0; i < files.length; i++) + { + if (files[i].isDirectory()) + { + create(list, files[i], filter); + } + else + { + if (filter == null || filter.accepts(files[i].getAbsolutePath())) + { + list.add(files[i]); + } + } + } + } + + public InputStream next() + { + if (index >= files.size()) return null; + File fp = (File) files.get(index++); + try + { + return new FileInputStream(fp); + } + catch (FileNotFoundException e) + { + throw new RuntimeException(e); + } + } + + public void close() + { + } +} diff --git a/src/main/java/org/scannotation/classpath/FileProtocolIteratorFactory.java b/src/main/java/org/scannotation/classpath/FileProtocolIteratorFactory.java new file mode 100644 index 0000000..5d3b44c --- /dev/null +++ b/src/main/java/org/scannotation/classpath/FileProtocolIteratorFactory.java @@ -0,0 +1,26 @@ +package org.scannotation.classpath; + +import java.io.File; +import java.io.IOException; +import java.net.URL; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class FileProtocolIteratorFactory implements DirectoryIteratorFactory +{ + + public StreamIterator create(URL url, Filter filter) throws IOException + { + File f = new File(url.getPath()); + if (f.isDirectory()) + { + return new FileIterator(f, filter); + } + else + { + return new JarIterator(url.openStream(), filter); + } + } +} diff --git a/src/main/java/org/scannotation/classpath/Filter.java b/src/main/java/org/scannotation/classpath/Filter.java new file mode 100644 index 0000000..208b313 --- /dev/null +++ b/src/main/java/org/scannotation/classpath/Filter.java @@ -0,0 +1,10 @@ +package org.scannotation.classpath; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface Filter +{ + boolean accepts(String filename); +} diff --git a/src/main/java/org/scannotation/classpath/InputStreamWrapper.java b/src/main/java/org/scannotation/classpath/InputStreamWrapper.java new file mode 100644 index 0000000..c19d0e7 --- /dev/null +++ b/src/main/java/org/scannotation/classpath/InputStreamWrapper.java @@ -0,0 +1,72 @@ +package org.scannotation.classpath; + +import java.io.InputStream; +import java.io.IOException; + +/** + * Delegate to everything but close(). This object will not close the stream + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class InputStreamWrapper extends InputStream +{ + private InputStream delegate; + + public InputStreamWrapper(InputStream delegate) + { + this.delegate = delegate; + } + + public int read() + throws IOException + { + return delegate.read(); + } + + public int read(byte[] bytes) + throws IOException + { + return delegate.read(bytes); + } + + public int read(byte[] bytes, int i, int i1) + throws IOException + { + return delegate.read(bytes, i, i1); + } + + public long skip(long l) + throws IOException + { + return delegate.skip(l); + } + + public int available() + throws IOException + { + return delegate.available(); + } + + public void close() + throws IOException + { + // ignored + } + + public void mark(int i) + { + delegate.mark(i); + } + + public void reset() + throws IOException + { + delegate.reset(); + } + + public boolean markSupported() + { + return delegate.markSupported(); + } +} diff --git a/src/main/java/org/scannotation/classpath/IteratorFactory.java b/src/main/java/org/scannotation/classpath/IteratorFactory.java new file mode 100644 index 0000000..adbc091 --- /dev/null +++ b/src/main/java/org/scannotation/classpath/IteratorFactory.java @@ -0,0 +1,43 @@ +package org.scannotation.classpath; + +import java.io.IOException; +import java.net.URL; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class IteratorFactory +{ + private static final ConcurrentHashMap registry = new ConcurrentHashMap(); + + static + { + registry.put("file", new FileProtocolIteratorFactory()); + } + + + public static StreamIterator create(URL url, Filter filter) throws IOException + { + String urlString = url.toString(); + if (urlString.endsWith("!/")) + { + urlString = urlString.substring(4); + urlString = urlString.substring(0, urlString.length() - 2); + url = new URL(urlString); + } + + + if (!urlString.endsWith("/")) + { + return new JarIterator(url.openStream(), filter); + } + else + { + DirectoryIteratorFactory factory = registry.get(url.getProtocol()); + if (factory == null) throw new IOException("Unable to scan directory of protocol: " + url.getProtocol()); + return factory.create(url, filter); + } + } +} diff --git a/src/main/java/org/scannotation/classpath/JarIterator.java b/src/main/java/org/scannotation/classpath/JarIterator.java new file mode 100644 index 0000000..dd4cf35 --- /dev/null +++ b/src/main/java/org/scannotation/classpath/JarIterator.java @@ -0,0 +1,77 @@ +package org.scannotation.classpath; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class JarIterator implements StreamIterator +{ + JarInputStream jar; + JarEntry next; + Filter filter; + boolean initial = true; + boolean closed = false; + + public JarIterator(File file, Filter filter) throws IOException + { + this(new FileInputStream(file), filter); + } + + + public JarIterator(InputStream is, Filter filter) throws IOException + { + this.filter = filter; + jar = new JarInputStream(is); + } + + private void setNext() + { + initial = true; + try + { + if (next != null) jar.closeEntry(); + next = null; + do + { + next = jar.getNextJarEntry(); + } while (next != null && (next.isDirectory() || (filter == null || !filter.accepts(next.getName())))); + if (next == null) + { + close(); + } + } + catch (IOException e) + { + throw new RuntimeException("failed to browse jar", e); + } + } + + public InputStream next() + { + if (closed || (next == null && !initial)) return null; + setNext(); + if (next == null) return null; + return new InputStreamWrapper(jar); + } + + public void close() + { + try + { + closed = true; + jar.close(); + } + catch (IOException ignored) + { + + } + + } +} diff --git a/src/main/java/org/scannotation/classpath/StreamIterator.java b/src/main/java/org/scannotation/classpath/StreamIterator.java new file mode 100644 index 0000000..410df8f --- /dev/null +++ b/src/main/java/org/scannotation/classpath/StreamIterator.java @@ -0,0 +1,25 @@ +package org.scannotation.classpath; + +import java.io.InputStream; + +/** + * Simpler iterator than java.util.iterator. Things like JarInputStream does not allow you to implement hasNext() + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface StreamIterator +{ + /** + * User is resposible for closing the InputStream returned + * + * @return null if no more streams left to iterate on + */ + InputStream next(); + + /** + * Cleanup any open resources of the iterator + * + */ + void close(); +} diff --git a/src/main/java/org/scannotation/classpath/WarUrlFinder.java b/src/main/java/org/scannotation/classpath/WarUrlFinder.java new file mode 100644 index 0000000..73d3372 --- /dev/null +++ b/src/main/java/org/scannotation/classpath/WarUrlFinder.java @@ -0,0 +1,74 @@ +package org.scannotation.classpath; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class WarUrlFinder +{ + public static URL[] findWebInfLibClasspaths(ServletContextEvent servletContextEvent) + { + ServletContext servletContext = servletContextEvent.getServletContext(); + return findWebInfLibClasspaths(servletContext); + } + + public static URL[] findWebInfLibClasspaths(ServletContext servletContext) + { + ArrayList list = new ArrayList(); + Set libJars = servletContext.getResourcePaths("/WEB-INF/lib"); + for (Object jar : libJars) + { + try + { + list.add(servletContext.getResource((String) jar)); + } + catch (MalformedURLException e) + { + throw new RuntimeException(e); + } + } + return list.toArray(new URL[list.size()]); + } + + public static URL findWebInfClassesPath(ServletContextEvent servletContextEvent) + { + ServletContext servletContext = servletContextEvent.getServletContext(); + return findWebInfClassesPath(servletContext); + } + + /** + * Find the URL pointing to "/WEB-INF/classes" This method may not work in conjunction with IteratorFactory + * if your servlet container does not extract the /WEB-INF/classes into a real file-based directory + * + * @param servletContext + * @return + */ + public static URL findWebInfClassesPath(ServletContext servletContext) + { + Set libJars = servletContext.getResourcePaths("/WEB-INF/classes"); + for (Object jar : libJars) + { + try + { + URL url = servletContext.getResource((String) jar); + String urlString = url.toString(); + int index = urlString.lastIndexOf("/WEB-INF/classes/"); + urlString = urlString.substring(0, index + "/WEB-INF/classes/".length()); + return new URL(urlString); + } + catch (MalformedURLException e) + { + throw new RuntimeException(e); + } + } + return null; + + } +} diff --git a/src/test/java/org/scannotation/test/ClassWithFieldAnnotation.java b/src/test/java/org/scannotation/test/ClassWithFieldAnnotation.java new file mode 100644 index 0000000..ba97384 --- /dev/null +++ b/src/test/java/org/scannotation/test/ClassWithFieldAnnotation.java @@ -0,0 +1,10 @@ +package org.scannotation.test; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class ClassWithFieldAnnotation +{ + private @SimpleAnnotation int field; +} diff --git a/src/test/java/org/scannotation/test/CrossRef.java b/src/test/java/org/scannotation/test/CrossRef.java new file mode 100644 index 0000000..07e6a02 --- /dev/null +++ b/src/test/java/org/scannotation/test/CrossRef.java @@ -0,0 +1,12 @@ +package org.scannotation.test; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class CrossRef implements InterfaceWithParameterAnnotations +{ + public void junk(int param) + { + } +} diff --git a/src/test/java/org/scannotation/test/InterfaceWithParameterAnnotations.java b/src/test/java/org/scannotation/test/InterfaceWithParameterAnnotations.java new file mode 100644 index 0000000..ae77276 --- /dev/null +++ b/src/test/java/org/scannotation/test/InterfaceWithParameterAnnotations.java @@ -0,0 +1,10 @@ +package org.scannotation.test; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface InterfaceWithParameterAnnotations +{ + public void junk(@SimpleAnnotation int param); +} diff --git a/src/test/java/org/scannotation/test/SimpleAnnotation.java b/src/test/java/org/scannotation/test/SimpleAnnotation.java new file mode 100644 index 0000000..6b2af2d --- /dev/null +++ b/src/test/java/org/scannotation/test/SimpleAnnotation.java @@ -0,0 +1,15 @@ +package org.scannotation.test; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface SimpleAnnotation +{ +} diff --git a/src/test/java/org/scannotation/test/TestSmoke.java b/src/test/java/org/scannotation/test/TestSmoke.java new file mode 100644 index 0000000..8f914aa --- /dev/null +++ b/src/test/java/org/scannotation/test/TestSmoke.java @@ -0,0 +1,227 @@ +package org.scannotation.test; + +import com.titan.domain.Address; +import org.junit.Assert; +import org.junit.Test; +import org.scannotation.AnnotationDB; +import org.scannotation.classpath.ClasspathUrlFinder; + +import java.io.IOException; +import java.net.URL; +import java.util.Map; +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class TestSmoke +{ + + @Test + public void testFindResourceBase() throws Exception + { + URL url = ClasspathUrlFinder.findResourceBase("com/titan/domain/Address.class"); + Assert.assertNotNull(url); + verify(url); + } + + @Test + public void testFindResourceBases() throws Exception + { + URL[] urls = ClasspathUrlFinder.findResourceBases("com/titan/domain/Address.class"); + Assert.assertNotNull(urls); + verify(urls); + } + + @Test + public void testFindClasspaths() throws Exception + { + Assert.assertNotNull(System.getProperty("java.class.path")); + if (System.getProperty("java.class.path").indexOf("titan-cruise-1.0.jar") == -1) + { + System.err.println("WARNING!!!!!!!! CANNOT TEST testFindClasspaths(): This is a Maven2 and Surefire problem in that it doesn't set java.class.path correctly. I run this test within the IDE"); + } + + URL[] urls = ClasspathUrlFinder.findClassPaths("titan-cruise-1.0.jar"); + Assert.assertNotNull(urls); + verify(urls); + } + + + @Test + public void testFindClasspaths2() throws Exception + { + Assert.assertNotNull(System.getProperty("java.class.path")); + if (System.getProperty("java.class.path").indexOf("titan-cruise-1.0.jar") == -1) + { + System.err.println("WARNING!!!!!!! CANNOT TEST testFindClasspaths2(): This is a Maven2 and Surefire problem in that it doesn't set java.class.path correctly. I run this test within the IDE"); + } + + URL[] urls = ClasspathUrlFinder.findClassPaths(); + Assert.assertNotNull(urls); + AnnotationDB db = verify(urls); + + Map> annotationIndex = db.getAnnotationIndex(); + Set tests = annotationIndex.get("org.junit.Test"); + Assert.assertTrue(tests.contains(TestSmoke.class.getName())); + + } + + @Test + public void testFieldParameter() throws Exception + { + URL url = ClasspathUrlFinder.findClassBase(TestSmoke.class); + AnnotationDB db = new AnnotationDB(); + db.scanArchives(url); + + Map> annotationIndex = db.getAnnotationIndex(); + Set simpleClasses = annotationIndex.get(SimpleAnnotation.class.getName()); + Assert.assertTrue(simpleClasses.contains(ClassWithFieldAnnotation.class.getName())); + Assert.assertTrue(simpleClasses.contains(InterfaceWithParameterAnnotations.class.getName())); + + Set simpleAnnotations = db.getClassIndex().get(ClassWithFieldAnnotation.class.getName()); + Assert.assertTrue(simpleAnnotations.contains(SimpleAnnotation.class.getName())); + simpleAnnotations = db.getClassIndex().get(InterfaceWithParameterAnnotations.class.getName()); + Assert.assertTrue(simpleAnnotations.contains(SimpleAnnotation.class.getName())); + + + + } + + @Test + public void testCrossRef() throws Exception + { + URL url = ClasspathUrlFinder.findClassBase(TestSmoke.class); + AnnotationDB db = new AnnotationDB(); + db.scanArchives(url); + db.crossReferenceImplementedInterfaces(); + + Map> annotationIndex = db.getAnnotationIndex(); + Set simpleClasses = annotationIndex.get(SimpleAnnotation.class.getName()); + Assert.assertTrue(simpleClasses.contains(CrossRef.class.getName())); + + Set simpleAnnotations = db.getClassIndex().get(CrossRef.class.getName()); + Assert.assertTrue(simpleAnnotations.contains(SimpleAnnotation.class.getName())); + } + + + @Test + public void testByClass() throws Exception + { + URL url = ClasspathUrlFinder.findClassBase(Address.class); + Assert.assertNotNull(url); + verify(url); + } + + + private AnnotationDB verify(URL... urls) + throws IOException + { + AnnotationDB db = new AnnotationDB(); + db.scanArchives(urls); + + Map> annotationIndex = db.getAnnotationIndex(); + { + Set entities = annotationIndex.get("javax.persistence.Entity"); + Assert.assertNotNull(entities); + + Assert.assertTrue(entities.contains("com.titan.domain.Address")); + Assert.assertTrue(entities.contains("com.titan.domain.Cabin")); + Assert.assertTrue(entities.contains("com.titan.domain.CreditCard")); + Assert.assertTrue(entities.contains("com.titan.domain.CreditCompany")); + Assert.assertTrue(entities.contains("com.titan.domain.Cruise")); + Assert.assertTrue(entities.contains("com.titan.domain.Customer")); + Assert.assertTrue(entities.contains("com.titan.domain.Phone")); + Assert.assertTrue(entities.contains("com.titan.domain.Reservation")); + Assert.assertTrue(entities.contains("com.titan.domain.Ship")); + } + + { + Set entities = annotationIndex.get("javax.persistence.GeneratedValue"); + Assert.assertNotNull(entities); + + Assert.assertTrue(entities.contains("com.titan.domain.Address")); + Assert.assertTrue(entities.contains("com.titan.domain.Cabin")); + Assert.assertTrue(entities.contains("com.titan.domain.CreditCard")); + Assert.assertTrue(entities.contains("com.titan.domain.CreditCompany")); + Assert.assertTrue(entities.contains("com.titan.domain.Cruise")); + Assert.assertTrue(entities.contains("com.titan.domain.Customer")); + Assert.assertTrue(entities.contains("com.titan.domain.Phone")); + Assert.assertTrue(entities.contains("com.titan.domain.Reservation")); + Assert.assertTrue(entities.contains("com.titan.domain.Ship")); + } + + { + Set entities = annotationIndex.get("javax.persistence.Id"); + Assert.assertNotNull(entities); + + Assert.assertTrue(entities.contains("com.titan.domain.Address")); + Assert.assertTrue(entities.contains("com.titan.domain.Cabin")); + Assert.assertTrue(entities.contains("com.titan.domain.CreditCard")); + Assert.assertTrue(entities.contains("com.titan.domain.CreditCompany")); + Assert.assertTrue(entities.contains("com.titan.domain.Cruise")); + Assert.assertTrue(entities.contains("com.titan.domain.Customer")); + Assert.assertTrue(entities.contains("com.titan.domain.Phone")); + Assert.assertTrue(entities.contains("com.titan.domain.Reservation")); + Assert.assertTrue(entities.contains("com.titan.domain.Ship")); + } + + Map> classIndex = db.getClassIndex(); + Set annotations = classIndex.get("com.titan.domain.Address"); + Assert.assertNotNull(annotations); + Assert.assertTrue(annotations.contains("javax.persistence.Entity")); + Assert.assertTrue(annotations.contains("javax.persistence.Id")); + Assert.assertTrue(annotations.contains("javax.persistence.GeneratedValue")); + + annotations = classIndex.get("com.titan.domain.Cabin"); + Assert.assertNotNull(annotations); + Assert.assertTrue(annotations.contains("javax.persistence.Entity")); + Assert.assertTrue(annotations.contains("javax.persistence.Id")); + Assert.assertTrue(annotations.contains("javax.persistence.GeneratedValue")); + + annotations = classIndex.get("com.titan.domain.CreditCard"); + Assert.assertNotNull(annotations); + Assert.assertTrue(annotations.contains("javax.persistence.Entity")); + Assert.assertTrue(annotations.contains("javax.persistence.Id")); + Assert.assertTrue(annotations.contains("javax.persistence.GeneratedValue")); + + annotations = classIndex.get("com.titan.domain.CreditCompany"); + Assert.assertNotNull(annotations); + Assert.assertTrue(annotations.contains("javax.persistence.Entity")); + Assert.assertTrue(annotations.contains("javax.persistence.Id")); + Assert.assertTrue(annotations.contains("javax.persistence.GeneratedValue")); + + annotations = classIndex.get("com.titan.domain.Cruise"); + Assert.assertNotNull(annotations); + Assert.assertTrue(annotations.contains("javax.persistence.Entity")); + Assert.assertTrue(annotations.contains("javax.persistence.Id")); + Assert.assertTrue(annotations.contains("javax.persistence.GeneratedValue")); + + annotations = classIndex.get("com.titan.domain.Customer"); + Assert.assertNotNull(annotations); + Assert.assertTrue(annotations.contains("javax.persistence.Entity")); + Assert.assertTrue(annotations.contains("javax.persistence.Id")); + Assert.assertTrue(annotations.contains("javax.persistence.GeneratedValue")); + + annotations = classIndex.get("com.titan.domain.Phone"); + Assert.assertNotNull(annotations); + Assert.assertTrue(annotations.contains("javax.persistence.Entity")); + Assert.assertTrue(annotations.contains("javax.persistence.Id")); + Assert.assertTrue(annotations.contains("javax.persistence.GeneratedValue")); + + annotations = classIndex.get("com.titan.domain.Reservation"); + Assert.assertNotNull(annotations); + Assert.assertTrue(annotations.contains("javax.persistence.Entity")); + Assert.assertTrue(annotations.contains("javax.persistence.Id")); + Assert.assertTrue(annotations.contains("javax.persistence.GeneratedValue")); + + annotations = classIndex.get("com.titan.domain.Ship"); + Assert.assertNotNull(annotations); + Assert.assertTrue(annotations.contains("javax.persistence.Entity")); + Assert.assertTrue(annotations.contains("javax.persistence.Id")); + Assert.assertTrue(annotations.contains("javax.persistence.GeneratedValue")); + + return db; + } +} -- 2.20.1