Add utilities and sample code to load luaj in custom class loader for strong sandboxing, and use of orphaned threads.

This commit is contained in:
James Roseborough
2015-04-17 02:59:50 +00:00
parent 70f7859cee
commit b545646922
7 changed files with 513 additions and 5 deletions

View File

@@ -464,6 +464,28 @@ multiple threads see <a href="examples/jse/SampleMultiThreaded.java">examples/js
As an alternative, the JSR-223 scripting interface can be used, and should always provide a separate Globals instance As an alternative, the JSR-223 scripting interface can be used, and should always provide a separate Globals instance
per script engine instance by using a ThreadLocal internally. per script engine instance by using a ThreadLocal internally.
<h2>Sandboxing</h2>
Lua and luaj are allow for easy sandboxing of scripts in a server environment.
<P>
Considerations include
<ul>
<li>The <em>debug</em> and <em>luajava</em> library give unfettered access to the luaj vm and java vm
<li>Portions of the <em>os</em>, <em>io</em>, and <em>coroutine</em> libraries are prone to abuse
<li>Rogue scripts may need to be throttled or killed
<li>Shared metatables (string, booleans, etc.) need to be made read-only or isolated via class loaders
such as <a href="http://luaj.sourceforge.net/api/3.0/org/luaj/vm2/server/LuajClassLoader.html">LuajClassLoader</a>
</ul>
Luaj provides sample code covering various approaches:
<ul>
<li><a href="examples/jse/SampleSandboxed.java">examples/jse/SampleSandboxed.java</a>
A java sandbox that limits libraries, limits bytecodes per script, and makes shared tables read-only
<li><a href="examples/lua/samplesandboxed.lua">examples/jse/samplesandboxed.lua</a>
A lua sandbox that limits librares,limits bytecodes per script, and makes shared tables read-only
<li><a href="examples/jse/SampleUsingClassLoader.java">examples/jse/SampleUsingClassLoader.java</a>
A heavier but strong sandbox where each script gets its own class loader and a full private luaj implementation
</ul>
<h1>4 - <a name="4">Libraries</a></h1> <h1>4 - <a name="4">Libraries</a></h1>
<h2>Standard Libraries</h2> <h2>Standard Libraries</h2>
@@ -535,7 +557,8 @@ Luaj uses WeakReferences and the OrphanedThread error to ensure that coroutines
are properly garbage collected. For thread safety, OrphanedThread should not be caught by Java code. are properly garbage collected. For thread safety, OrphanedThread should not be caught by Java code.
See <a href="http://luaj.sourceforge.net/api/3.0/org/luaj/vm2/LuaThread.html">LuaThread</a> See <a href="http://luaj.sourceforge.net/api/3.0/org/luaj/vm2/LuaThread.html">LuaThread</a>
and <a href="http://luaj.sourceforge.net/api/3.0/org/luaj/vm2/OrphanedThread.html">OrphanedThread</a> and <a href="http://luaj.sourceforge.net/api/3.0/org/luaj/vm2/OrphanedThread.html">OrphanedThread</a>
javadoc for details. javadoc for details. The sample code in <a href="examples/jse/CollectingOrphanedCoroutines.java">examples/jse/CollectingOrphanedCoroutines.java</a>
provides working examples.
<h3>Debug Library</h3> <h3>Debug Library</h3>
The <em>debug</em> library is not included by default by The <em>debug</em> library is not included by default by
@@ -982,6 +1005,9 @@ Files are no longer hosted at LuaForge.
<li>Fix os.date("*t") to return hour in 24 hour format (fixes issue #45)</li> <li>Fix os.date("*t") to return hour in 24 hour format (fixes issue #45)</li>
<li>Add SampleSandboxed.java example code to illustrate sandboxing techniques in Java.</li> <li>Add SampleSandboxed.java example code to illustrate sandboxing techniques in Java.</li>
<li>Add samplesandboxed.lua example code to illustrate sandboxing techniques in lua.</li> <li>Add samplesandboxed.lua example code to illustrate sandboxing techniques in lua.</li>
<li>Add CollectingOrphanedCoroutines.java example code to show how to deal with orphaned lua threads.</li>
<li>Add LuajClassLoader.java and Launcher.java to simplify loading via custom class loader.</li>
<li>Add SampleUsingClassLoader.java example code to demonstrate loading using custom class loader.</li>
<li>Make string metatable a proper metatable, and make it read-only by default.</li> <li>Make string metatable a proper metatable, and make it read-only by default.</li>
<li>Add sample code that illustrates techniques in creating sandboxed environments.</li> <li>Add sample code that illustrates techniques in creating sandboxed environments.</li>
<li>Add convenience methods to Global to load string scripts with custom environment.</li> <li>Add convenience methods to Global to load string scripts with custom environment.</li>
@@ -1001,6 +1027,8 @@ Files are no longer hosted at LuaForge.
<li>negative zero is treated as identical to integer value zero throughout luaj <li>negative zero is treated as identical to integer value zero throughout luaj
<li>lua compiled into java bytecode using luajc cannot use string.dump() or xpcall() <li>lua compiled into java bytecode using luajc cannot use string.dump() or xpcall()
<li>number formatting with string.format() is not supported <li>number formatting with string.format() is not supported
<li>shared metatables for string, bool, etc are shared across Globals instances in the same class loader
<li>orphaned threads will not be collected unless garbage collection is run and sufficient time elapses
</ul> </ul>
<h3>File Character Encoding</h3> <h3>File Character Encoding</h3>
Source files can be considered encoded in UTF-8 or ISO-8859-1 and results should be as expected, Source files can be considered encoded in UTF-8 or ISO-8859-1 and results should be as expected,

View File

@@ -86,11 +86,11 @@
<javac destdir="build/jse/classes" encoding="utf-8" source="1.3" target="1.3" <javac destdir="build/jse/classes" encoding="utf-8" source="1.3" target="1.3"
classpath="lib/bcel-5.2.jar" classpath="lib/bcel-5.2.jar"
srcdir="build/jse/src" srcdir="build/jse/src"
excludes="**/script/*,**/Lua2Java*,lua*"/> excludes="**/script/*,**/Lua2Java*,**/server/*,lua*"/>
<javac destdir="build/jse/classes" encoding="utf-8" source="1.5" target="1.5" <javac destdir="build/jse/classes" encoding="utf-8" source="1.5" target="1.5"
classpath="build/jse/classes" classpath="build/jse/classes"
srcdir="build/jse/src" srcdir="build/jse/src"
includes="**/script/*,**/Lua2Java*"/> includes="**/script/*,**/Lua2Java*,**/server/*"/>
<javac destdir="build/jse/classes" encoding="utf-8" source="1.3" target="1.3" <javac destdir="build/jse/classes" encoding="utf-8" source="1.3" target="1.3"
classpath="build/jse/classes" classpath="build/jse/classes"
srcdir="build/jse/src" srcdir="build/jse/src"
@@ -127,7 +127,7 @@
use="true" use="true"
windowtitle="Luaj API"> windowtitle="Luaj API">
<fileset dir="src/core" defaultexcludes="yes" includes="org/luaj/vm2/*.java,org/luaj/vm2/compiler/LuaC.java,org/luaj/vm2/lib/*.java"/> <fileset dir="src/core" defaultexcludes="yes" includes="org/luaj/vm2/*.java,org/luaj/vm2/compiler/LuaC.java,org/luaj/vm2/lib/*.java"/>
<fileset dir="src/jse" defaultexcludes="yes" includes="org/luaj/vm2/lib/jse/*.java,org/luaj/vm2/luajc/LuaJC.java"/> <fileset dir="src/jse" defaultexcludes="yes" includes="org/luaj/vm2/lib/jse/*.java,org/luaj/vm2/luajc/LuaJC.java,org/luaj/vm2/server/*.java"/>
<fileset dir="src/jme" defaultexcludes="yes" includes="org/luaj/vm2/lib/jme/*.java"/> <fileset dir="src/jme" defaultexcludes="yes" includes="org/luaj/vm2/lib/jme/*.java"/>
<doctitle><![CDATA[<h1>Luaj API</h1>]]></doctitle> <doctitle><![CDATA[<h1>Luaj API</h1>]]></doctitle>
<bottom><![CDATA[<i>Copyright &#169; 2007-2008 Luaj.org. All Rights Reserved.</i>]]></bottom> <bottom><![CDATA[<i>Copyright &#169; 2007-2008 Luaj.org. All Rights Reserved.</i>]]></bottom>

View File

@@ -0,0 +1,53 @@
import org.luaj.vm2.Globals;
import org.luaj.vm2.LuaThread;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.jse.JsePlatform;
/** Example that continually launches coroutines, and illustrates how to make
* sure the orphaned coroutines are cleaned up properly.
*
* Main points:
* <ul><li>Each coroutine consumes one Java Thread while active or reference anywhere</li>
* <li>All references to a coroutine must be dropped for the coroutine to be collected</li>
* <li>Garbage collection must be run regularly to remove weak references to lua threads</li>
* <li>LuaThread.thread_orphan_check_interval must be short enough to find orphaned references quickly</li>
* </ul>
*/
public class CollectingOrphanedCoroutines {
// Script that launches coroutines over and over in a loop.
// Garbage collection is done periodically to find and remove orphaned threads.
// Coroutines yield out when they are done.
static String script =
"i,n = 0,0\n print(i)\n"
+ "f = function() n=n+1; coroutine.yield(false) end\n"
+ "while true do\n"
+ " local cor = coroutine.wrap(f)\n"
+ " cor()\n"
+ " i = i + 1\n"
+ " if i % 1000 == 0 then\n"
+ " collectgarbage()\n"
+ " print('threads:', i, 'executions:', n, collectgarbage('count'))\n"
+ " end\n"
+ "end\n";
public static void main(String[] args) throws InterruptedException {
// This timer controls how often each Java thread wakes up and checks if
// it has been orhaned or not. A large number here will produce a long
// delay between orphaning and colleciton, and a small number here will
// consumer resources polling for orphaned status if there are many threads.
LuaThread.thread_orphan_check_interval = 500;
// Should work with standard or debug globals.
Globals globals = JsePlatform.standardGlobals();
// Globals globals = JsePlatform.debugGlobals();
// Should work with plain compiler or lua-to-Java compiler.
// org.luaj.vm2.luajc.LuaJC.install(globals);;
// Load and run the script, which launches coroutines over and over forever.
LuaValue chunk = globals.load(script, "main");
chunk.call();
}
}

View File

@@ -0,0 +1,95 @@
import java.io.InputStream;
import java.io.Reader;
import org.luaj.vm2.Globals;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.jse.JsePlatform;
import org.luaj.vm2.server.Launcher;
import org.luaj.vm2.server.LuajClassLoader;
/** Example of using {@link LuajClassLoader} to launch scripts that are blocked from
* interfering with globals from other scripts including shared static metatables.
* <P>
* This technique is useful in a server environment to expose the full set of
* lua features to each script, while preventing scripts from interfering with
* each other.
* <P>
* Because each Launch gets its own {@link LuajClassLoader}, it should be possible
* to include the debug library, or let scripts manipulate shared metatables,
* or luajava, which otherwise present challenges in a server environment.
* <P>
*/
public class SampleUsingClassLoader {
/** Script that manipulates the shared string metatable.
* When loaded by the {@link LuajClassLoader} via a {@link Launcher}
* created by that class, each instance of {@link LuajClassLoader} will
* have a completely separate version of all static variables.
*/
static String script =
"print('args:', ...)\n" +
"print('abc.foo', ('abc').foo)\n" +
"getmetatable('abc').__index.foo = function() return 'bar' end\n" +
"print('abc.foo', ('abc').foo)\n" +
"print('abc:foo()', ('abc'):foo())\n" +
"return math.pi\n";
public static void main(String[] s) throws Exception {
// The default launcher used standard globals.
RunUsingDefaultLauncher();
RunUsingDefaultLauncher();
// Example using custom launcher class that instantiates debug globals.
RunUsingCustomLauncherClass();
RunUsingCustomLauncherClass();
}
static void RunUsingDefaultLauncher() throws Exception {
Launcher launcher = LuajClassLoader.NewLauncher();
// starts with pristine Globals including all luaj static variables.
print(launcher.launch(script, new Object[] { "--------" }));
// reuses Globals and static variables from previous step.
print(launcher.launch(script, new Object[] {}));
}
static void RunUsingCustomLauncherClass() throws Exception {
Launcher launcher = LuajClassLoader.NewLauncher(MyLauncher.class);
// starts with pristine Globals including all luaj static variables.
print(launcher.launch(script, new Object[] { "=========" }));
// reuses Globals and static variables from previous step.
print(launcher.launch(script, new Object[] { "" }));
}
/** Example of Launcher implementation performing specialized launching.
* When loaded by the {@link LuajClassLoader} all luaj classes will be loaded
* for each instance of the {@link Launcher} and not interfere with other
* classes loaded by other instances.
*/
public static class MyLauncher implements Launcher {
Globals g;
public MyLauncher() {
g = JsePlatform.debugGlobals();
// ... plus any other customization of the user environment
}
public Object[] launch(String script, Object[] arg) {
LuaValue chunk = g.load(script, "main");
return new Object[] { chunk.call(LuaValue.valueOf(arg[0].toString())) };
}
public Object[] launch(InputStream script, Object[] arg) {
LuaValue chunk = g.load(script, "main", "bt", g);
return new Object[] { chunk.call(LuaValue.valueOf(arg[0].toString())) };
}
public Object[] launch(Reader script, Object[] arg) {
LuaValue chunk = g.load(script, "main");
return new Object[] { chunk.call(LuaValue.valueOf(arg[0].toString())) };
}
}
/** Print the return values as strings. */
private static void print(Object[] return_values) {
for (int i =0; i<return_values.length; ++i)
System.out.println("Return value " + return_values[i]);
}
}

View File

@@ -0,0 +1,105 @@
/*******************************************************************************
* Copyright (c) 2015 Luaj.org. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
package org.luaj.vm2.server;
import java.io.InputStream;
import java.io.Reader;
import org.luaj.vm2.Globals;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.Varargs;
import org.luaj.vm2.lib.jse.CoerceJavaToLua;
import org.luaj.vm2.lib.jse.JsePlatform;
/**
* Default {@link Launcher} instance that creates standard globals
* and runs the supplied scripts with chunk name 'main'.
* <P>
* Arguments are coerced into lua using {@link CoerceJavaToLua#coerce(Object)}.
* <P>
* Return values with simple types are coerced into Java simple types.
* Tables, threads, and functions are returned as lua objects.
*
* @see Launcher
* @see LuajClassLoader
* @see LuajClassLoader#NewLauncher()
* @see LuajClassLoader#NewLauncher(Class)
* @since luaj 3.0.1
*/
public class DefaultLauncher implements Launcher {
protected Globals g;
public DefaultLauncher() {
g = JsePlatform.standardGlobals();
}
/** Launches the script with chunk name 'main' */
public Object[] launch(String script, Object[] arg) {
return launchChunk(g.load(script, "main"), arg);
}
/** Launches the script with chunk name 'main' and loading using modes 'bt' */
public Object[] launch(InputStream script, Object[] arg) {
return launchChunk(g.load(script, "main", "bt", g), arg);
}
/** Launches the script with chunk name 'main' */
public Object[] launch(Reader script, Object[] arg) {
return launchChunk(g.load(script, "main"), arg);
}
private Object[] launchChunk(LuaValue chunk, Object[] arg) {
LuaValue args[] = new LuaValue[arg.length];
for (int i = 0; i < args.length; ++i)
args[i] = CoerceJavaToLua.coerce(arg[i]);
Varargs results = chunk.invoke(LuaValue.varargsOf(args));
final int n = results.narg();
Object return_values[] = new Object[n];
for (int i = 0; i < n; ++i) {
LuaValue r = results.arg(i+1);
switch (r.type()) {
case LuaValue.TBOOLEAN:
return_values[i] = r.toboolean();
break;
case LuaValue.TNUMBER:
return_values[i] = r.todouble();
break;
case LuaValue.TINT:
return_values[i] = r.toint();
break;
case LuaValue.TNIL:
return_values[i] = null;
break;
case LuaValue.TSTRING:
return_values[i] = r.tojstring();
break;
case LuaValue.TUSERDATA:
return_values[i] = r.touserdata();
break;
default:
return_values[i] = r;
}
}
return return_values;
}
}

View File

@@ -0,0 +1,70 @@
/*******************************************************************************
* Copyright (c) 2015 Luaj.org. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
package org.luaj.vm2.server;
import java.io.InputStream;
import java.io.Reader;
/** Interface to launch lua scripts using the {@link LuajClassLoader}.
* <P>
* <em>Note: This class is experimental and subject to change in future versions.</em>
* <P>
* This interface is purposely genericized to defer class loading so that
* luaj classes can come from the class loader.
* <P>
* The implementation should be acquired using {@link LuajClassLoader#NewLauncher()}
* or {@link LuajClassLoader#NewLauncher(Class)} which ensure that the classes are
* loaded to give each Launcher instance a pristine set of Globals, including
* the shared metatables.
*
* @see LuajClassLoader
* @see LuajClassLoader#NewLauncher()
* @see LuajClassLoader#NewLauncher(Class)
* @see DefaultLauncher
* @since luaj 3.0.1
*/
public interface Launcher {
/** Launch a script contained in a String.
*
* @param script The script contents.
* @param arg Optional arguments supplied to the script.
* @return return values from the script.
*/
public Object[] launch(String script, Object[] arg);
/** Launch a script from an InputStream.
*
* @param script The script as an InputStream.
* @param arg Optional arguments supplied to the script.
* @return return values from the script.
*/
public Object[] launch(InputStream script, Object[] arg);
/** Launch a script from a Reader.
*
* @param script The script as a Reader.
* @param arg Optional arguments supplied to the script.
* @return return values from the script.
*/
public Object[] launch(Reader script, Object[] arg);
}

View File

@@ -0,0 +1,157 @@
/*******************************************************************************
* Copyright (c) 2015 Luaj.org. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
package org.luaj.vm2.server;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
/**
* Class loader that can be used to launch a lua script in a Java VM that has a
* unique set of classes for org.luaj classes.
* <P>
* <em>Note: This class is experimental and subject to change in future versions.</em>
* <P>
* By using a custom class loader per script, it allows the script to have
* its own set of globals, including static values such as shared metatables
* that cannot access lua values from other scripts because their classes are
* loaded from different class loaders. Thus normally unsafe libraries such
* as luajava can be exposed to scripts in a server environment using these
* techniques.
* <P>
* All classes in the package "org.luaj.vm2." are considered user classes, and
* loaded into this class loader from their bytes in the class path. Other
* classes are considered systemc classes and loaded via the system loader. This
* class set can be extended by overriding {@link #isUserClass(String)}.
* <P>
* The {@link Launcher} interface is loaded as a system class by exception so
* that the caller may use it to launch lua scripts.
* <P>
* By default {@link #NewLauncher()} creates a subclass of {@link Launcher} of
* type {@link DefaultLauncher} which creates debug globals, runs the script,
* and prints the return values. This behavior can be changed by supplying a
* different implementation class to {@link #NewLauncher(Class)} which must
* extend {@link Launcher}.
*
* @see Launcher
* @see #NewLauncher()
* @see #NewLauncher(Class)
* @see DefaultLauncher
* @since luaj 3.0.1
*/
public class LuajClassLoader extends ClassLoader {
/** String describing the luaj packages to consider part of the user classes */
static final String luajPackageRoot = "org.luaj.vm2.";
/** String describing the Launcher interface to be considered a system class */
static final String launcherInterfaceRoot = Launcher.class.getName();
/** Local cache of classes loaded by this loader. */
Map<String, Class<?>> classes = new HashMap<String, Class<?>>();
/**
* Construct a default {@link Launcher} instance that will load classes in
* its own {@link LuajClassLoader} using the default implementation class
* {@link DefaultLauncher}.
* <P>
* The {@link Launcher} that is returned will be a pristine luaj vm
* whose classes are loaded into this loader including static variables
* such as shared metatables, and should not be able to directly access
* variables from other Launcher instances.
*
* @return {@link Launcher} instance that can be used to launch scripts.
* @throws InstantiationException
* @throws IllegalAccessException
* @throws ClassNotFoundException
*/
public static Launcher NewLauncher() throws InstantiationException,
IllegalAccessException, ClassNotFoundException {
return NewLauncher(DefaultLauncher.class);
}
/**
* Construct a {@link Launcher} instance that will load classes in
* its own {@link LuajClassLoader} using a user-supplied implementation class
* that implements {@link Launcher}.
* <P>
* The {@link Launcher} that is returned will be a pristine luaj vm
* whose classes are loaded into this loader including static variables
* such as shared metatables, and should not be able to directly access
* variables from other Launcher instances.
*
* @return instance of type 'launcher_class' that can be used to launch scripts.
* @throws InstantiationException
* @throws IllegalAccessException
* @throws ClassNotFoundException
*/
public static Launcher NewLauncher(Class<? extends Launcher> launcher_class)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
final LuajClassLoader loader = new LuajClassLoader();
final Object instance = loader.loadAsUserClass(launcher_class.getName())
.newInstance();
return (Launcher) instance;
}
/**
* Test if a class name should be considered a user class and loaded
* by this loader, or a system class and loaded by the system loader.
* @param classname Class name to test.
* @return true if this should be loaded into this class loader.
*/
public static boolean isUserClass(String classname) {
return classname.startsWith(luajPackageRoot)
&& !classname.startsWith(launcherInterfaceRoot);
}
public Class<?> loadClass(String classname) throws ClassNotFoundException {
if (classes.containsKey(classname))
return classes.get(classname);
if (!isUserClass(classname))
return super.findSystemClass(classname);
return loadAsUserClass(classname);
}
private Class<?> loadAsUserClass(String classname) throws ClassNotFoundException {
final String path = classname.replace('.', '/').concat(".class");
InputStream is = getResourceAsStream(path);
if (is != null) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
for (int n = 0; (n = is.read(b)) >= 0;)
baos.write(b, 0, n);
byte[] bytes = baos.toByteArray();
Class<?> result = super.defineClass(classname, bytes, 0,
bytes.length);
classes.put(classname, result);
return result;
} catch (java.io.IOException e) {
throw new ClassNotFoundException("Read failed: " + classname
+ ": " + e);
}
}
throw new ClassNotFoundException("Not found: " + classname);
}
}