public class SandboxContext implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = -5253800931844658443L;

	private transient Logger logger = Logger.getLogger(getClass().getName());
	
	/**
	 * 
	 * @author Arno Mittelbach
	 */
	public enum FileAccess{ READ, WRITE, DELETE }
	
	/**
	 * 
	 * @author Arno Mittelbach
	 */
	public enum AccessType{ PERMIT, DENY }
	
	/**
	 * 
	 * @author Arno Mittelbach
	 */
	public enum RuntimeMode{ CPU_TIME, ABSOLUTE_TIME}
	
	/**
	 * 
	 * @author Arno Mittelbach
	 */
	public enum Mode{ NORMAL, PREFIX }
	
	private String name = "";
	
	private boolean debug = false;
	
	/* service */
	private boolean runRemote = false;
	private boolean runInThread = false;
	private int maximumStackDepth = -1;
	private long maximumRunTime = -1;
	private TimeUnit maximumRunTimeUnit = TimeUnit.MILLISECONDS;
	private RuntimeMode maximumRuntimeMode  = RuntimeMode.ABSOLUTE_TIME;
	
	
	/* sandbox loader */
	private Collection<URL> jarsForApplicationLoader = new HashSet<URL>();
	private Collection<String> classPrefixesForApplicationLoader = new HashSet<String>();
	private Collection<String> classesForApplicationLoader = new HashSet<String>();
	
	private Collection<String> classPrefixesForSandboxLoader = new HashSet<String>();
	private Collection<String> classesForSandboxLoader = new HashSet<String>();
	
	private Map<URL, SandboxContext> subLoaderContextByJar = new HashMap<URL, SandboxContext>();
	private Map<String, SandboxContext> subLoaderContextByClass = new HashMap<String, SandboxContext>();
	private Map<String, SandboxContext> subLoaderContextByClassPrefix = new HashMap<String, SandboxContext>();
	
	private String codesource;
	
	private SandboxLoaderEnhancer loaderEnhancer;
	
	private boolean removeFinalizers = true;
	
	/* permissions */
	private Map<String, Collection<SecurityPermission>> permissionWhitelist = new HashMap<String, Collection<SecurityPermission>>();
	private Map<String, Collection<SecurityPermission>> permissionBlacklist = new HashMap<String, Collection<SecurityPermission>>();
	
	private Collection<String> classPrefixWhitelist = new HashSet<String>();
	private Collection<String> classPrefixBlacklist = new HashSet<String>();
	private Collection<String> classBlacklist = new HashSet<String>();
	private Collection<String> classWhitelist = new HashSet<String>();
	private Collection<ClassPermission> complexClassWhitelist = new HashSet<ClassPermission>();
	
	private Collection<String> packagePrefixWhitelist = new HashSet<String>();
	private Collection<String> packagePrefixBlacklist = new HashSet<String>();
	private Collection<String> packageWhitelist = new HashSet<String>();
	private Collection<String> packageBlacklist = new HashSet<String>();
	private Collection<PackagePermission> complexPackageWhitelist = new HashSet<PackagePermission>();
	
	private Collection<FilePermission> fileReadPermissions = new HashSet<FilePermission>();
	private Collection<FilePermission> fileReadDenials = new HashSet<FilePermission>();

	private Collection<FilePermission> fileWritePermissions = new HashSet<FilePermission>();
	private Collection<FilePermission> fileWriteDenials = new HashSet<FilePermission>();
	
	private Collection<FilePermission> fileDeletePermissions = new HashSet<FilePermission>();
	private Collection<FilePermission> fileDeleteDenials = new HashSet<FilePermission>();
	
	private Collection<URL> whitelistedJars = new HashSet<URL>();
	
	private boolean passAll = false;
	private boolean bypassClassAccessChecks = false;
	private boolean bypassPermissionAccessChecks = false;
	private boolean bypassPackageAccessChecks = true;
	
	/**
	 * Initializes a new context.
	 */
	public SandboxContext(){
		this(true);
	}
	
	/**
	 * 
	 * @param initDefault
	 */
	public SandboxContext(boolean initDefault){
		if(initDefault){
			addClasspath();
			
			/* blacklist important objects */
			addSecurityPermission(AccessType.DENY, new SecurityPermission("java.lang.reflect.SuppressAccessCheckPermission", "net.datenwerke.sandbox.SandboxSecurityManager"));
			addSecurityPermission(AccessType.DENY, new SecurityPermission("java.lang.reflect.AccessDeclaredMemberPermission", "net.datenwerke.sandbox.SandboxSecurityManager"));
			
			addSecurityPermission(AccessType.DENY, new SecurityPermission("java.lang.reflect.SuppressAccessCheckPermission", "net.datenwerke.sandbox.SandboxLoader"));
			addSecurityPermission(AccessType.DENY, new SecurityPermission("java.lang.reflect.AccessDeclaredMemberPermission", "net.datenwerke.sandbox.SandboxLoader"));
		}
	}
	
	/**
	 * All classes loadable from the jar can be accessed from within the sandbox.
	 * 
	 * @param url URL pointing to a jar.
	 */
	public void addJarToWhitelist(URL url){
		whitelistedJars.add(url);
	}
	
	/**
	 * 
	 * @see #addJarToWhitelist(URL)
	 * @return
	 */
	public Collection<URL> getWhitelistedJars() {
		return whitelistedJars;
	}
	
	/**
	 * 
	 * @see #checkPackageAccess(Class, Class[])
	 * @see SandboxSecurityManager#checkPackageAccess(String)
	 * @param type
	 * @param pkg
	 */
	public void addPackagePermission(AccessType type, String pkg){
		addPackagePermission(type, Mode.PREFIX, pkg);
	}
	
	/**
	 * 
	 * @see #checkPackageAccess(Class, Class[])
	 * @see SandboxSecurityManager#checkPackageAccess(String)
	 * @param type
	 * @param mode
	 * @param pkg
	 */
	public void addPackagePermission(AccessType type, Mode mode, String pkg){
		switch(type){
		case PERMIT:
			if(mode == Mode.PREFIX || pkg.endsWith("."))
				packagePrefixWhitelist.add(pkg);
			else
				packageWhitelist.add(pkg);
			break;
		case DENY: 
			if(mode == Mode.PREFIX || pkg.endsWith("."))
				packagePrefixBlacklist.add(pkg);
			else
				packageBlacklist.add(pkg);
			break;
		}
	}
	
	/**
	 * @see #checkPackageAccess(Class, Class[])
	 * @see SandboxSecurityManager#checkPackageAccess(String)
	 * @param wpkg
	 */
	public void addPackagePermission(PackagePermission wpkg) {
		setBypassPackageAccessChecks(false);
		complexPackageWhitelist.add(wpkg);
	}
	
	/**
	 * 
	 * @see #checkClassAccess(Class, Class[])
	 * @see SandboxSecurityManager#checkClassAccess(String)
	 * @param type
	 * @param classes
	 */
	public void addClassPermission(AccessType type, String... classes){
		for(String clazz : classes)
			addClassPermission(type, Mode.NORMAL, clazz);
	}
	
	/**
	 * 
	 * @see #checkClassAccess(Class, Class[])
	 * @see SandboxSecurityManager#checkClassAccess(String)
	 * @param type
	 * @param clazz
	 */
	public void addClassPermission(AccessType type, String clazz){
		addClassPermission(type, Mode.NORMAL, clazz);
	}
	
	/**
	 * 
	 * @see #checkClassAccess(Class, Class[])
	 * @see SandboxSecurityManager#checkClassAccess(String)
	 * @param type
	 * @param mode
	 * @param clazz
	 */
	public void addClassPermission(AccessType type, Mode mode,  String clazz){
		switch(type){
		case PERMIT:
			if(mode == Mode.PREFIX || clazz.endsWith("."))
				classPrefixWhitelist.add(clazz);
			else
				classWhitelist.add(clazz);
			break;
		case DENY:
			if(mode == Mode.PREFIX || clazz.endsWith("."))
				classPrefixBlacklist.add(clazz);
			else
				classBlacklist.add(clazz);
			break;
		}
	}
	
	/**
	 * 
	 * @see #checkClassAccess(Class, Class[])
	 * @see SandboxSecurityManager#checkClassAccess(String)
	 * @param wclass
	 */
	public void addClassPermission(ClassPermission wclass) {
		complexClassWhitelist.add(wclass);
	}
	
	/**
	 * 
	 * @see SandboxLoader#doGetSubLoaderByClassContext(String)
	 * @see SandboxLoader#getSubloaderByName(String)
	 * @param clazz
	 * @param context
	 */
	public void addSubloaderContext(String clazz, SandboxContext context){
		addSubloaderContext(clazz, Mode.NORMAL, context);
	}
	
	/**
	 * 
	 * @see SandboxLoader#doGetSubLoaderByClassContext(String)
	 * @see SandboxLoader#getSubloaderByName(String)
	 * @param url
	 * @param context
	 */
	public void addSubloaderContext(URL url, SandboxContext context){
		subLoaderContextByJar.put(url, context);
	}
	
	/**
	 * 
	 * @see SandboxLoader#doGetSubLoaderByClassContext(String)
	 * @see SandboxLoader#getSubloaderByName(String)
	 * @param clazz
	 * @param mode
	 * @param context
	 */
	public void addSubloaderContext(String clazz, Mode mode, SandboxContext context) {
		if(mode == Mode.PREFIX || clazz.endsWith("."))
			subLoaderContextByClassPrefix.put(clazz, context);
		else
			subLoaderContextByClass.put(clazz, context);
	}
	
	/**
	 * 
	 * @return
	 */
	public Map<URL, SandboxContext> getSubLoaderContextByJar() {
		return subLoaderContextByJar;
	}
	
	/**
	 * 
	 * @return
	 */
	public Map<String, SandboxContext> getSubLoaderContextByClassMap() {
		return subLoaderContextByClass;
	}

	/**
	 * 
	 * @return
	 */
	public Map<String, SandboxContext> getSubLoaderContextByClassPrefixMap() {
		return subLoaderContextByClassPrefix;
	}
	
	/**
	 * 
	 * @param loaderEnhancer
	 */
	public void setLoaderEnhancer(SandboxLoaderEnhancer loaderEnhancer) {
		this.loaderEnhancer = loaderEnhancer;
	}
	
	/**
	 * 
	 * @return
	 */
	public SandboxLoaderEnhancer getLoaderEnhancer() {
		return loaderEnhancer;
	}
	
	/**
	 * 
	 * @param clazz
	 */
	public void addClassForApplicationLoader(String clazz){
		addClassForApplicationLoader(clazz, Mode.NORMAL);
	}
	
	/**
	 * 
	 * @param classes
	 */
	public void addClassForApplicationLoader(String... classes){
		for(String clazz : classes)
			addClassForApplicationLoader(clazz, Mode.NORMAL);
	}
	
	/**
	 * 
	 * @param clazz
	 * @param mode
	 */
	public void addClassForApplicationLoader(String clazz, Mode mode){
		if(mode == Mode.PREFIX || clazz.endsWith("."))
			classPrefixesForApplicationLoader.add(clazz);
		else
			classesForApplicationLoader.add(clazz);
	}
	
	/**
	 * 
	 * @param url
	 */
	public void addJarForApplicationLoader(URL url){
		jarsForApplicationLoader.add(url);
	}
	
	/**
	 * 
	 * @return
	 */
	public Collection<URL> getJarsForApplicationLoader() {
		return jarsForApplicationLoader;
	}
	
	/**
	 * 
	 * @return
	 */
	public Collection<String> getClassesForApplicationLoader() {
		return classesForApplicationLoader;
	}

	/**
	 * 
	 * @return
	 */
	public Collection<String> getClassPrefixesForApplicationLoader() {
		return classPrefixesForApplicationLoader;
	}
	
	/**
	 * 
	 * @param clazz
	 */
	public void addClassForSandboxLoader(String clazz){
		addClassForSandboxLoader(clazz, Mode.NORMAL);
	}
	
	/**
	 * 
	 * @param classes
	 */
	public void addClassForSandboxLoader(String... classes){
		for(String clazz : classes)
			addClassForSandboxLoader(clazz, Mode.NORMAL);
	}
	
	/**
	 * 
	 * @param clazz
	 * @param mode
	 */
	public void addClassForSandboxLoader(String clazz, Mode mode){
		if(mode == Mode.PREFIX || clazz.endsWith("."))
			classPrefixesForSandboxLoader.add(clazz);
		else
			classesForSandboxLoader.add(clazz);
	}
	
	/**
	 * 
	 * @return
	 */
	public Collection<String> getClassesForSandboxLoader() {
		return classesForSandboxLoader;
	}
	
	/**
	 * 
	 * @return
	 */
	public Collection<String> getClassPrefixesForSandboxLoader() {
		return classPrefixesForSandboxLoader;
	}
	
	
	/**
	 * Returns true if debug mode is enabled for this sandbox context.
	 * @return
	 */
	public boolean isDebug() {
		return debug;
	}
	
	/**
	 * Enables/Disables debug mode for this context
	 * 
	 * @param debug
	 */
	public void setDebug(boolean debug) {
		this.debug = debug;
	}
	
	/**
	 * Returns true if sandboxed code for this context is to be run on a remote agent.
	 * 
	 * @see #setRunRemote(boolean)
	 * @return
	 */
	public boolean isRunRemote() {
		return runRemote;
	}
	
	/**
	 * Enables/Disables that sandboxes are to be run on remote agents.
	 * @param runRemote
	 */
	public void setRunRemote(boolean runRemote) {
		this.runRemote = runRemote;
	}
	
	/**
	 * Returns true if sandboxed code is to be executed in its own thread.
	 * 
	 * @see #setRunInThread(boolean)
	 * @return
	 */
	public boolean isRunInThread() {
		return runInThread;
	}

	/**
	 * Enables/Disables execution of sandboxed code in an own thread.
	 * 
	 * @param runInThread
	 */
	public void setRunInThread(boolean runInThread) {
		this.runInThread = runInThread;
	}

	/**
	 * Returns the restriction on the number of items on the execution stack
	 * during sandboxed code execution.
	 * 
	 * @see #setMaximumStackDepth(int)
	 * @return
	 */
	public int getMaximumStackDepth() {
		return maximumStackDepth;
	}

	/**
	 * Sets a maximum stack depth for sandboxed code.
	 * 
	 * For this restriction to take effect the sandboxed code must be executed
	 * in its own thread. Note that the execution stack is probed only at
	 * discrete time steps and thus execution stacks that violate the
	 * here specified maximum might not be detected.
	 * 
	 * Set to -1 to disable execution stack probing.
	 * 
	 * @see #setRunInThread(boolean)
	 * @see SandboxService#setMonitorDaemonCheckInterval(long)
	 * @return
	 */
	public void setMaximumStackDepth(int maximumStackDepth) {
		this.maximumStackDepth = maximumStackDepth;
	}

	/**
	 * Returns the maximum runtime of sandboxed code.
	 * 
	 * @see #setMaximumRunTime(long)
	 * @see #setMaximumRuntimeMode(RuntimeMode)
	 * @see #setMaximumRunTimeUnit(TimeUnit)
	 * @return
	 */
	public long getMaximumRunTime() {
		return maximumRunTime;
	}

	/**
	 * Sets the maximum runtime of sandboxed code.
	 * 
	 * @see #setMaximumRuntimeMode(RuntimeMode)
	 * @see #setMaximumRunTimeUnit(TimeUnit)
	 * @param maximumRunTime
	 */
	public void setMaximumRunTime(long maximumRunTime) {
		this.maximumRunTime = maximumRunTime;
	}
	
	/**
	 * Sets the maximum runtime of sandboxed code
	 * 
	 * @see #setMaximumRuntimeMode(RuntimeMode)
	 * @see #setMaximumRunTimeUnit(TimeUnit)
	 * @param maximumRunTime
	 * @param unit
	 * @param mode
	 */
	public void setMaximumRunTime(long maximumRunTime, TimeUnit unit, RuntimeMode mode) {
		this.maximumRunTime = maximumRunTime;
		this.maximumRunTimeUnit = unit;
		this.maximumRuntimeMode = mode;
	}
	
	/**
	 * Returns the {@link TimeUnit} for the maximum runtime restriction.
	 *  
	 * @see #setMaximumRunTimeUnit(TimeUnit)
	 * @return
	 */
	public TimeUnit getMaximumRunTimeUnit() {
		return maximumRunTimeUnit;
	}
	
	/**
	 * Sets the {@link TimeUnit} for the runtime restriction of sandboxed code.
	 * The default is TimeUnit.MILLISECONDS
	 * 
	 * @see #setMaximumRunTime(long)
	 * @param maximumRunTimeUnit
	 */
	public void setMaximumRunTimeUnit(TimeUnit maximumRunTimeUnit) {
		this.maximumRunTimeUnit = maximumRunTimeUnit;
	}
	
	/**
	 * Returns the mode of measuring the runtime of sandboxed code.
	 * 
	 * @see #setMaximumRuntimeMode(RuntimeMode)
	 * @return
	 */
	public RuntimeMode getMaximumRuntimeMode() {
		return maximumRuntimeMode;
	}
	
	/**
	 * Sets the mode of measuring the runtime of sandboxed code.
	 * The default is ABSOLUTE_TIME. 
	 * 
	 * CPU_TIME denotes the actual time the thread was using the CPU,
	 * while ABSOLUTE_TIME denotes the time from thread start to 
	 * the current moment in time.
	 * 
	 * @param maximumRuntimeMode
	 */
	public void setMaximumRuntimeMode(RuntimeMode maximumRuntimeMode) {
		this.maximumRuntimeMode = maximumRuntimeMode;
	}

	/**
	 * @see #setPassAll(boolean)
	 * @return True if all access is permitted. 
	 */
	public boolean isPassAll() {
		return passAll;
	}

	/**
	 * Tells the class to allow any request.
	 * This is similar to the {@link AllPermission} for the basic java
	 * {@link SecurityManager}.
	 * 
	 * @param passAll true to allow any request
	 */
	public void setPassAll(boolean passAll) {
		this.passAll = passAll;
	}
	
	/**
	 *
	 * @see #setName(String)
	 * @return The context's name
	 */
	public String getName() {
		return name;
	}
	
	/**
	 * Sets the name of the context. This name is for example used
	 * by the {@link SandboxLoader} to specifiy codesource (unless
	 * a specific codebase is set using {@link #setCodesource(String)}.
	 * 
	 * Also subloaders can be referred to by name.
	 * 
	 * @param name
	 */
	public void setName(String name) {
		this.name = name;
	}
	
	/**
	 * 
	 * @see #setCodesource(String)
	 * @return The codesource used by {@link SandboxLoader} when loading classes in this context.
	 */
	public String getCodesource() {
		return codesource;
	}
	
	/**
	 * Sets the codesource that is to be used by {@link SandboxLoader} when loading classes in this context.
	 * 
	 * @param codesource
	 */
	public void setCodesource(String codesource) {
		this.codesource = codesource;
	}
	
	/**
	 * 
	 * @see #setRemoveFinalizers(boolean)
	 * @return
	 */
	public boolean isRemoveFinalizers() {
		return removeFinalizers;
	}
	
	/**
	 * Enable/Disable whether the {@link SandboxLoader} is to remove finalizers from loaded classes.
	 * 
	 * Note that this only works for classes not in java.*.
	 * 
	 * Defaults to true
	 * 
	 * @param removeFinalizers
	 */
	public void setRemoveFinalizers(boolean removeFinalizers) {
		this.removeFinalizers = removeFinalizers;
	}

	/**
	 * 
	 * @param type
	 * @param perm
	 */
	public void addSecurityPermission(AccessType type, SecurityPermission perm){
		switch (type) {
		case PERMIT:
			if(! permissionWhitelist.containsKey(perm.getType()))
				permissionWhitelist.put(perm.getType(), new ArrayList<SecurityPermission>());
			permissionWhitelist.get(perm.getType()).add(perm);
			break;

		case DENY:
			if(! permissionBlacklist.containsKey(perm.getType()))
				permissionBlacklist.put(perm.getType(), new ArrayList<SecurityPermission>());
			permissionBlacklist.get(perm.getType()).add(perm);
			break;
		}
	}
	
	/**
	 * 
	 * @param fileAccess
	 * @param type
	 * @param permission
	 */
	public void addFilePermission(FileAccess fileAccess, AccessType type, FilePermission permission){
		switch(fileAccess){
		case READ:
			if(AccessType.PERMIT == type)
				fileReadPermissions.add(permission);
			else
				fileReadDenials.add(permission);
			break;
		case WRITE:
			if(AccessType.PERMIT == type)
				fileWritePermissions.add(permission);
			else
				fileWriteDenials.add(permission);
			break;
		case DELETE:
			if(AccessType.PERMIT == type)
				fileDeletePermissions.add(permission);
			else
				fileDeleteDenials.add(permission);
			break;
		}
	}

	/**
	 * Returns true if class access is permitted with this configuration.
	 * 
	 * @param type
	 * @param stack
	 * @return
	 */
	public boolean checkClassAccess(Class type, Class[] stack) {
		return checkClassAccess(type.getName(), stack);
	}
	
	/**
	 * Returns true if class access is permitted with this configuration.
	 * 
	 * @param name
	 * @param stack
	 * @return
	 */
	public boolean checkClassAccess(String name, Class[] stack) {
		if(passAll)
			return true;
		
		boolean found = false;
		for(String wlClass : classPrefixWhitelist){
			if(name.startsWith(wlClass)){
				found = true;
				break;
			}
		}
		found |= classWhitelist.contains(name); 
		
		if(! found){
			for(ClassPermission wclass : complexClassWhitelist){
				if(name.startsWith(wclass.getName()) && checkEntriesAgainstStack(wclass.getEntries(), stack)){
					found = true;
					break;
				}
			}
		}
		
		if(found){
			for(String blClass : classPrefixBlacklist)
				if(name.startsWith(blClass))
					return false;
			if(classBlacklist.contains(name))
				return false;
		}
		
		return found;
	}

	/**
	 * Returns true if pacakge access is permitted with this configuration.
	 * 
	 * @see #checkPackageAccess(String, Class[])
	 * 
	 * @param type
	 * @param stack
	 * @return
	 */
	public boolean checkPackageAccess(Class type, Class[] stack) {
		return checkPackageAccess(type.getName(), stack);
	}

	/**
	 * Returns true if pacakge access is permitted with this configuration.
	 * 
	 * Note that package access does not necessarily mean that classes in the package
	 * may be accessed. For this either class level checks need to be bypassed or
	 * the class must also be accessible.
	 * 
	 * 
	 * @see #checkClassAccess(String, Class[])
	 * @see #bypassClassAccessChecks
	 * 
	 * @param name
	 * @param stack
	 * @return
	 */
	public boolean checkPackageAccess(String name, Class[] stack) {
		if(passAll)
			return true;
		
		boolean found = false;
		for(String wlPkg : packagePrefixWhitelist){
			if(name.startsWith(wlPkg)){
				found = true;
				break;
			}
		}
		found |= packageWhitelist.contains(name); 
		
		if(! found){
			for(PackagePermission wpkg : complexPackageWhitelist){
				if(name.startsWith(wpkg.getName()) && checkEntriesAgainstStack(wpkg.getEntries(), stack)){
					found = true;
					break;
				}
			}
		}
		
		if(found){
			for(String blPkg : packagePrefixBlacklist)
				if(name.startsWith(blPkg))
					return false;
			if(packageBlacklist.contains(name))
				return false;
		}
		
		return found;
	}

	/**
	 * Returns true if the permission is granted by this configuration.
	 * 
	 * @param perm
	 * @param stack
	 * @return
	 */
	public boolean checkPermission(Permission perm, Class[] stack) {
		/* check blacklist */
		Collection<SecurityPermission> blacklistedPermissions = permissionBlacklist.get(perm.getClass().getName());
		if(null != blacklistedPermissions){
			for(SecurityPermission blacklistedPermission : blacklistedPermissions){
				if(permissionMatches(blacklistedPermission, perm, stack))
					return false;
			}
		}
		
		/* specialized checks */
		if(java.io.FilePermission.class.equals(perm.getClass())){
			if(SecurityConstants.FILE_READ_ACTION.equals(perm.getActions()) && null != perm.getName()) {
				return checkFileReadAction(perm.getName());
			} else if(SecurityConstants.FILE_WRITE_ACTION.equals(perm.getActions()) && null != perm.getName()) {
				return checkFileWriteAction(perm.getName());
			} else if(SecurityConstants.FILE_DELETE_ACTION.equals(perm.getActions()) && null != perm.getName()) {
				return checkFileDeleteAction(perm.getName());
			}
		}
		
		/* general checks */
		Collection<SecurityPermission> whitelistedPermissions = permissionWhitelist.get(perm.getClass().getName());
		if(null != whitelistedPermissions){
			for(SecurityPermission whitelistedPermission : whitelistedPermissions){
				if(permissionMatches(whitelistedPermission, perm, stack))
					return true;
			}
		}
		
		return false;
	}
	
	protected boolean permissionMatches(SecurityPermission permission,
			Permission toBeMatched, Class[] stack) {
		if(null != permission.getName() && null == toBeMatched.getName())
			return false;
		if(null != permission.getName() && ! permission.getName().equals(toBeMatched.getName()))
			return false;
		if(null != permission.getActions() && ! permission.getActions().equals(toBeMatched.getActions()))
			return false;
		Collection<StackEntry> entries = permission.getEntries();
		if(null != entries && ! checkEntriesAgainstStack(entries, stack))
			return false;

		return true;
	}

	protected boolean checkFileReadAction(String name) {
		return checkFileAction(name, fileReadPermissions, fileReadDenials);
	}
	
	protected boolean checkFileWriteAction(String name) {
		return checkFileAction(name, fileWritePermissions, fileWriteDenials);
	}
	
	protected boolean checkFileDeleteAction(String name) {
		return checkFileAction(name, fileDeletePermissions, fileDeleteDenials);
	}
	
	protected boolean checkFileAction(String name, Collection<FilePermission> permissions, Collection<FilePermission> prohibition){
		if(passAll)
			return true;
		
		for(FilePermission perm : prohibition)
			if(perm.testPermission(name))
				return false;
		
		for(FilePermission perm : permissions)
			if(perm.testPermission(name))
				return true;
		
		return false;
	}
	
	protected boolean checkEntriesAgainstStack(Collection<StackEntry> entries, Class[] stack) {
		for(StackEntry entry :entries){
			int pos = entry.getPos();
			if(pos >= 0){
				if(stack.length <= pos)
					return false;
				if((entry.isPrefix() && ! stack[pos].getName().startsWith(entry.getType())) || (! entry.isPrefix() && ! entry.getType().equals(stack[pos].getName())))
					return false;
			} else {
				boolean found = false;
				String name = entry.getType();
				for(Class c : stack){
					if( (entry.isPrefix() && c.getName().startsWith(name)) || (! entry.isPrefix() && c.getName().equals(name))){
						found = true;
						break;
					}
				}
				if(! found)
					return false;
			}
		}
		
		return true;
	}

	protected void debug(Level level, String msg) {
		if(null == logger)
			logger = Logger.getLogger(getClass().getName());
		logger.log(level, name + "(" + System.identityHashCode(this) +") : " + msg);
	}
	
	/**
	 * Called by the {@link SandboxSecurityManager} when a permission is checked and
	 * the context is in debug mode.
	 * 
	 * @see #setDebug(boolean)
	 * @param perm
	 */
	public void debugPermissionCheck(Permission perm) {
		debug(Level.INFO, "PermissionCheck: " + perm + "\n");
	}

	/**
	 * Called by the {@link SandboxSecurityManager} when a permission was denied and
	 * the context is in debug mode.
	 *  
	 * @see #setDebug(boolean)
	 * @param perm
	 * @param classes
	 */
	public void debugDeniedPermission(Permission perm, Class[] classes) {
		StringBuffer b = new StringBuffer();
		b.append("DENY: PermissionCheck: " + perm + "\n");
		appendClasses(b, classes);
		debug(Level.WARNING, b.toString());
	}

	protected void appendClasses(StringBuffer b, Class[] classes) {
		int i = 0;
		if(null != classes)
			for(Class c : classes)
				b.append("\t" + i++ + "\t: " + c + "\n");		
	}

	/**
	 * Called by the {@link SandboxSecurityManager} when package access is checked and
	 * the context is in debug mode.
	 * 
	 * @see #setDebug(boolean)
	 * @param pkg
	 */
	public void debugCheckPackageAccess(String pkg) {
		debug(Level.INFO, "PackageAccessCheck: " + pkg + "\n");		
	}
	
	/**
	 * Called by the {@link SandboxSecurityManager} when package access was denied and
	 * the context is in debug mode.
	 * 
	 * @see #setDebug(boolean)
	 * @param pkg
	 * @param classes
	 */
	public void debugDeniedPackageAccess(String pkg, Class[] classes) {
		StringBuffer b = new StringBuffer();
		b.append("DENY: PackageAccessCheck: " + pkg + "\n");
		appendClasses(b, classes);
		debug(Level.WARNING, b.toString());
	}

	/**
	 * Called by the {@link SandboxSecurityManager} when class access is checked and
	 * the context is in debug mode.
	 * 
	 * @see #setDebug(boolean)
	 * @param clazz
	 */
	public void debugCheckClassAccess(String clazz) {
		debug(Level.INFO, "ClassAccessCheck: " + clazz + "\n");
	}

	/**
	 * Called by the {@link SandboxSecurityManager} when class access was denied and
	 * the context is in debug mode.
	 * 
	 * @see #setDebug(boolean)
	 * @param clazz
	 * @param classes
	 */
	public void debugDeniedClassAccess(String clazz, Class[] classes) {
		StringBuffer b = new StringBuffer();
		b.append("DENY: ClassAccessCheck: " + clazz + "\n");
		appendClasses(b, classes);
		debug(Level.WARNING, b.toString());
	}
	
	/**
	 * Convenience method to allow read access to anything on the classpath (java.class.path).
	 */
	public void addClasspath() {
		for(String s : System.getProperty("java.class.path").split(":")){
			if(s.endsWith(".jar"))
				addFilePermission(FileAccess.READ, AccessType.PERMIT, new FileEqualsPermission(s));
			else if(s.endsWith(".class"))
				addFilePermission(FileAccess.READ, AccessType.PERMIT,new FileEqualsPermission(s));
			else
				addFilePermission(FileAccess.READ, AccessType.PERMIT,new FilePrefixPermission(s));
		}
	}
	
	/**
	 * Convenience method to allow read access to the home dir (java.home).
	 */
	public void addHome(){
		String home = System.getProperty("java.home");
		addFilePermission(FileAccess.READ, AccessType.PERMIT,new FilePrefixPermission(home));
	}

	/**
	 * Adds the {@link SandboxLoader}'s default Codesource prefix to be read accessible for
	 * sandboxed code.
	 * 
	 * @see SandboxLoader#DEFAULT_CODESOURCE_PREFIX
	 */
	public void addDefaultCodesourcePrefix(){
		addFilePermission(FileAccess.READ, AccessType.PERMIT,new FilePrefixPermission(SandboxLoader.DEFAULT_CODESOURCE_PREFIX));
	}

	
	/**
	 * Convenience method to allow read and write access to the tmpdir (java.io.tmpdir).
	 */
	public void addTempDir() {
		String dir = System.getProperty("java.io.tmpdir");
		addFilePermission(FileAccess.READ, AccessType.PERMIT,new FilePrefixPermission(dir));
		addFilePermission(FileAccess.WRITE, AccessType.PERMIT,new FilePrefixPermission(dir));
		addFilePermission(FileAccess.DELETE, AccessType.PERMIT,new FilePrefixPermission(dir));
	}

	/**
	 * Convenience method to allow read access to the user's home dir (user.dir).
	 */
	public void addWorkDir() {
		String dir = System.getProperty("user.dir");
		addFilePermission(FileAccess.READ, AccessType.PERMIT, new FilePrefixPermission(dir));
	}

	/**
	 * Returns true if {@link SecurityPermission} are not checked.
	 * 
	 * @see #setBypassPermissionAccessChecks(boolean)
	 * @return
	 */
	public boolean isBypassPermissionAccessChecks() {
		return bypassPermissionAccessChecks;
	}
	
	/**
	 * Enables/Disables the checking of {@link SecurityPermission}.
	 * Default is to check {@link SecurityPermission}s.
	 * 
	 * @param bypassPermissionAccess
	 */
	public void setBypassPermissionAccessChecks(boolean bypassPermissionAccess) {
		this.bypassPermissionAccessChecks = bypassPermissionAccess;
	}
	
	/**
	 * Returns true if checking of package access is disabled.
	 * 
	 * @see #setBypassPackageAccessChecks(boolean)
	 * @return
	 */
	public boolean isBypassPackageAccessChecks() {
		return bypassPackageAccessChecks;
	}
	
	/**
	 * Enables/Disables package access checks.
	 * Default is to not check package access.
	 * 
	 * @param bypassPackageAccess
	 */
	public void setBypassPackageAccessChecks(boolean bypassPackageAccess) {
		this.bypassPackageAccessChecks = bypassPackageAccess;
	}

	/**
	 * Returns true if class access checks are disabled.
	 * 
	 * @see #setBypassClassAccessChecks(boolean)
	 * @return
	 */
	public boolean isBypassClassAccessChecks() {
		return bypassClassAccessChecks;
	}
	
	/**
	 * Enables/Disables class access checks.
	 * Default is to check class access.
	 * 
	 * @param bypassClassAccess
	 */
	public void setBypassClassAccessChecks(boolean bypassClassAccess) {
		this.bypassClassAccessChecks = bypassClassAccess;
	}
	
	protected void mergeClassRestrictions(SandboxContext set){
		this.classBlacklist.addAll(set.classBlacklist);
		this.classPrefixBlacklist.addAll(set.classPrefixBlacklist);
		this.classWhitelist.addAll(set.classWhitelist);
		this.classPrefixWhitelist.addAll(set.classPrefixWhitelist);

		for(ClassPermission wp : set.complexClassWhitelist)
			this.complexClassWhitelist.add(wp.clone());
	}
	
	protected void mergePackageRestrictions(SandboxContext set){
		this.packageBlacklist.addAll(set.packageBlacklist);
		this.packagePrefixBlacklist.addAll(set.packagePrefixBlacklist);
		this.packageWhitelist.addAll(set.packageWhitelist);
		this.packagePrefixWhitelist.addAll(set.packagePrefixWhitelist);
	
		for(PackagePermission wp : set.complexPackageWhitelist)
			this.complexPackageWhitelist.add(wp.clone());
	}

	protected void mergePermissions(SandboxContext restrictSet) {
		for(String key : restrictSet.permissionWhitelist.keySet()){
			if(! permissionWhitelist.containsKey(key))
				permissionWhitelist.put(key, new ArrayList<SecurityPermission>());

			for(SecurityPermission perm : restrictSet.permissionWhitelist.get(key))
				permissionWhitelist.get(key).add(perm.clone());
		}

		for(String key : restrictSet.permissionBlacklist.keySet()){
			if(! permissionBlacklist.containsKey(key))
				permissionBlacklist.put(key, new ArrayList<SecurityPermission>());

			for(SecurityPermission perm : restrictSet.permissionBlacklist.get(key))
				permissionBlacklist.get(key).add(perm.clone());
		}
	}
	
	protected void mergeFilePermissions(SandboxContext restrictSet) {
		for(FilePermission perm : restrictSet.fileReadDenials)
			fileReadDenials.add(perm.clone());
		
		for(FilePermission perm : restrictSet.fileReadPermissions)
			fileReadPermissions.add(perm.clone());
		
		for(FilePermission perm : restrictSet.fileWriteDenials)
			fileWriteDenials.add(perm.clone());

		for(FilePermission perm : restrictSet.fileWritePermissions)
			fileWritePermissions.add(perm.clone());
		
		for(FilePermission perm : restrictSet.fileDeletePermissions)
			fileDeletePermissions.add(perm.clone());
		
		for(FilePermission perm : restrictSet.fileDeleteDenials)
			fileDeleteDenials.add(perm.clone());
	}
	
	/**
	 * Merges the configuration for the application loader.
	 * 
	 * @see #addClassForApplicationLoader(String)
	 * @param set
	 */
	protected void mergeApplicationLoaderConfiguration(SandboxContext context) throws MalformedURLException {
		this.classesForApplicationLoader.addAll(context.classesForApplicationLoader);
		this.classPrefixesForApplicationLoader.addAll(context.classPrefixesForApplicationLoader);
		
		for(URL url : context.jarsForApplicationLoader)
			this.jarsForApplicationLoader.add(new URL(url.toExternalForm()));
		
		this.classesForSandboxLoader.addAll(context.classesForSandboxLoader);
		this.classPrefixesForSandboxLoader.addAll(context.classPrefixesForSandboxLoader);
	}
	
	protected void mergeWhitelistedJars(SandboxContext context) throws MalformedURLException {
		for(URL url : context.whitelistedJars)
			this.whitelistedJars.add(new URL(url.toExternalForm()));
	}
	
	protected void mergeSubloaders(SandboxContext context){
		Map<SandboxContext,SandboxContext> contextCache = new IdentityHashMap<SandboxContext, SandboxContext>();
		
		for(String prefix : context.subLoaderContextByClassPrefix.keySet()){
			SandboxContext subcontext = context.subLoaderContextByClassPrefix.get(prefix);
			if(! contextCache.containsKey(subcontext))
				contextCache.put(subcontext, subcontext.clone());
			
			this.subLoaderContextByClassPrefix.put(prefix, contextCache.get(subcontext));
		}
		
		for(String clazz : context.subLoaderContextByClass.keySet()){
			SandboxContext subcontext = context.subLoaderContextByClass.get(clazz);
			if(! contextCache.containsKey(subcontext))
				contextCache.put(subcontext, subcontext.clone());
			
			this.subLoaderContextByClass.put(clazz, contextCache.get(subcontext));
		}
		
		for(URL url : context.subLoaderContextByJar.keySet()){
			SandboxContext subcontext = context.subLoaderContextByJar.get(url);
			if(! contextCache.containsKey(subcontext))
				contextCache.put(subcontext, subcontext.clone());
			
			this.subLoaderContextByJar.put(url, contextCache.get(subcontext));
		}
	}
	
	public void merge(SandboxContext context) {
		mergeClassRestrictions(context);
		mergePackageRestrictions(context);
		mergeFilePermissions(context);
		mergePermissions(context);
		try{
			mergeApplicationLoaderConfiguration(context);
			mergeWhitelistedJars(context);
		} catch(MalformedURLException e){
			throw new IllegalStateException(e);
		}
		
		debug = context.debug;
		passAll = context.passAll;
		name = context.name;
		codesource = context.codesource;
		bypassClassAccessChecks = context.bypassClassAccessChecks;
		bypassPackageAccessChecks = context.bypassPackageAccessChecks;
		bypassPermissionAccessChecks = context.bypassPermissionAccessChecks;
		
		removeFinalizers = context.removeFinalizers;
		loaderEnhancer = context.loaderEnhancer;
		
		runRemote = context.runRemote;
		runInThread = context.runInThread;
		maximumRunTime = context.maximumRunTime;
		maximumRunTimeUnit = context.maximumRunTimeUnit;
		maximumRuntimeMode = context.maximumRuntimeMode;
		maximumStackDepth = context.maximumStackDepth;
	}

	@Override
	public SandboxContext clone() {
		SandboxContext clone = new SandboxContext();
		
		clone.merge(this);
		
		return clone;
	}
	
} final public class SandboxLoader extends ClassLoader {
	
	public static final String DEFAULT_CODESOURCE_PREFIX = "/java-sandbox-default-codesource";

	private final Logger logger = Logger.getLogger(getClass().getName());
	
	private static HashSet<String> BYPASSCLASSES;
	static {
		BYPASSCLASSES = new HashSet<String>();

		BYPASSCLASSES.add("net.datenwerke.sandbox.SandboxLoader");
		BYPASSCLASSES.add("net.datenwerke.sandbox.SandboxServiceImpl");
			
		BYPASSCLASSES.add("net.datenwerke.sandbox.SandboxedCallResult");
		BYPASSCLASSES.add("net.datenwerke.sandbox.SandboxedCallResultImpl");
		BYPASSCLASSES.add("net.datenwerke.sandbox.SandboxService");
		BYPASSCLASSES.add("net.datenwerke.sandbox.SandboxContext");
		
		BYPASSCLASSES.add("net.datenwerke.sandbox.handlers.SandboxHandler");
		BYPASSCLASSES.add("net.datenwerke.sandbox.handlers.BadThreadKillHandler");
		BYPASSCLASSES.add("net.datenwerke.sandbox.handlers.ContextRegisteredHandler");
		
		BYPASSCLASSES.add("net.datenwerke.sandbox.permissions.FileEqualsPermission");
		BYPASSCLASSES.add("net.datenwerke.sandbox.permissions.FilePermission");
		BYPASSCLASSES.add("net.datenwerke.sandbox.permissions.FilePrefixPermission");
		BYPASSCLASSES.add("net.datenwerke.sandbox.permissions.FileRegexPermission");
		BYPASSCLASSES.add("net.datenwerke.sandbox.permissions.FileSuffixPermission");
		BYPASSCLASSES.add("net.datenwerke.sandbox.permissions.StackEntry");
		BYPASSCLASSES.add("net.datenwerke.sandbox.permissions.ClassPermission");
		BYPASSCLASSES.add("net.datenwerke.sandbox.permissions.PackagePermission");
		BYPASSCLASSES.add("net.datenwerke.sandbox.permissions.SecurityPermission");

		BYPASSCLASSES.add("net.datenwerke.sandbox.util.VariableAssignment");
	}
	
	private final SandboxSecurityManager securityManager;

	private Map<String, SandboxLoader> subLoaderCache = new HashMap<String, SandboxLoader>();
	private Map<String, SandboxLoader> subLoaderPrefixCache = new HashMap<String, SandboxLoader>();
	private Map<URLClassPath, SandboxLoader> subLoaderByJar  = new HashMap<URLClassPath, SandboxLoader>();
	
	private boolean debug = false;
	
	private SandboxContext context = new SandboxContext();

	private String name = "";
	
	private URLClassPath whitelistedUcp;
	private URLClassPath bypassUcp;
	
	private Collection<String> classesToLoadWithParent = new HashSet<String>();
	private Collection<String> classesByPrefixToLoadWithParent = new HashSet<String>();
	
	private Collection<String> classesToLoadDirectly = new HashSet<String>();
	private Collection<String> classesByPrefixToLoadDirectly = new HashSet<String>();
	
	private boolean hasSubloaders;

	private String codesource;

	private boolean removeFinalizers;
	
	private SandboxLoaderEnhancer enhancer;
	
	private ClassLoader parent;

	/**
	 * Instantiates a new SandboxLoader with the current ClassLoader as parent.
	 */
	public SandboxLoader() {
		this(SandboxService.class.getClassLoader());
	}

	/**
	 * Instantiates a new SandboxLoader with the given ClassLoader as parent.
	 * @param parent
	 */
	public SandboxLoader(ClassLoader parent) {
		this(parent, System.getSecurityManager());
	}
	
	/**
	 * Instantiates a new SandboxLoader with the given ClassLoader as parent and using the given security manager.
	 * @param parent
	 * @param securityManager
	 */
	public SandboxLoader(ClassLoader parent, SecurityManager securityManager) {
		super(parent);
		this.parent = parent;
		this.securityManager = (SandboxSecurityManager) securityManager;
	}
	
	/**
	 * Initializes this classloader with the config provided by the SandboxContext
	 * 
	 * @param context
	 */
	public void init(SandboxContext context) {
		securityManager.checkPermission(new SandboxRuntimePermission("initSandboxLoader"));
		
		/* name */
		this.name = context.getName();
		
		/* jars */
		if(null != context.getWhitelistedJars() && ! context.getWhitelistedJars().isEmpty())
			whitelistedUcp = new URLClassPath(context.getWhitelistedJars().toArray(new URL[]{}));
		else 
			whitelistedUcp = null;
		
		if(null != context.getJarsForApplicationLoader() && ! context.getJarsForApplicationLoader().isEmpty())
			bypassUcp = new URLClassPath(context.getJarsForApplicationLoader().toArray(new URL[]{}));
		else 
			bypassUcp = null;
		
		/* load configuration */
		classesToLoadWithParent = new HashSet<String>(context.getClassesForApplicationLoader());
		classesToLoadWithParent.addAll(BYPASSCLASSES);
		classesByPrefixToLoadWithParent = new HashSet<String>(context.getClassPrefixesForApplicationLoader());
		
		classesToLoadDirectly = new HashSet<String>(context.getClassesForSandboxLoader());
		classesByPrefixToLoadDirectly = new HashSet<String>(context.getClassPrefixesForSandboxLoader());
		
		/* subloaders */
		this.hasSubloaders = ! context.getSubLoaderContextByClassMap().isEmpty() || 
							 ! context.getSubLoaderContextByClassPrefixMap().isEmpty() ||
							 ! context.getSubLoaderContextByJar().isEmpty();
		if(hasSubloaders){
			IdentityHashMap<SandboxContext, SandboxLoader> loaderMap = new IdentityHashMap<SandboxContext, SandboxLoader>();
			
			for(Entry<String, SandboxContext> e :context.getSubLoaderContextByClassMap().entrySet())
				subLoaderCache.put(e.getKey(), initSubLoader(loaderMap, e.getValue()));
			for(Entry<String, SandboxContext> e : context.getSubLoaderContextByClassPrefixMap().entrySet())
				subLoaderPrefixCache.put(e.getKey(), initSubLoader(loaderMap, e.getValue()));
			for(Entry<URL, SandboxContext> e : context.getSubLoaderContextByJar().entrySet())
				subLoaderByJar.put(new URLClassPath(new URL[]{e.getKey()}), initSubLoader(loaderMap, e.getValue()));
		}
		
		/* debug */
		this.debug = context.isDebug();
		
		this.codesource = context.getCodesource();
		if(null == this.codesource)
			this.codesource = DEFAULT_CODESOURCE_PREFIX.concat("/").concat(null == name || "".equals(name) ? "default" : name).concat("/");
		
		this.removeFinalizers = context.isRemoveFinalizers();
		
		this.enhancer = context.getLoaderEnhancer();
		
		/* store context */
		this.context = context;
	}
	
	private SandboxLoader initSubLoader(IdentityHashMap<SandboxContext, SandboxLoader> loaderMap, SandboxContext context) {
		if(loaderMap.containsKey(context))
			return loaderMap.get(context);
		
		SandboxLoader subLoader =  new SandboxLoader(this, securityManager);
		subLoader.init(context);
		
		loaderMap.put(context, subLoader);
		
		return subLoader;
	}

	/**
	 * Returns the context used to initialize this {@link SandboxLoader}
	 * 
	 * @see #init(SandboxContext)
	 * @return
	 */
	public SandboxContext getContext() {
		getSecurityManager().checkPermission(new SandboxRuntimePermission("getSandboxLoaderContext"));
		
		return context;
	}
	
	@Override
	protected Class<?> loadClass(final String name, boolean resolve) throws ClassNotFoundException {
		Class clazz = null;

		if(debug)
			logger.log(Level.INFO, getName() + "(" + System.identityHashCode(this) + ")" + " about to load class: " + name);
		
		if(null != enhancer)
			enhancer.classtoBeLoaded(this, name, resolve);
		
		boolean trustedSource = false;
		
	    if(name.startsWith("java.") || bypassClazz(name) ) {
	    	clazz = super.loadClass(name, resolve);
	    	
	    	/* check if it comes from an available jar */
			if(! name.startsWith("java.") && null != whitelistedUcp){
				String path = name.replace('.', '/').concat(".class");
				
				Resource res = whitelistedUcp.getResource(path, false);
				if (res != null) 
					trustedSource = true;
			}
	    	
	    } else {
			/* check subcontext */
			if(hasSubloaders){
				SandboxLoader subLoader = doGetSubLoaderByClassContext(name);
				if(null != subLoader)
					return subLoader.loadClass(name, resolve);
			}
	    	
	    	/* check if we have already handeled this class */
			clazz = findLoadedClass(name);
			if( clazz != null ){
				if(null != whitelistedUcp){
					String path = name.replace('.', '/').concat(".class");
					Resource res = whitelistedUcp.getResource(path, false);
					if (res != null) 
						trustedSource = true;
				}
			} else {
				try {
					String basePath = name.replace('.', '/');
					String path = basePath.concat(".class");
					
					ProtectionDomain domain = null;
					try {
						CodeSource codeSource = new CodeSource(new URL("file", "", codesource.concat(basePath)), (java.security.cert.Certificate[]) null);
						domain = new ProtectionDomain(codeSource, new Permissions(), this, null);
			        }
			        catch (MalformedURLException e) {
			            throw new RuntimeException("Could not create protection domain.");
			        }
					
					/* define package */
					int i = name.lastIndexOf('.');
					if (i != -1) {
						String pkgName = name.substring(0, i);
						java.lang.Package pkg = getPackage(pkgName);
						if (pkg == null) {
							definePackage(pkgName, null, null, null, null, null, null, null);
						}
					}
					
					
					/* first strategy .. check jars */
					if(null != whitelistedUcp){
						Resource res = whitelistedUcp.getResource(path, false);
						if (res != null) {
							byte[] cBytes = enhance(name, res.getBytes());
							clazz = defineClass(name, cBytes, 0, cBytes.length, domain);
							trustedSource = true;
						}
					}
					
					/* load class */
					if( clazz == null ){
						InputStream in = null;
						try{
							/* we only load from local sources */
							in = parent.getResourceAsStream(path);
							byte[] cBytes = null;
							if( in != null )
								 cBytes = IOUtils.toByteArray(in);	
								
							if(null == cBytes && null != enhancer)
								cBytes = enhancer.loadClass(this, name);
							if(null == cBytes)
								throw new ClassNotFoundException("Could not find " + name);
							
							/* load and define class */
							cBytes = enhance(name, cBytes);
							clazz = defineClass(name, cBytes, 0, cBytes.length, domain);
						} finally {
							if(null != in) {
								try {
									in.close();
								} catch (IOException e) {
									e.printStackTrace();
								}
							}
						}
					}
							
					/* do we need to resolve */
					if( resolve ) 
						resolveClass(clazz);
				} catch (IOException e) {
					throw new ClassNotFoundException("Could not load "+name, e);
				} catch (Exception e) {
					throw new ClassNotFoundException("Could not load "+name, e);
				}
			} 
	    }
	    
	    if(! trustedSource && null != clazz && null != securityManager)
	    	securityManager.checkClassAccess(name);
	    
	    if(null != enhancer)
	    	enhancer.classLoaded(this, name, clazz);
	    
	    return clazz;
	}


	private byte[] enhance(String name, byte[] cBytes) throws IOException, RuntimeException, CannotCompileException, NotFoundException {
		if(removeFinalizers){
			CtClass clazz = new ClassPool().makeClass(new ByteArrayInputStream(cBytes));
			if(! clazz.isInterface()) {
				try{
					CtMethod method = clazz.getMethod("finalize", "()V");
					if(null != method && ! method.isEmpty()){
						clazz.removeMethod(method);
						cBytes = clazz.toBytecode();	
					}
				} catch(NotFoundException ignore){}
			}
		}
		if(null != enhancer)
			cBytes = enhancer.enhance(this, name, cBytes);
		
		return cBytes;
		
	}

	public SandboxLoader getSubLoaderByClassContext(String clazz){
		getSecurityManager().checkPermission(new SandboxRuntimePermission("getSubLoader"));
		
		return doGetSubLoaderByClassContext(clazz);
	}
	
	private SandboxLoader doGetSubLoaderByClassContext(String clazz) {
		String path = clazz.replace('.', '/').concat(".class");
		for(Entry<URLClassPath, SandboxLoader> e : subLoaderByJar.entrySet()){
			Resource res = e.getKey().getResource(path, false);
			if (res != null)
				return e.getValue();
		}
		
		SandboxLoader subLoader = subLoaderCache.get(clazz);
		if(null != subLoader)
			return subLoader;
		for(String prefix : subLoaderPrefixCache.keySet())
			if(clazz.startsWith(prefix))
				return subLoaderPrefixCache.get(prefix);
		
		
		return null;
	}

	private boolean bypassClazz(String name) {
		if(null != enhancer && enhancer.isLoadClassWithApplicationLoader(name))
			return true;
		
		if(classesToLoadWithParent.contains(name))
			return ! prohibitBypass(name);
		for(String bp : classesByPrefixToLoadWithParent)
			if(name.startsWith(bp))
				return ! prohibitBypass(name);
		
		/* check if it comes from an available jar */
		if(! name.startsWith("java.") && null != bypassUcp){
			String path = name.replace('.', '/').concat(".class");
			
			Resource res = bypassUcp.getResource(path, false);
			if (res != null) 
				return ! prohibitBypass(name);
		}
		
		return false;
	}

	private boolean prohibitBypass(String name) {
		if(classesToLoadDirectly.contains(name))
			return true;
		for(String bp : classesByPrefixToLoadDirectly)
			if(name.startsWith(bp))
				return true;
	
		return false;
	}
	

	public SandboxLoader getSubloaderByName(String name){
		getSecurityManager().checkPermission(new SandboxRuntimePermission("getSubLoader"));
		
		return doGetSubloaderByName(name);
	}

	private SandboxLoader doGetSubloaderByName(String name){
		if(name.equals(getName()))
			return this;
		
		for(SandboxLoader loader : subLoaderByJar.values())
			if(name.equals(loader.getName()))
				return loader;
		for(SandboxLoader loader : subLoaderCache.values())
			if(name.equals(loader.getName()))
				return loader;
		for(SandboxLoader loader : subLoaderPrefixCache.values())
			if(name.equals(loader.getName()))
				return loader;
		
		for(SandboxLoader loader : subLoaderByJar.values()){
			SandboxLoader subLoader = loader.getSubloaderByName(name);
			if(null != subLoader)
				return subLoader;
		}
		
		for(SandboxLoader loader : subLoaderCache.values()){
			SandboxLoader subLoader = loader.getSubloaderByName(name);
			if(null != subLoader)
				return subLoader;
		}
		
		for(SandboxLoader loader : subLoaderPrefixCache.values()){
			SandboxLoader subLoader = loader.getSubloaderByName(name);
			if(null != subLoader)
				return subLoader;
		}
		
		return null;
	}
	
	/**
	 * Returns the name of the ClassLoader.
	 * 
	 * @return
	 */
	public String getName(){
		return name;
	}
	
	public SandboxSecurityManager getSecurityManager() {
		securityManager.checkPermission(new SandboxRuntimePermission("getSecurityManager"));
		
		return securityManager;
	}
	
	public Class<?> defineClass(String name, byte[] classBytes) {
		return defineClass(name, classBytes, true);
	}
	
	public Class<?> defineClass(String name, byte[] classBytes, boolean enhanceClass) {
		securityManager.checkPermission(new SandboxRuntimePermission("defineClass"));
		
		Class<?> clazz = findLoadedClass(name);
		if(null != clazz)
			return clazz;
		
		if(enhanceClass){
			try {
				classBytes = enhance(name, classBytes);
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
		
		ProtectionDomain domain = null;
		try {
			CodeSource codeSource = new CodeSource(new URL("file", "", codesource), (java.security.cert.Certificate[]) null);
			domain = new ProtectionDomain(codeSource, new Permissions(), this, null);
        }
        catch (MalformedURLException e) {
            throw new RuntimeException("Could not create protection domain.");
        }
		
		return defineClass(name, classBytes, 0, classBytes.length, domain);
	}
		
	
} @Singleton
public class SandboxServiceImpl implements SandboxService {

	private final Logger logger = Logger.getLogger(getClass().getName());
	
	private static SandboxService INSTANCE;
	
	protected final SandboxCleanupService cleanupService;
	protected final boolean enabled;
	
	protected Map<String, SandboxContext> registeredContexts = new Hashtable<String, SandboxContext>();

	protected JvmPool jvmPool;

	private Set<SandboxHandler> handlers = new HashSet<SandboxHandler>();

	protected final ConcurrentLinkedQueue<SandboxMonitoredThread> monitorQueue;
	private int monitorCnt = 1;
	private SandboxMonitorDaemon monitorDaemon;
	private Thread monitorDaemonThread;
	private SandboxMonitorWatchdog monitorWatchdog;
	private Thread monitorWatchdogThread;
	private long monitorDaemonCheckInterval = 10;
	private long monitorWatchdogCheckInterval = 10000;



	/**
	 * 
	 */
	public SandboxServiceImpl(){
		this(true, 
			new SandboxCleanupServiceImpl(), 
			new JvmPoolImpl(new JvmPoolConfigImpl(1, 1)));
	}
	
	public SandboxServiceImpl(File pathToSandCastle){
		this(true, 
			new SandboxCleanupServiceImpl(), 
			new JvmPoolImpl(new JvmPoolConfigImpl(1, 1, "-Xbootclasspath/p:" + pathToSandCastle.getAbsolutePath())));
	}
	
	/**
	 * Initializes the Sandbox service. 
	 * 
	 * Note that this service should be treated as a singleton.
	 * 
	 * @param enabled true to install the security manager
	 */
	@Inject
	public SandboxServiceImpl(
		@EnableSandboxing boolean enabled,
		SandboxCleanupService cleanupService,
		@Nullable JvmPool jvmPool
		){
		this.enabled = enabled;
		this.cleanupService = cleanupService;
		this.jvmPool = jvmPool;
				
		if(null == INSTANCE)
			INSTANCE = this;
		else
			throw new IllegalStateException("SandboxService already instantiated");
		
		if(enabled)
			enable();
		
		monitorQueue = new ConcurrentLinkedQueue<SandboxMonitoredThread>();
		startMonitorDaemon();
		
		Runtime.getRuntime().addShutdownHook(new Thread(){
			@Override
			public void run() {
				shutdown();
			}
		});
	}

	final private void enable() {
		if(! (System.getSecurityManager() instanceof SandboxSecurityManager)){
			try {
				SandboxSecurityManager securityManager = SandboxSecurityManager.newInstance(this);
				System.setSecurityManager(securityManager);
			} catch (Exception e) {
				throw new RuntimeException("Could not initialize security manager: ", e);
			}
		}
	}

	/**
	 * Returns the instance of the {@link SandboxServiceImpl}.
	 * @return
	 */
	public static SandboxService getInstance(){
		if(null == INSTANCE)
			new SandboxServiceImpl();
		return INSTANCE;
	}
	
	public static SandboxService initInstance(File pathToSandcastle){
		if(null != INSTANCE)
			throw new IllegalStateException("already initialized");
		return new SandboxServiceImpl(pathToSandcastle);
	}
	
	/**
	 * Initializes a {@link SandboxServiceImpl} without remote jvm agents.
	 * 
	 * @return
	 */
	public static SandboxService initLocalSandboxService(){
		if(null != INSTANCE)
			throw new IllegalStateException("already initialized");
		return new SandboxServiceImpl(true, 
			new SandboxCleanupServiceImpl(), 
			null);
	}
	
	@Override
	public void setCodesourceSecurityChecks(boolean enable){
		SandboxSecurityManager securityManager = (SandboxSecurityManager) System.getSecurityManager();
		if(null != securityManager)
			securityManager.setCodesourceSecurityChecks(enable);
	}
	
	@Override
	public boolean getCodesourceSecurityChecks(){
		SandboxSecurityManager securityManager = (SandboxSecurityManager) System.getSecurityManager();
		if(null != securityManager)
			return securityManager.isCodesourceSecurityChecks();
		return false;
	}
	
	@Override
	public void startMonitorDaemon() {
		if(isMonitorDaemonActive())
			return;
			
		if(null != monitorDaemon)
			monitorDaemon.shutdown();
		
		monitorDaemon = new SandboxMonitorDaemon(this, monitorQueue);
		monitorDaemon.setCheckInterval(monitorDaemonCheckInterval);
		
		monitorDaemonThread = Executors.defaultThreadFactory().newThread(monitorDaemon);
		monitorDaemonThread.setDaemon(true);
		monitorDaemonThread.setName("sandboxMonitor-" + monitorCnt ++);
		monitorDaemonThread.start();
		
		startMonitorWatchdog();
	}
	
	@Override
	public boolean isMonitorDaemonActive() {
		return null != monitorDaemonThread && monitorDaemonThread.isAlive() && null != monitorDaemon && ! monitorDaemon.isShutdown();
	}
	
	@Override
	public void shutdownMonitorDaemon(){
		if(null != monitorDaemon)
			monitorDaemon.shutdown();
	}

	@Override
	public void startMonitorWatchdog() {
		if(isMonitorWatchdogActive())
			return;
		
		if(null != monitorWatchdog)
			monitorWatchdog.shutdown();
		
		monitorWatchdog = new SandboxMonitorWatchdog(this);
		monitorWatchdog.setCheckInterval(monitorWatchdogCheckInterval);
		
		monitorWatchdogThread = Executors.defaultThreadFactory().newThread(monitorWatchdog);
		monitorWatchdogThread.setDaemon(true);
		monitorWatchdogThread.setName("sandboxMonitorWatchdog");
		monitorWatchdogThread.start();
	}
	
	@Override
	public boolean isMonitorWatchdogActive() {
		return null != monitorWatchdogThread && monitorWatchdogThread.isAlive() && ! monitorWatchdog.isShutdown();
	}
	
	@Override
	public void shutdownMonitorWatchdog(){
		if(null != monitorWatchdog)
			monitorWatchdog.shutdown();
	}
	
	@Override
	public long getMonitorDaemonCheckInterval() {
		return monitorDaemonCheckInterval;
	}

	@Override
	public void setMonitorDaemonCheckInterval(long monitorDaemonCheckInterval) {
		this.monitorDaemonCheckInterval = monitorDaemonCheckInterval;
	}

	@Override
	public long getMonitorWatchdogCheckInterval() {
		return monitorWatchdogCheckInterval;
	}

	@Override
	public void setMonitorWatchdogCheckInterval(long monitorWatchdogCheckInterval) {
		this.monitorWatchdogCheckInterval = monitorWatchdogCheckInterval;
	}

	@Override
	public void shutdown() {
		shutdownJvmPool();
		shutdownMonitorWatchdog();
		shutdownMonitorDaemon();
	}
	
	/*
	 * (non-Javadoc)
	 * @see net.datenwerke.rs.utils.sandbox.SandboxingService#getManager()
	 */
	@Override
	public SandboxSecurityManager getManager() {
		if(! isActive())
			return null;
		
		SecurityManager manager = System.getSecurityManager();
		return (SandboxSecurityManager) manager;
	}

	/*
	 * (non-Javadoc)
	 * @see net.datenwerke.rs.utils.sandbox.SandboxingService#isActive()
	 */
	@Override
	public boolean isActive() {
		return enabled;
	}

	/*
	 * (non-Javadoc)
	 * @see net.datenwerke.rs.utils.sandbox.SandboxingService#restrict()
	 */
	@Override
	public String restrict() {
		if(! isActive())
			return null;
		
		String pw = UUID.randomUUID().toString();
		SandboxContext baseSet = new SandboxContext();
		
		getManager().restrictAccess(pw, baseSet);
		
		return pw;
	}


	/*
	 * (non-Javadoc)
	 * @see net.datenwerke.rs.utils.sandbox.SandboxingService#restrict(java.lang.String, java.lang.String)
	 */
	@Override
	public synchronized String restrict(String context){
		SandboxContext set = registeredContexts.get(context);
		if(null == set) 
			set = new SandboxContext();
		return restrict(set);
	}
	
	/*
	 * (non-Javadoc)
	 * @see net.datenwerke.rs.utils.sandbox.SandboxingService#restrict(java.lang.String, java.lang.String)
	 */
	@Override
	public synchronized String restrict(String pw, String context){
		if(! isActive())
			return pw;
		SandboxContext set = registeredContexts.get(context);
		if(null == set) 
			set = new SandboxContext();
		return restrict(pw, set);
	}
	
	@Override
	public String restrict(SandboxContext context) {
		String pw = UUID.randomUUID().toString();
		restrict(pw, context);
		return pw;
	}
	
	@Override
	public String restrict(String pw, SandboxContext context) {
		if(! isActive())
			return null;
		getManager().restrictAccess(pw, context);
		return pw;
	}
	
	@Override
	public JvmFreelancer acquireFreelancer() throws InterruptedException{
		return jvmPool.acquireFreelancer();
	}
	
	@Override
	public JvmFreelancer acquireFreelancer(long timeout, TimeUnit unit) throws InterruptedException{
		return jvmPool.acquireFreelancer(timeout, unit);
	}
	
	@Override
	public void releaseFreelancer(JvmFreelancer freelancer){
		jvmPool.releaseFreelancer(freelancer);
	}
	
	/*
	 * (non-Javadoc)
	 * @see net.datenwerke.sandbox.SandboxService#shutdownPool()
	 */
	@Override
	public void shutdownJvmPool() {
		if(null != jvmPool)
			jvmPool.shutdown();
	}
	
	/*
	 * (non-Javadoc)
	 * @see net.datenwerke.sandbox.SandboxService#restartPool()
	 */
	@Override
	public void restartJvmPool() {
		if(null != jvmPool)
			jvmPool.restart();
	}
	
	/*
	 * (non-Javadoc)
	 * @see net.datenwerke.sandbox.SandboxService#initJvmPool(net.datenwerke.sandbox.jvm.JvmPool)
	 */
	@Override
	public void initJvmPool(JvmPool pool){
		shutdownJvmPool();
		jvmPool = pool;
	}
	
	/*
	 * (non-Javadoc)
	 * @see net.datenwerke.rs.utils.sandbox.SandboxingService#isRestricted()
	 */
	@Override
	public boolean isRestricted() {
		return isActive() && getManager().isRestricted();
	}
	
	public SandboxSecurityManager getSecurityManager(){
		return (SandboxSecurityManager) System.getSecurityManager();
	}
	
	@Override
	public synchronized void registerContext(String name, SandboxContext context){
		getSecurityManager().checkPermission(new SandboxRuntimePermission("registerSandboxContext"));
		
		for(ContextRegisteredHandler handler : getHandlers(ContextRegisteredHandler.class))
			handler.contextRegistered(name, context);
		
		registeredContexts.put(name, context);			
	}
	
	@Override
	public void releaseRestriction(String pw) {
		if(! isActive())
			return;
		getManager().releaseRestriction(pw);
	}

	/*
	 * (non-Javadoc)
	 * @see net.datenwerke.sandbox.SandboxService#initClassLoader(java.lang.String)
	 */
	@Override
	public SandboxLoader initClassLoader(String restrictSet) {
		return initClassLoader(getContext(restrictSet));
	}
	
	/*
	 * (non-Javadoc)
	 * @see net.datenwerke.sandbox.SandboxService#initClassLoader(net.datenwerke.sandbox.SandboxContext)
	 */
	@Override
	public SandboxLoader initClassLoader(SandboxContext restrictSet) {
		return initClassLoader(getClass().getClassLoader(), restrictSet);
	}

	/*
	 * (non-Javadoc)
	 * @see net.datenwerke.sandbox.SandboxService#initClassLoader(java.lang.ClassLoader, java.lang.String)
	 */
	@Override
	public SandboxLoader initClassLoader(ClassLoader loader, String context) {
		return initClassLoader(loader, getContext(context));
	}
	
	/*
	 * (non-Javadoc)
	 * @see net.datenwerke.sandbox.SandboxService#initClassLoader(java.lang.ClassLoader, net.datenwerke.sandbox.SandboxContext)
	 */
	@Override
	public SandboxLoader initClassLoader(ClassLoader loader, SandboxContext context) {
		SandboxLoader sandboxLoader = new SandboxLoader(loader);
		sandboxLoader.init(context);
		
		return sandboxLoader;
	}

	@Override
	public SandboxContext getContext(String name){
		getSecurityManager().checkPermission(new SandboxRuntimePermission("getSandboxContext"));
		
		if(! registeredContexts.containsKey(name))
			return null;
		return registeredContexts.get(name);
	}
	
	/*
	 * (non-Javadoc)
	 * @see net.datenwerke.sandbox.SandboxService#runSandboxed(java.lang.Class, net.datenwerke.sandbox.SandboxContext, java.lang.Object[])
	 */
	@Override
	public <V> SandboxedCallResult<V> runSandboxed(Class<? extends SandboxedEnvironment> call, SandboxContext context, Object... args){
		SandboxLoader loader = initClassLoader(context);

		return runSandboxed(call, context, loader, args);
	}
		
	/*
	 * (non-Javadoc)
	 * @see net.datenwerke.sandbox.SandboxService#runSandboxed(java.lang.Class, net.datenwerke.sandbox.SandboxContext, java.lang.ClassLoader, java.lang.Object[])
	 */
	@Override
	public <V> SandboxedCallResult<V> runSandboxed(Class<? extends SandboxedEnvironment> call, SandboxContext context, ClassLoader loader, Object... args){
		return run(call, context, loader, false, args);
	}

	
	protected <V> SandboxedCallResult<V> run(Class<? extends SandboxedEnvironment> call,
			SandboxContext context, ClassLoader loader, boolean runInContext, Object[] args) {
		if(null == call)
			return null;

		if(isRemoteService() && context.isDebug())
			logger.log(Level.INFO, "run remote");
		
		try{
			Object result = null;
			if(! isRemoteService() && context.isRunRemote() && null != jvmPool ){
				result = jvmPool.addTask(new JvmSandboxTask(call, context, runInContext, args)).get().getRaw();
			} else if(context.isRunInThread()){
				Method runMethod = getCalleableMethod(call, loader);
				Object instance = getCalleableInstance(call, loader, args);
				
				SandboxedThread thread = new SandboxedThread(this, runMethod, instance, context, runInContext);
				thread.setContextClassLoader(loader);
				
				/* put in monitor queue */
				monitorQueue.add(new SandboxMonitoredThread(Thread.currentThread(), thread, context));
				
				/* start thread and wait */
				try{
					thread.start();
					thread.join();
				} catch(InterruptedException ignore){
				}

				/* obtain result */
				if(thread.isSuccess())
					result = thread.getResult();
				else {
					if(isRemoteService()&& thread.isKilled() &&! thread.isKilledSafely())
						throw new JvmKilledUnsafeThreadRuntimeException();

					Exception e = thread.getException();
					if(null == e)
						throw new JvmKilledThreadRuntimeException();
					
					throw e;
				}
			} else {
				Method runMethod = getCalleableMethod(call, loader);
				Object instance = getCalleableInstance(call, loader, args);
				
				if(runInContext)
					result = runMethod.invoke(instance);
				else {
					String pw = restrict(context);
					try{
						result = runMethod.invoke(instance);
					} finally {
						releaseRestriction(pw);
					}
				}
			}
			
			return new SandboxedCallResultImpl<V>(result);
		} catch(Exception e){
			if(e instanceof JvmKilledUnsafeThreadRuntimeException)
				throw (JvmKilledUnsafeThreadRuntimeException)e;
			if(e instanceof SandboxException)
				throw (SandboxException)e;
			if(e instanceof InvocationTargetException && null != e.getCause())
				throw new SandboxException(e.getCause().getClass().getName() + ": " + e.getCause().getMessage(), e);
			throw new SandboxException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * @see net.datenwerke.sandbox.SandboxService#isRemoteService()
	 */
	@Override
	public boolean isRemoteService(){
		return false;
	}

	@Override
	public boolean hasRemoteAgents() {
		return null != jvmPool;
	}

	/*
	 * (non-Javadoc)
	 * @see net.datenwerke.sandbox.SandboxService#runInContext(java.lang.Class, net.datenwerke.sandbox.SandboxContext, java.lang.Object[])
	 */
	@Override
	public <V> SandboxedCallResult<V> runInContext(Class<? extends SandboxedEnvironment> call,
			SandboxContext context, Object... args){
		SandboxLoader loader = initClassLoader(context);
		return runInContext(call, context, loader, args);
	}
	
	/*
	 * (non-Javadoc)
	 * @see net.datenwerke.sandbox.SandboxService#runInContext(java.lang.Class, net.datenwerke.sandbox.SandboxContext, java.lang.ClassLoader, java.lang.Object[])
	 */
	@Override
	public <V> SandboxedCallResult<V> runInContext(Class<? extends SandboxedEnvironment> call,
			SandboxContext context, ClassLoader loader, Object... args){
		return run(call, context, loader, true, args);
	}
	
	/**
	 * 
	 */
	protected Method getCalleableMethod(Class<?> call, ClassLoader loader) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException{
		Class<?> clazz = Class.forName(call.getName(), true, loader);
		Method m = clazz.getMethod("execute");
		m.setAccessible(true);

		return m;
	}
	
	protected Object getCalleableInstance(Class<?> call, ClassLoader loader, Object... args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException{
		Class<?> clazz = Class.forName(call.getName(), true, loader);

		/* find constructor */
		Constructor constructor = null;
		Object[] values = null;
		if(null == args || args.length == 0) 
			constructor = clazz.getDeclaredConstructor((Class<?>[])null);
		else {
			int length = args.length;
			Class<?>[] typeArray = new Class<?>[length];
			values = new Object[length];
			for(int i = 0; i<length; i++){
				Object o = args[i];
				if(o instanceof TypedArgument){
					typeArray[i] = Class.forName(((TypedArgument)o).getType().getName(), true, loader);
					if(((TypedArgument)o).isBridge())
						values[i] = bridge(((TypedArgument)o).getValue(), loader);
					else 
						values[i] = ((TypedArgument)o).getValue();
				} else {
					typeArray[i] = Class.forName(o.getClass().getName(), true, loader);
					values[i] = o; 
				}
			}
			
			try{
				constructor = clazz.getDeclaredConstructor(typeArray);
			} catch(NoSuchMethodException e){
				throw new SandboxConfigurationException(e.getMessage()  + ": Could not load constructor for the SandboxedEnvironment object. I tried to find a constructor " +
						"with the following types: " + typeArray +
						" Is there a classloader problem?", e);
			}
		}
			
		/* generate instance */
		constructor.setAccessible(true);
		if(null == values)
			return constructor.newInstance();
		try{
			return constructor.newInstance(values);
		} catch(IllegalArgumentException e){
			StringBuffer buf = new StringBuffer("[");
			for(Object o : values)
				buf.append(o).append(", ");
			buf.append("]");
			throw new SandboxConfigurationException(e.getMessage() + ": Could not instantiate the SandboxedEnvironment object. " +
					"Is there a classloader problem? The SandboxedEnvironment's constructor expects " + 
					constructor + " but I got: " + buf, e);
		}
	}

	/*
	 * (non-Javadoc)
	 * @see net.datenwerke.sandbox.SandboxService#bridge(java.lang.Object)
	 */
	@Override
	public Object bridge(Object result) {
		return bridge(result, getClass().getClassLoader());
	}
	
	/*
	 * (non-Javadoc)
	 * @see net.datenwerke.rs.utils.sandbox.SandboxingService#bridge(java.lang.Object)
	 */
	@Override
	public Object bridge(Object result, ClassLoader loader) {
		if(null == result)
			return null;
		
		if(result.getClass().getClassLoader() == null && loader == null)
			return result;
		if(null != result.getClass().getClassLoader() && result.getClass().getClassLoader().equals(loader))
			return result;
		
		Transloader transloader = new DefaultTransloader(CloningStrategy.newMinimalInstance());
		ObjectWrapper resultWrapped = transloader.wrap(result);
		
		return resultWrapped.cloneWith(loader);
	}

	void kill(SandboxMonitoredThread monitor, SandboxedTaskKilledException exception) {
		boolean safe = false;
		try{
			BadKillInfo killInfo = cleanupService.kill(monitor, exception);
			if(null == killInfo)
				safe = true;
			else {
				for(BadThreadKillHandler handler : getHandlers(BadThreadKillHandler.class))
					handler.badThreadKilled(killInfo);
			}
		} catch(Exception ignore){}
		
		if(! safe){
			if(! isRemoteService())
				logger.log(Level.SEVERE, "killed potentially unsafe thread");
		}
	}

	@Override
	public synchronized void attachHandler(SandboxHandler handler) {
		getSecurityManager().checkPermission(new SandboxRuntimePermission("attachHandler"));
		
		handlers.add(handler);
	}
	
	@Override
	public synchronized boolean dettachHandler(SandboxHandler handler) {
		getSecurityManager().checkPermission(new SandboxRuntimePermission("dettachHandler"));
		
		return handlers.remove(handler);
	}
	
	protected synchronized <H extends SandboxHandler>  Collection<H> getHandlers(Class<H> clazz) {
		Collection<H> col = new HashSet<H>();
		for(SandboxHandler handler : handlers)
			if(clazz.isAssignableFrom(handler.getClass()))
				col.add((H) handler);
		return col;
	}
	
	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		shutdownJvmPool();
	}
	
} public interface SandboxedCallResult<V> extends Serializable {
	
	/**
	 * Returns the raw object 
	 * 
	 * @return
	 */
	Object getRaw();
	
	/**
	 * Returns the object in the scope of the classloader that has loaded this instance
	 * of {@link SandboxedCallResult}
	 * 
	 * @return
	 */
	V get();
	
	/**
	 * Returns the object in the scope of the given classloader.
	 * 
	 * @param  loader
	 * @return
	 */
	Object get(ClassLoader loader);
	
	/**
	 * Returns the wrapped object in the scope of the classloader that has loaded object obj.
	 * 
	 * @param obj
	 * @return
	 */
	Object get(Object obj);

	
} public interface SandboxedEnvironment<V> {

	/**
	 * Called by the {@link SandboxService} when asked to execute code in a sandbox.
	 * 
	 * @see SandboxService#runInContext(Class, SandboxContext, Object...)
	 * @see SandboxService#runSandboxed(Class, SandboxContext, Object...)
	 * @return
	 * @throws Exception
	 */
	public V execute() throws Exception;
} public interface JvmTask extends Serializable, Callable<SandboxedCallResult> {

} public class JvmInitializedTwiceException extends RemoteException {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1787074846756698184L;

	
	public JvmInitializedTwiceException(){
		super("This jvm has not yet been initialized.");
	}
} public class JvmKilledUnsafeThreadException extends RemoteException {

	/**
	 * 
	 */
	private static final long serialVersionUID = -5420515201162587892L;



} public class JvmKilledUnsafeThreadRuntimeException extends RuntimeException {

	/**
	 * 
	 */
	private static final long serialVersionUID = -973698092416729397L;


} public class JvmNotInitializedException extends RemoteException {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1787074846756698184L;

	
	public JvmNotInitializedException(){
		super("This jvm has already been initialized. Need to be reset first.");
	}
}
import net.datenwerke.sandbox.SandboxContext;
import net.datenwerke.sandbox.SandboxLoader;
import net.datenwerke.sandbox.SandboxServiceImpl;
import net.datenwerke.sandbox.SandboxedCallResult;
import net.datenwerke.sandbox.SandboxedEnvironment;
import net.datenwerke.sandbox.jvm.JvmTask;
import net.datenwerke.sandbox.jvm.exceptions.JvmInitializedTwiceException;
import net.datenwerke.sandbox.jvm.exceptions.JvmKilledUnsafeThreadException;
import net.datenwerke.sandbox.jvm.exceptions.JvmKilledUnsafeThreadRuntimeException;
import net.datenwerke.sandbox.jvm.exceptions.JvmNotInitializedException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;
import java.util.logging.Level;
import java.util.logging.Logger;
/*
*  java-sandbox
*  Copyright (c) 2012 datenwerke Jan Albrecht
*  http://www.datenwerke.net
*  
*  This file is part of the java-sandbox: https://sourceforge.net/p/dw-sandbox/
*
*
*  This program 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.

*  This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
*
*/

package net.datenwerke.sandbox.jvm.server;



public class SandboxJvmServer extends UnicastRemoteObject implements SandboxRemoteServer {

	/**
	 * 
	 */
	private static final long serialVersionUID = 3625471545043099538L;
	
	private final Logger logger = Logger.getLogger(getClass().getName());
	
	private final  String name;

	private SandboxContext context;

	private SandboxLoader classloader;

	public SandboxJvmServer(String namePrefix)  throws RemoteException {
		this.name = NAME + namePrefix;
		
		logger.log(Level.INFO, "started sandbox server: " + getName());
	}
	
	@Override
	public String getName() {
		return name;
	}
	
	@Override
	public boolean isAlive() {
		return true;
	}
	
	@Override
	public SandboxedCallResult execute(JvmTask task) throws RemoteException {
		try {
			return task.call();
		} catch(JvmKilledUnsafeThreadRuntimeException e){
			throw new JvmKilledUnsafeThreadException();
		} catch (Exception e) {
			throw new RemoteException(e.getMessage(), e);
		}
	}
	
	@Override
	public void destroy() {
		System.exit(0);
	}
	
	@Override
	public void init(SandboxContext context) throws RemoteException {
		if(null != this.context)
			throw new JvmInitializedTwiceException();
		
		this.context = context;
		this.classloader = SandboxServiceImpl.getInstance().initClassLoader(context);