/*
* The contents of this file are subject to the terms of the Common Development
* and Distribution License (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at http://www.sun.com/cddl/cddl.html
* or http://www.netbeans.org/cddl.txt.
*
* When distributing Covered Code, include this CDDL Header Notice in each file
* and include the License file at http://www.netbeans.org/cddl.txt.
* If applicable, add the following below the CDDL Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* The Original Software is SezPoz. The Initial Developer of the Original
* Software is Sun Microsystems, Inc. Portions Copyright 2006-2007 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package net.java.sezpoz;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.java.sezpoz.impl.SerAnnotatedElement;
import org.sonatype.guice.bean.reflect.ClassSpace;
import org.sonatype.guice.bean.reflect.URLClassSpace;
/**
* Represents an index of a single annotation.
* Indices are <em>not</em> automatically cached
* (but reading them should be pretty cheap anyway).
* @param T the type of annotation to load
* @param I the type of instance which will be created
*/
public final class SpaceIndex<T extends Annotation, I> implements Iterable<SpaceIndexItem<T,I>> {
private static final Logger LOGGER = Logger.getLogger(SpaceIndex.class.getName());
/**
* Load an index for a given annotation type.
* @param annotation the type of annotation to find
* @param instanceType the type of instance to be created (use {@link Void} if all instances will be null)
* @param space a class space in which to find the index and any annotated classes
* @param globalIndex search the entire classloader hierarchy?
* @return an index of all elements known to be annotated with it
* @throws IllegalArgumentException if the annotation type is not marked with {@link Indexable}
* or the instance type is not equal to or a supertype of the annotation's actual {@link Indexable#type}
*/
public static <T extends Annotation,I> SpaceIndex<T,I> load(Class<T> annotation, Class<I> instanceType, ClassSpace space, boolean globalIndex) throws IllegalArgumentException {
return new SpaceIndex<T,I>(annotation, instanceType, space, globalIndex);
}
private final boolean globalIndex;
private final Class<T> annotation;
private final Class<I> instanceType;
private final ClassSpace space;
private SpaceIndex(Class<T> annotation, Class<I> instance, ClassSpace space, boolean globalIndex) {
this.globalIndex = globalIndex;
this.annotation = annotation;
this.instanceType = instance;
this.space = space;
}
/**
* Find all items in the index.
* Calls to iterator methods may fail with {@link IndexError}
* as the index is parsed lazily.
* @return an iterator over items in the index
*/
public Iterator<SpaceIndexItem<T,I>> iterator() {
return new LazyIndexIterator();
}
/**
* Lazy iterator. Opens and parses annotation streams only on demand.
*/
private final class LazyIndexIterator implements Iterator<SpaceIndexItem<T,I>> {
private Enumeration<URL> resources;
private ObjectInputStream ois;
private URL resource;
private SpaceIndexItem<T,I> next;
private boolean end;
private final Set<String> loadedMembers = new HashSet<String>();
public LazyIndexIterator() {
if (LOGGER.isLoggable(Level.FINE)) {
String urls;
if (space instanceof URLClassSpace) {
urls = " " + Arrays.toString(((URLClassSpace) space).getURLs());
} else {
urls = "";
}
LOGGER.log(Level.FINE, "Searching for indices of {0} in {1}{2}", new Object[] {annotation, space, urls});
}
}
private void peek() throws IndexError {
try {
for (int iteration = 0; true; iteration++) {
if (iteration == 9999) {
LOGGER.log(Level.WARNING, "possible endless loop getting index for {0} from {1}", new Object[] {annotation, space});
}
if (next != null || end) {
return;
}
if (ois == null) {
if (resources == null) {
if (globalIndex) {
resources = space.getResources("META-INF/annotations/" + annotation.getName());
} else {
resources = space.findEntries("META-INF/annotations/", annotation.getName(), false);
}
}
if (!resources.hasMoreElements()) {
// Exhausted all streams.
end = true;
return;
}
resource = resources.nextElement();
LOGGER.log(Level.FINE, "Loading index from {0}", resource);
ois = new ObjectInputStream(resource.openStream());
}
SerAnnotatedElement el = (SerAnnotatedElement) ois.readObject();
if (el == null) {
// Skip to next stream.
ois.close();
ois = null;
continue;
}
String memberName = el.isMethod ? el.className + '#' + el.memberName + "()" :
el.memberName != null ? el.className + '#' + el.memberName :
el.className;
if (!loadedMembers.add(memberName)) {
// Already encountered this element, so skip it.
LOGGER.log(Level.FINE, "Already loaded index item {0}", el);
continue;
}
// XXX JRE #6865375 would make loader param accurate for duplicated modules
next = new SpaceIndexItem<T,I>(el, annotation, instanceType, space, resource);
break;
}
} catch (Exception x) {
if (ois != null) {
try {
ois.close();
} catch (IOException x2) {
LOGGER.log(Level.WARNING, null, x2);
}
}
throw new IndexError(x);
}
}
public boolean hasNext() {
peek();
return !end;
}
public SpaceIndexItem<T,I> next() {
peek();
if (!end) {
assert next != null;
SpaceIndexItem<T,I> _next = next;
next = null;
return _next;
} else {
throw new NoSuchElementException();
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
}