/*
* Copyright (C) 2011 Rhegium Team
*
* 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.rhegium.internal.modules;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import org.aspectj.weaver.loadtime.WeavingURLClassLoader;
import org.rhegium.api.modules.IllegalCyclicDepedency;
import org.rhegium.internal.utils.ReflectionUtils;
import org.rhegium.internal.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PluginClassLoader extends WeavingURLClassLoader {
private static final Logger LOG = LoggerFactory.getLogger(PluginClassLoader.class);
private final ReentrantLock lock = new ReentrantLock();
private final Set<PluginClassLoader> buddyClassLoaders = new HashSet<PluginClassLoader>();
private Collection<String> exports = null;
private String pluginName;
public PluginClassLoader(final URL[] urls, final ClassLoader parent) throws IOException {
super(urls, parent);
if (LOG.isDebugEnabled()) {
LOG.debug(StringUtils.join(" ", "Adding classloader: PluginClassLoader(",
ReflectionUtils.buildClassLoaderHierachy(this), ")"));
}
}
public boolean addBuddyClassLoader(final PluginClassLoader buddyClassLoader) {
try {
lock.lock();
buddyClassLoader.checkCyclicDependency(this);
return buddyClassLoaders.add(buddyClassLoader);
}
finally {
lock.unlock();
}
}
public boolean removeBuddyClassLoader(final PluginClassLoader buddyClassLoader) {
try {
lock.lock();
return buddyClassLoaders.remove(buddyClassLoader);
}
finally {
lock.unlock();
}
}
@Override
public synchronized Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException {
if (LOG.isTraceEnabled()) {
LOG.trace(StringUtils.join(" ", "Searching class ", name, "..."));
}
try {
final Class<?> clazz = super.loadClass(name, resolve);
if (clazz != null) {
if (LOG.isTraceEnabled()) {
LOG.trace(StringUtils.join(" ", "Found class ", name, " in parent classLoader."));
}
return clazz;
}
}
catch (final ReflectiveOperationException e) {
try {
lock.lock();
if (LOG.isTraceEnabled()) {
LOG.trace(StringUtils.join(" ", "Available buddyClassLoaders for ", pluginName, ": ",
buddyClassLoadersToString()));
}
for (final PluginClassLoader buddyClassLoader : buddyClassLoaders) {
final Collection<String> buddyExports = buddyClassLoader.exports;
if (buddyExports != null) {
if (LOG.isTraceEnabled()) {
LOG.trace(StringUtils.join(" ", "Searching class ", name, " in buddyClassLoader '",
buddyClassLoader.pluginName, "'..."));
}
final Iterator<String> iterator = buddyExports.iterator();
while (iterator.hasNext()) {
final String export = iterator.next();
// If export is a package
if (export.endsWith("*")) {
// If requested class is not in exported package
// move on to next possible package
if (!name.startsWith(export.substring(0, export.length() - 2))) {
continue;
}
}
else {
// If export is a single class the canonical
// name must completely match!
if (!name.equals(export)) {
continue;
}
}
try {
if (LOG.isTraceEnabled()) {
LOG.trace(StringUtils.join(" ", "Trying to load class ", name,
" in buddyClassLoader '", buddyClassLoader.pluginName, "'..."));
}
final Class<?> buddyClass = buddyClassLoader.loadClass(name);
if (buddyClass != null) {
return buddyClass;
}
}
catch (final ReflectiveOperationException ex) {
// ignore and try next buddyClassLoader
}
}
}
}
}
finally {
lock.unlock();
}
}
throw new ClassNotFoundException(StringUtils.join(" ", "Class ", name,
" could not be found on plugin classpath for plugin '", pluginName, "'"));
}
void setExports(final Collection<String> exports) {
if (exports != null) {
this.exports = Collections.unmodifiableList(new ArrayList<String>(exports));
}
}
void setPluginName(final String pluginName) {
this.pluginName = pluginName;
}
private void checkCyclicDependency(final PluginClassLoader possibleCyclicClassLoader) {
if (buddyClassLoaders.contains(possibleCyclicClassLoader)) {
throw new IllegalCyclicDepedency(possibleCyclicClassLoader.pluginName, pluginName);
}
}
private String buddyClassLoadersToString() {
final StringBuilder sb = new StringBuilder("[");
boolean first = true;
for (final PluginClassLoader bcl : buddyClassLoaders) {
if (first) {
first = false;
}
else {
sb.append(", ");
}
sb.append(bcl.pluginName);
}
return sb.append("]").toString();
}
@Override
protected Class<?> findClass(final String name) throws ClassNotFoundException {
// System.err.println("? PluginClassLoader.findClass(" + name + ")");
try {
final byte[] bytes = getBytes(name);
if (bytes != null) {
return defineClass(name, bytes);
}
else {
throw new ClassNotFoundException(name);
}
}
catch (final IOException ex) {
throw new ClassNotFoundException(name);
}
}
private Class<?> defineClass(final String name, final byte[] bytes) throws IOException {
final String packageName = getPackageName(name);
if (packageName != null) {
final Package pakkage = getPackage(packageName);
if (pakkage == null) {
definePackage(packageName, null, null, null, null, null, null, null);
}
}
return defineClass(name, bytes);
}
private String getPackageName(final String className) {
final int offset = className.lastIndexOf('.');
return (offset == -1) ? null : className.substring(0, offset);
}
}