/*
* This file is part of aion-emu <aion-emu.com>.
*
* aion-emu is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* aion-emu is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with aion-emu. If not, see <http://www.gnu.org/licenses/>.
*/
package com.aionemu.commons.scripting;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandlerFactory;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.apache.log4j.Logger;
import com.aionemu.commons.scripting.url.VirtualClassURLStreamHandler;
/**
*
* Abstract class loader that should be extended by child classloaders. If needed, this class should wrap another
* classloader.
*
* @author SoulKeeper
*/
public abstract class ScriptClassLoader extends URLClassLoader
{
/**
* Logger
*/
private static final Logger log = Logger.getLogger(ScriptClassLoader.class);
/**
* URL Stream handler to allow valid url generation by {@link #getResource(String)}
*/
private final VirtualClassURLStreamHandler urlStreamHandler = new VirtualClassURLStreamHandler(this);
/**
* Classes that were loaded from libraries. They are no parsed for any annotations, but they are needed by
* JavaCompiler to perform valid compilation
*/
protected Set<String> libraryClasses = new HashSet<String>();
/**
* Just for compatibility with {@link URLClassLoader}
*
* @param urls
* list of urls
* @param parent
* parent classloader
*/
public ScriptClassLoader(URL[] urls, ClassLoader parent)
{
super(urls, parent);
}
/**
* Just for compatibility with {@link URLClassLoader}
*
* @param urls
* list of urls
*/
public ScriptClassLoader(URL[] urls)
{
super(urls);
}
/**
* Just for compatibility with {@link URLClassLoader}
*
* @param urls
* list of urls
* @param parent
* parent classloader
* @param factory
* {@link java.net.URLStreamHandlerFactory}
*/
public ScriptClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory)
{
super(urls, parent, factory);
}
/**
* Adds library to this classloader, it shuould be jar file
*
* @param file
* jar file
* @throws IOException
* if can't add library
*/
public void addLibrary(File file) throws IOException
{
URL fileURL = file.toURI().toURL();
super.addURL(fileURL);
JarFile jarFile = new JarFile(file);
Enumeration<JarEntry> entries = jarFile.entries();
while(entries.hasMoreElements())
{
JarEntry entry = entries.nextElement();
String name = entry.getName();
if(name.endsWith(".class"))
{
name = name.substring(0, name.length() - 6);
name = name.replace('/', '.');
libraryClasses.add(name);
}
}
jarFile.close();
}
/**
* {@inheritDoc}
*/
@Override
public URL getResource(String name)
{
if(!name.endsWith(".class"))
{
return super.getResource(name);
}
else
{
String newName = name.substring(0, name.length() - 6);
newName = newName.replace('/', '.');
if(getCompiledClasses().contains(newName))
{
try
{
return new URL(null, VirtualClassURLStreamHandler.HANDLER_PROTOCOL + newName, urlStreamHandler);
}
catch(MalformedURLException e)
{
log.error("Can't create url for compiled class", e);
}
}
}
return super.getResource(name);
}
/**
* Loads class from library, parent or compiled
*
* @param name
* class to load
* @return loaded class
* @throws ClassNotFoundException
* if class not found
*/
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException
{
boolean isCompiled = getCompiledClasses().contains(name);
if(!isCompiled)
{
return super.loadClass(name, true);
}
Class<?> c = getDefinedClass(name);
if(c == null)
{
byte[] b = getByteCode(name);
c = super.defineClass(name, b, 0, b.length);
setDefinedClass(name, c);
}
return c;
}
/**
* Returns unmodifiable set of class names that were loaded from libraries
*
* @return unmodifiable set of class names that were loaded from libraries
*/
public Set<String> getLibraryClasses()
{
return Collections.unmodifiableSet(libraryClasses);
}
/**
* Retuns unmodifiable set of class names that were compiled
*
* @return unmodifiable set of class names that were compiled
*/
public abstract Set<String> getCompiledClasses();
/**
* Returns bytecode for given className. Array is copy of actual bytecode, so modifications will not harm.
*
* @param className
* class name
* @return bytecode
*/
public abstract byte[] getByteCode(String className);
/**
* Returns cached class instance for give name or null if is not cached yet
*
* @param name
* class name
* @return cached class instance or null
*/
public abstract Class<?> getDefinedClass(String name);
/**
* Sets defined class into cache
*
* @param name
* class name
* @param clazz
* class object
* @throws IllegalArgumentException
* if class was not loaded by this class loader
*/
public abstract void setDefinedClass(String name, Class<?> clazz) throws IllegalArgumentException;
}