package com.mastfrog.acteur.annotations;
import com.google.inject.Module;
import com.mastfrog.acteur.Page;
import com.mastfrog.util.Exceptions;
import com.mastfrog.util.Streams;
import com.mastfrog.util.collections.CollectionUtils;
import com.mastfrog.util.collections.Converter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*
* @author Tim Boudreau
*/
public final class HttpCallRegistryLoader implements Iterable<Class<? extends Page>> {
private final Class<?> type;
private final List<Entry> entries = new LinkedList<>();
public HttpCallRegistryLoader(Class<?> type) {
// Get the type so we have the classloader
this.type = type;
}
public Set<Class<?>> implicitBindings() {
Set<Class<?>> types = new LinkedHashSet<>();
for (Entry e : entries()) {
types.addAll(e.bindings);
}
// Load META-INF/http/numble.list - technically we should have
// pluggable loaders in ServiceLoader for this, but this will do for now
ClassLoader cl = type.getClassLoader();
try {
for (URL url : CollectionUtils.toIterable(cl.getResources("META-INF/http/numble.list"))) {
try (final InputStream in = url.openStream()) {
String[] lines = Streams.readString(in, "UTF-8").split("\n");
for (String line : lines) {
// CRLF issues if build was done on Windows
// gives us class names ending in a \r
line = line.trim();
if (line.isEmpty() || line.startsWith("#")) {
continue;
}
// types.add(Class.forName(line));
System.err.println("Numble load '" + line + "'");
types.add(cl.loadClass(line));
}
} catch (ClassNotFoundException ex) {
return Exceptions.chuck(ex);
}
}
} catch (IOException ex) {
return Exceptions.chuck(ex);
}
return types;
}
@SuppressWarnings("unchecked")
public Set<Class<? extends Module>> modules() throws IOException, ClassNotFoundException {
Set<Class<? extends Module>> types = new HashSet();
ClassLoader cl = type.getClassLoader();
for (URL url : CollectionUtils.toIterable(cl.getResources(GuiceModule.META_INF_PATH))) {
try (final InputStream in = url.openStream()) {
// Split into lines
String[] lines = Streams.readString(in, "UTF-8").split("\n");
for (String line : lines) {
line = line.trim();
// Skip comments and blanks - these could be
// generated manually
if (line.isEmpty() || line.charAt(0) == '#') {
continue;
}
Class<?> moduleType = Class.forName(line);
if (!Module.class.isAssignableFrom(moduleType)) {
throw new ClassCastException("Not a subclass of " + Module.class.getName() + ": " + line);
}
types.add((Class<? extends Module>) moduleType);
}
}
}
return types;
}
private synchronized List<Entry> entries() {
if (entries.isEmpty()) {
try {
ClassLoader cl = type.getClassLoader();
// Lines are in the form fqn:order
Pattern classAndOrder = Pattern.compile("(.*?):(-?\\d+)");
Pattern classAndOrderWithImplicitBindings = Pattern.compile("(.*?):(-?\\d+)\\{(.*)\\}$");
// We are keeping track of both classpath order and ordering
// attributes!
int ix = 0;
// Look up all META-INF/http/pages.list files on the classpath
for (URL url : CollectionUtils.toIterable(cl.getResources(HttpCall.META_INF_PATH))) {
try (final InputStream in = url.openStream()) {
// Split into lines
String[] lines = Streams.readString(in, "UTF-8").split("\n");
for (String line : lines) {
// Skip comments and blanks - these could be
// generated manually
if (line.isEmpty() || line.charAt(0) == '#') {
continue;
}
Matcher m = classAndOrderWithImplicitBindings.matcher(line);
if (m.find()) {
String className = m.group(1);
int order = Integer.parseInt(m.group(2));
String bindings = m.group(3);
entries.add(new Entry(ix, className, order, url, bindings));
} else {
m = classAndOrder.matcher(line);
if (m.find()) {
String className = m.group(1);
int order = Integer.parseInt(m.group(2));
entries.add(new Entry(ix, className, order, url));
}
}
}
ix++;
}
}
} catch (Exception ex) {
Exceptions.chuck(ex);
}
// Sort the entries
Collections.sort(entries);
}
return entries;
}
@Override
public Iterator<Class<? extends Page>> iterator() {
// Convert its iterator to an Iterator<Class<? extends Page>>
return CollectionUtils.convertedIterator(new EntryConverter(), entries().iterator());
}
private static class Entry implements Comparable<Entry> {
private final int order;
private final int classpathOrder;
private final Class<? extends Page> type;
private final Set<Class<?>> bindings = new LinkedHashSet<>();
@SuppressWarnings(value = "unchecked")
Entry(int classpathOrder, String className, int order, URL url) throws ClassNotFoundException {
this.order = order;
this.classpathOrder = classpathOrder;
// Load the class (and fail early)
Class<?> type = Class.forName(className);
if (!Page.class.isAssignableFrom(type)) {
throw new ClassCastException(type.getName() + " is not a subtype of " + Page.class.getName() + " in " + url);
}
this.type = (Class<? extends Page>) type;
}
Entry(int classpathOrder, String className, int order, URL url, String bindings) throws ClassNotFoundException {
this(classpathOrder, className, order, url);
for (String type : bindings.split(",")) {
type = type.trim();
this.bindings.add(Class.forName(type));
}
}
@Override
public int compareTo(Entry o) {
Integer mine = classpathOrder;
Integer theirs = o.classpathOrder;
if (mine.equals(theirs)) {
mine = order;
theirs = o.order;
}
return mine.compareTo(theirs);
}
public String toString() {
return type.getSimpleName();
}
}
static class EntryConverter implements Converter<Class<? extends Page>, Entry> {
@Override
public Class<? extends Page> convert(Entry r) {
return r.type;
}
@Override
public Entry unconvert(Class<? extends Page> t) {
throw new UnsupportedOperationException();
}
}
}