org.eiichiro.reverb.lang.ClassResolver Maven / Gradle / Ivy
/*
* Copyright (C) 2012 Eiichiro Uchiumi. All Rights Reserved.
*
* 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 org.eiichiro.reverb.lang;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.jar.Attributes.Name;
/**
* {@code ClassResolver} is a base class for the component that resolves classes
* matches to the specified condition from its own search path.
* {@code ClassResolver} can resolve classes by the following conditions:
*
* - By name
* - By superclass
* - By interface
* - By annotation
* - By {@code Matcher<T>}
*
* Type parameter T
is the type of class representation. So the sub
* class must override {@code #load(String, InputStream)} method to load a class
* from the specified {@code InputStream} as its own type of class
* representation and the methods described above according to the type of class
* representation. By default, {@link JLCClassResolver} is provided, which
* represents the loaded class as {@code java.lang.Class}.
*
* @author Eiichiro Uchiumi
*/
public abstract class ClassResolver {
private Iterable urls = new ArrayList();
/**
* {@code Matcher} indicates whether the specified class matches to some
* condition or not.
*
* @author Eiichiro Uchiumi
*/
public static interface Matcher {
/**
* Indicates whether the specified class matches to some condition or
* not.
*
* @param T The type of class representation.
* @param clazz The class to be tested.
* @return true
If the specified class matches to some
* condition.
*/
public boolean matches(T clazz);
}
/**
* Constructs a new {@code ClassResolver} instance with the
* {@code ClassLoader}'s search paths in the current thread context.
*/
public ClassResolver() {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if (!(classLoader instanceof URLClassLoader)) {
throw new IllegalArgumentException(
"'classLoader' must be an instance of URLClassLoader");
}
this.urls = Arrays.asList(((URLClassLoader) classLoader).getURLs());
}
/**
* Constructs a new {@code ClassResolver} instance with the specified search
* paths.
*
* @param urls The search path.
*/
public ClassResolver(Iterable urls) {
this.urls = urls;
}
/**
* Loads the class of the specified name from the specified
* {@code InputStream} and returns loaded class representation as the type
* of T
.
*
* @param clazz The name of the class to be loaded.
* @param stream {@code InputStream} to load a class file.
* @return The loaded class representation as T
.
*/
protected abstract T load(String clazz, InputStream stream);
/**
* Resolves the {@code Class}es that contains the specified name.
*
* @param name The part of the class name.
* @return {@code Class}es that contains the specified name.
* @throws IOException If any I/O access fails while traversing the search
* path.
*/
public abstract Set resolveByName(final String name) throws IOException;
/**
* Resolves the {@code Class}es that inherits the specified superclass.
*
* @param superclass The superclass being inherited.
* @return {@code Class}es that inherits the specified superclass.
* @throws IOException If any I/O access fails while traversing the search
* path.
*/
public abstract Set resolveBySuperclass(final Class> superclass) throws IOException;
/**
* Resolves the {@code Class}es that implements the specified interface.
*
* @param interfaceClass The interface being implemented.
* @return {@code Class}es that implements the specified interface.
* @throws IOException If any I/O access fails while traversing the search
* path.
*/
public abstract Set resolveByInterface(final Class> interfaceClass) throws IOException;
/**
* Resolves the {@code Class}es that is annotated by the specified annotation.
*
* @param annotation The annotation the class being annotated.
* @return {@code Class}es that is annotated by the specified annotation.
* @throws IOException If any I/O access fails while traversing the search
* path.
*/
public abstract Set resolveByAnnotation(final Class extends Annotation> annotation) throws IOException;
/**
* Resolves the {@code Class}es that matches to the specified {@code Matcher}.
*
* @param matcher {@code Matcher}.
* @return {@code Class}es that matches to the specified {@code Matcher}.
* @throws IOException If any I/O access fails while traversing the search
* path.
*/
public Set resolve(Matcher matcher) throws IOException {
Set classes = new HashSet();
for (URL url : urls) {
if (url.toString().endsWith(".jar")) {
// System.out.println(url);
JarFile jarFile = new JarFile(URLDecoder.decode(url.getPath(), "UTF-8"));
Manifest manifest = jarFile.getManifest();
if (manifest != null) {
// System.out.println(manifest);
Attributes mainAttributes = manifest.getMainAttributes();
if (mainAttributes != null) {
// System.out.println(mainAttributes);
String classpath = mainAttributes.getValue(Name.CLASS_PATH);
if (classpath != null) {
// System.out.println(classpath);
StringTokenizer stringTokenizer = new StringTokenizer(classpath);
while (stringTokenizer.hasMoreTokens()) {
String token = stringTokenizer.nextToken();
URL entry = new URL(url, token);
if (entry.toString().endsWith("/")) {
// System.out.println(entry);
classes.addAll(getMatchedClasses(matcher, new File(URLDecoder.decode(entry.getPath(), "UTF-8"))));
} else {
// System.out.println(entry);
classes.addAll(getMatchedClasses(matcher, new JarFile(URLDecoder.decode(entry.getPath(), "UTF-8"))));
}
}
}
}
}
classes.addAll(getMatchedClasses(matcher, jarFile));
} else {
File base = new File(URLDecoder.decode(url.getPath(), "UTF-8"));
classes.addAll(getMatchedClasses(matcher, base));
}
}
return classes;
}
private Set getMatchedClasses(Matcher matcher, File base) throws IOException {
List files = getClasses(base);
Set classes = new HashSet();
for (File file : files) {
String path = file.getPath();
// System.out.println(path);
InputStream stream = null;
try {
stream = new FileInputStream(file);
T clazz = load(path.substring(base.getPath().length() + 1, path.length() - 6).replace(File.separatorChar, '.'), stream);
if (clazz != null && matcher.matches(clazz)) {
classes.add(clazz);
}
} finally {
if (stream != null) {
stream.close();
}
}
}
return classes;
}
private List getClasses(File base) {
// System.out.println(directory);
List classes = new ArrayList();
if (base.isDirectory()) {
File[] files = base.listFiles();
for (File file : files) {
if (file.isDirectory()) {
classes.addAll(getClasses(file));
} else {
if (file.getPath().endsWith(".class")) {
classes.add(file);
}
}
}
} else {
if (base.getPath().endsWith(".class")) {
classes.add(base);
}
}
return classes;
}
private Set getMatchedClasses(Matcher matcher, JarFile jarFile) throws IOException {
Enumeration entries = jarFile.entries();
Set classes = new HashSet();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName();
// System.out.println(name);
if (name.endsWith(".class")) {
InputStream stream = null;
try {
stream = jarFile.getInputStream(entry);
T clazz = load(name.substring(0, name.length() - 6).replace('/', '.'), stream);
if (clazz != null && matcher.matches(clazz)) {
classes.add(clazz);
}
} finally {
if (stream != null) {
stream.close();
}
}
}
}
return classes;
}
/**
* Returns the search paths to be traversed.
*
* @return The search paths to be traversed.
*/
public Iterable urls() {
return urls;
}
}