Commits

Don Brown committed c84b956

Well, had it working, then decided to get rid of script classes
and go with a more consistent plugin module class system,
but that brought in moduledescriptors and down the rabbit hole I went.

The core issue is we need to access extensions but get metadata w/o invoking them,
and just using class instances are too annoying with rhino, so we need something
else.

todo:
- add pluginLoader to load in legacy script classes, get rid of scriptloader
- implement scriptmoduledescriptor
- get menupath and other info out of script for new stuff
- add and implement appropriate getModule getModuleDescriptor methods
- implement all the overlay stuff
- add an overlaymoduledescriptor and a ringo impl
- hook the new overlay stuff and actions up to the ui

  • Participants
  • Parent commits dcd1663

Comments (0)

Files changed (41)

File plugin/overlay-action/move.js

+/**
+ * @label Move
+ * @type sector, warp
+ */
+
+function createAction(sector, db) {
+    if (db.you.sector.hasWarpTo(sector)) {
+        return function() {
+            send("m" + sector + "\r\n");
+        }
+    }
+}
+

File plugin/overlay-detector/warp.js

+function detect(line, mouseX) {
+    if (line.startsWith("Warps to")) {
+        return {
+            start : 10,
+            end : 12,
+            value : "300"
+        }
+    }
+}
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>com.google.inject</groupId>
+            <artifactId>guice</artifactId>
+            <version>3.0</version>
+        </dependency>
+        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
             <version>1.6.6</version>

File src/krum/weaponm/WeaponM.java

 package krum.weaponm;
 
-import java.io.File;
-import java.io.IOException;
-import java.util.*;
-
-import javax.swing.JOptionPane;
-import javax.swing.SwingUtilities;
-
 import com.atlassian.event.api.EventPublisher;
 import com.atlassian.event.config.ListenerHandlersConfiguration;
-import com.atlassian.event.internal.*;
-import com.atlassian.event.spi.EventDispatcher;
-import com.atlassian.event.spi.EventExecutorFactory;
+import com.atlassian.event.internal.AnnotatedMethodsListenerHandler;
+import com.atlassian.event.internal.LockFreeEventPublisher;
 import com.atlassian.event.spi.ListenerHandler;
-import krum.weaponm.event.SwingEventListener;
-import krum.weaponm.event.WeaponMEventDispatcher;
-import krum.weaponm.plugin.js.RhinoPluginManager;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
 import krum.jtx.ScrollbackBuffer;
 import krum.weaponm.database.DatabaseManager;
 import krum.weaponm.emulation.Emulation;
+import krum.weaponm.event.SwingEventListener;
+import krum.weaponm.event.WeaponMEventDispatcher;
 import krum.weaponm.gui.GUI;
 import krum.weaponm.network.NetworkManager;
+import krum.weaponm.plugin.PluginManager;
+import krum.weaponm.plugin.js.RhinoPluginManager;
 import krum.weaponm.script.ScriptManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-import static java.util.Arrays.asList;
+import javax.swing.*;
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
 
 // threads to think about: edt, timer thread, network thread
 // network thread lifecycle is completely bounded by database reference lifecycle
     public final DatabaseManager dbm;
     public final GUI gui;
     public final RhinoPluginManager pluginManager;
+    public final Injector injector;
 
     public void shutdown() {
         dbm.close(); // kills network and scripts
 
     protected WeaponM() throws IOException, ClassNotFoundException {
         log.info("Weapon M started {}", new Date());
+
         eventPublisher = createEventPublisher();
-        pluginManager = new RhinoPluginManager();
+        pluginManager = new RhinoPluginManager(this);
         buffer = new ScrollbackBuffer(80, AppSettings.getBufferLines());
         emulation = new Emulation(this);
         network = new NetworkManager(this);
         scripts = new ScriptManager(this);
         dbm = new DatabaseManager(this);
         gui = new GUI(this);
+
+        this.injector = Guice.createInjector(new AbstractModule() {
+
+            @Override
+            protected void configure() {
+                bind(EventPublisher.class).toInstance(eventPublisher);
+                bind(PluginManager.class).toInstance(pluginManager);
+                bind(ScrollbackBuffer.class).toInstance(buffer);
+                bind(Emulation.class).toInstance(emulation);
+                bind(NetworkManager.class).toInstance(network);
+                bind(ScriptManager.class).toInstance(scripts);
+                bind(DatabaseManager.class).toInstance(dbm);
+                bind(GUI.class).toInstance(gui);
+            }
+        });
     }
 
     public static void main(final String[] args) {

File src/krum/weaponm/gui/ActionManager.java

 
         for (Class<? extends Script> clazz : classes) {
             try {
-                Script script = clazz.newInstance();
+                Script script = gui.weapon.injector.getInstance(clazz);
                 String menuPath = script.getMenuPath();
                 String[] split = menuPath.split("[\\|/]", 2);
                 if (split.length == 1) {

File src/krum/weaponm/gui/MainWindow.java

 		this.gui = gui;
 		setIconImage(gui.getIcon().getImage());
 		setTitle("Weapon M");
-		terminalPanel = new TerminalPanel(gui.weapon.buffer, gui.weapon.network);
+		terminalPanel = new TerminalPanel(gui.weapon.buffer, gui.weapon.network, gui.weapon.pluginManager);
 		add(terminalPanel);
 
         chatPanel = new ChatPanel(gui);

File src/krum/weaponm/gui/TerminalPanel.java

 import krum.jtx.StickyScrollPane;
 import krum.jtx.VGASoftFont;
 import krum.weaponm.AppSettings;
+import krum.weaponm.gui.overlay.Overlay;
+import krum.weaponm.gui.overlay.OverlayDetector;
 import krum.weaponm.network.NetworkManager;
+import krum.weaponm.plugin.PluginManager;
 
 public class TerminalPanel extends JPanel {
     private static final long serialVersionUID = 1L;
 
     private static final Pattern warpPtn  = Pattern.compile("\\(?([0-9]+)");
     private final Dimension glyphSize;
-    private SelectedWarp selectedWarp;
+    private OverlayUI overlayUI;
     private final NetworkManager networkManager;
+    private final Iterable<OverlayDetector> overlayDetectors;
 
     /**
      * @throws IOException            if there was an error loading the font resource
      * @throws ClassNotFoundException
      */
-    protected TerminalPanel(final Buffer buffer, final NetworkManager networkManager) throws IOException {
+    protected TerminalPanel(final Buffer buffer, final NetworkManager networkManager, PluginManager pluginManager) throws IOException {
         super(new BorderLayout());
         this.networkManager = networkManager;
+        this.overlayDetectors = pluginManager.getModules(OverlayDetector.class);
         SoftFont font;
         if (AppSettings.getGiantFont()) font = new VGASoftFont("/resource/custom18x32.png");
         else font = new VGASoftFont("/resource/vga9x16.png");
 
             @Override
             public void onExit(MouseEvent event) {
-                selectedWarp = null;
+                overlayUI = null;
                 repaint();
             }
         });
         display.addMouseListener(new MouseAdapter() {
             @Override
             public void mouseClicked(MouseEvent e) {
-                if (selectedWarp != null) {
+                if (overlayUI != null) {
                     if (e.getButton() == MouseEvent.BUTTON1) {
 
-                        selectedWarp.actionPerformed(null);
-                        selectedWarp = null;
+                        overlayUI.actionPerformed(null);
+                        overlayUI = null;
                         repaint();
                     } else if (e.getButton() == MouseEvent.BUTTON3) {
-                        selectedWarp.showPopup(display, e.getPoint());
+                        overlayUI.showPopup(display, e.getPoint());
                     }
                 }
             }
     }
 
     private void processHoverOnLine(Buffer buffer, Point bufferPos, String line) {
-        Matcher m = warpPtn.matcher(line);
-        while (m.find()) {
-            if (m.start() <= bufferPos.x && m.end() >= bufferPos.x) {
-
-                final boolean unexplored = m.group(0).startsWith("(");
-                String sector = unexplored ? m.group(0).substring(1) : m.group(0);
-
-                int y = (25 - (buffer.getExtents().height - bufferPos.y)) * glyphSize.height;
-                int x = (unexplored ? m.start() + 1 : m.start()) * glyphSize.width;
-                int width = (m.group(0).length() - (unexplored ? 1 : 0)) * glyphSize.width;
-                int height = glyphSize.height;
-                selectedWarp = new SelectedWarp(new Rectangle(x, y, width, height), Integer.parseInt(sector));
-                repaint();
-                break;
-            }
-        }
+//        for (OverlayDetector detector : overlayDetectors) {
+//            Overlay overlay = detector.detect(line, bufferPos.x);
+//            if (overlay != null) {
+//                int y = (25 - (buffer.getExtents().height - bufferPos.y)) * glyphSize.height;
+//                int x = overlay.getStart() * glyphSize.width;
+//                int width = overlay.getEnd() * glyphSize.width;
+//                int height = glyphSize.height;
+//                overlayUI = new OverlayUI(new Rectangle(x, y, width, height), overlay.getValue());
+//                repaint();
+//                break;
+//            }
+//        }
+//        Matcher m = warpPtn.matcher(line);
+//        while (m.find()) {
+//            if (m.start() <= bufferPos.x && m.end() >= bufferPos.x) {
+//
+//                final boolean unexplored = m.group(0).startsWith("(");
+//                String sector = unexplored ? m.group(0).substring(1) : m.group(0);
+//
+//
+//                break;
+//            }
+//        }
     }
 
     @Override
 
         super.paint(g);
 
-        if (selectedWarp != null) {
-            selectedWarp.paint(g);
+        if (overlayUI != null) {
+            overlayUI.paint(g);
         }
     }
 
         return e.isAltDown() || e.isAltGraphDown() || e.isControlDown() || e.isMetaDown();
     }
 
-    private class SelectedWarp implements ActionListener {
+    private class OverlayUI implements ActionListener {
         private final Rectangle selectionBox;
-        private final int sectorId;
+        private final String value;
         private final JPopupMenu popupMenu;
 
-        private SelectedWarp(Rectangle selectionBox, int sectorId) {
+        private OverlayUI(Rectangle selectionBox, String value) {
             int padding = 5;
             this.selectionBox = new Rectangle(selectionBox.x - padding, selectionBox.y - padding,
                     selectionBox.width + padding * 2, selectionBox.height + padding * 2);
-            this.sectorId = sectorId;
+            this.value = value;
             popupMenu = new JPopupMenu();
 
+
             JMenuItem move = new JMenuItem("Move");
             move.setFont(move.getFont().deriveFont(Font.BOLD));
             move.addActionListener(this);
         }
 
         public void actionPerformed(ActionEvent event) {
-            try {
-                networkManager.write("m" + sectorId + "\r\n");
-            } catch (IOException e) {
-                throw new RuntimeException(e);
-            }
+//            try {
+//                networkManager.write("m" + sectorId + "\r\n");
+//            } catch (IOException e) {
+//                throw new RuntimeException(e);
+//            }
         }
     }
 

File src/krum/weaponm/gui/overlay/Overlay.java

+package krum.weaponm.gui.overlay;
+
+/**
+ */
+public abstract class Overlay<T> {
+    private final int start;
+    private final int end;
+    private final T value;
+
+    public Overlay(int start, int end, T value) {
+        this.start = start;
+        this.end = end;
+        this.value = value;
+    }
+
+    public int getStart() {
+        return start;
+    }
+
+    public int getEnd() {
+        return end;
+    }
+
+    public T getValue() {
+        return value;
+    }
+}

File src/krum/weaponm/gui/overlay/OverlayAction.java

+package krum.weaponm.gui.overlay;
+
+/**
+ * TODO: Document this class / interface here
+ *
+ * @since v5.2
+ */
+public class OverlayAction {
+}

File src/krum/weaponm/gui/overlay/OverlayDetector.java

+package krum.weaponm.gui.overlay;
+
+/**
+ */
+public interface OverlayDetector {
+    Overlay detect(String line, int cursorX);
+}

File src/krum/weaponm/js/JsDoc.java

+package krum.weaponm.js;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+/**
+ *
+ */
+public class JsDoc {
+    private final String description;
+    private final Map<String, Collection<String>> attributes;
+
+    private static final Pattern PARAM_PATTERN = Pattern.compile("([^ ]+)\\s+(.*)");
+
+    private final List<JsDocParam> params;
+
+    public JsDoc(String description) {
+        this(description, Collections.<String, Collection<String>>emptyMap());
+    }
+
+    public JsDoc(String description, Map<String, Collection<String>> attributes) {
+        this.description = description != null ? description : "";
+        this.attributes = attributes;
+        List<JsDocParam> params = newArrayList();
+        if (attributes.containsKey("param")) {
+            for (String line : attributes.get("param")) {
+                Matcher m = PARAM_PATTERN.matcher(line);
+                if (m.matches()) {
+                    params.add(new JsDocParam(m.group(1), m.group(2)));
+                }
+            }
+        }
+        this.params = Collections.unmodifiableList(params);
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public String getAttribute(String key) {
+        final Collection<String> values = attributes.get(key);
+        if (values != null && !values.isEmpty()) {
+            return values.iterator().next();
+        }
+        return null;
+    }
+
+    public Collection<String> getAttributeValues(String key) {
+        return attributes.get(key);
+    }
+
+
+    public List<JsDocParam> getParams() {
+        return params;
+    }
+}

File src/krum/weaponm/js/JsDocParam.java

+package krum.weaponm.js;
+
+/**
+ *
+ */
+public class JsDocParam {
+    private final String name;
+
+    private final String description;
+
+    public JsDocParam(String name, String description) {
+        this.name = name;
+        this.description = description;
+    }
+
+
+    public String getName() {
+        return name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+}

File src/krum/weaponm/js/RhinoRunner.java

+package krum.weaponm.js;
+
+import krum.weaponm.js.JsDoc;
+import krum.weaponm.js.impl.JsDocParser;
+import krum.weaponm.js.impl.WeaponMGlobal;
+import org.apache.commons.io.FileUtils;
+import org.mozilla.javascript.*;
+import org.mozilla.javascript.ast.AstRoot;
+import org.mozilla.javascript.ast.ScriptNode;
+import org.mozilla.javascript.tools.debugger.Main;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Set;
+
+import static com.google.common.collect.Sets.newHashSet;
+import static java.util.Collections.unmodifiableSet;
+
+/**
+ * Runner for Rhino-based scripts
+ */
+public class RhinoRunner {
+
+    private WeaponMGlobal scope;
+    private final Set<String> functionNames;
+    private final String scriptPath;
+
+    private final Object scriptLock = new Object();
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private Main debugger;
+
+    private final String scriptSource;
+    private final JsDoc jsDoc;
+
+    public static interface ScopeCreator {
+
+        WeaponMGlobal create(Context cx);
+    }
+    public RhinoRunner(String scriptPath) {
+
+        this.scriptPath = scriptPath;
+
+        Context cx = Context.enter();
+        try {
+            cx.setOptimizationLevel(-1);
+            cx.setLanguageVersion(Context.VERSION_1_8);
+            scriptSource = FileUtils.readFileToString(new File(scriptPath));
+
+            CompilerEnvirons compilerEnvirons = new CompilerEnvirons();
+            compilerEnvirons.initFromContext(cx);
+            compilerEnvirons.setRecordingLocalJsDocComments(true);
+            compilerEnvirons.setRecordingComments(true);
+            Parser p = new Parser(compilerEnvirons);
+            AstRoot ast = p.parse(scriptSource, scriptPath, 1);
+
+            IRFactory irf = new IRFactory(compilerEnvirons);
+            ScriptNode tree = irf.transformTree(ast);
+            AstRoot root = tree.getAstRoot();
+            if (root.getComments() != null && !root.getComments().isEmpty()
+                    && root.getComments().first().getCommentType() == Token.CommentType.JSDOC) {
+                this.jsDoc = JsDocParser.parse(root.getComments().first().getValue());
+            } else {
+                this.jsDoc = new JsDoc("");
+            }
+
+            Set<String> functionNames = newHashSet();
+            for (int x = 0; x < tree.getFunctionCount(); x++) {
+                functionNames.add(tree.getFunctionNode(x).getName());
+            }
+            this.functionNames = unmodifiableSet(functionNames);
+        } catch (FileNotFoundException e) {
+            throw new RuntimeException(e);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        } finally {
+            // Exit from the context.
+            Context.exit();
+        }
+    }
+
+    public Set<String> getFunctionNames() {
+        return functionNames;
+    }
+
+    public WeaponMGlobal getScope() {
+        return scope;
+    }
+
+    public void load(ScopeCreator scopeCreator) {
+        Context cx = Context.enter();
+        try {
+            cx.setOptimizationLevel(-1);
+            cx.setLanguageVersion(Context.VERSION_1_8);
+
+            scope = scopeCreator.create(cx);
+
+            if (Boolean.getBoolean("debug")) {
+                debugger = new Main("Rhino JavaScript Debugger");
+                debugger.doBreak();
+
+                System.setIn(debugger.getIn());
+                System.setOut(debugger.getOut());
+                System.setErr(debugger.getErr());
+
+
+                scope.setIn(debugger.getIn());
+                scope.setOut(debugger.getOut());
+                scope.setErr(debugger.getErr());
+
+                debugger.attachTo(cx.getFactory());
+
+                debugger.setScope(scope);
+
+                debugger.pack();
+                debugger.setSize(600, 460);
+                debugger.setVisible(true);
+            }
+            else {
+                debugger = null;
+            }
+
+            cx.evaluateString(scope, scriptSource, scriptPath, 1, null);
+        } finally {
+            // Exit from the context.
+            Context.exit();
+        }
+    }
+
+    public boolean callIfExists(String functionName, Object... args) {
+        if (functionNames.contains(functionName)) {
+            call(functionName, args);
+            return true;
+        }
+        return false;
+    }
+
+    public void call(String functionName, Object... args) {
+        synchronized(scriptLock) {
+            Context cx = Context.enter();
+            try {
+                Object prop = ScriptableObject.getProperty(scope, functionName);
+                if (prop != null && prop instanceof Function) {
+                    Object[] wrappedArgs = new Object[args.length];
+                    cx.getWrapFactory().setJavaPrimitiveWrap(false);
+                    for (int x=0; x<args.length; x++)
+                    {
+                        wrappedArgs[x] = cx.getWrapFactory().wrap(cx, scope, args[x], args[x].getClass());
+                    }
+                    try {
+                        cx.callFunctionWithContinuations((Function)prop, scope, wrappedArgs);
+                    } catch (WrappedException ex) {
+                        if (ex.getWrappedException() instanceof RuntimeException) {
+                            throw (RuntimeException) ex.getWrappedException();
+                        } else {
+                            throw new RuntimeException(ex.getWrappedException());
+                        }
+                    } catch (ContinuationPending continuation) {
+                        log.debug("Continuation returned");
+                    }
+
+                }
+                else
+                {
+                    log.warn("Not a function: " + functionName + " in " + scriptPath);
+                }
+            } finally {
+                Context.exit();
+            }
+        }
+    }
+
+    public JsDoc getJsDoc() {
+        return jsDoc;
+    }
+}

File src/krum/weaponm/js/impl/JsDocParser.java

+package krum.weaponm.js.impl;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import krum.weaponm.js.JsDoc;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import static com.google.common.collect.Iterables.filter;
+import static java.util.Arrays.asList;
+
+/**
+ *
+ */
+public class JsDocParser {
+    private static final String DESCRIPTION = "desc";
+    private static final Logger log = LoggerFactory.getLogger(JsDocParser.class);
+    public static final JsDoc EMPTY_JSDOC = new JsDoc("");
+
+    public static JsDoc parse(String content) {
+        if (content == null) {
+            return EMPTY_JSDOC;
+        }
+        if (content.startsWith("/**")) {
+            final String noStars = stripStars(content);
+            Iterable<String> tokens = filter(asList(noStars.split("(?:^|[\\r\\n])\\s*@")), new Predicate<String>() {
+                public boolean apply(String input) {
+                    return input.trim().length() > 0;
+                }
+            });
+
+            ListMultimap<String, String> tags = ArrayListMultimap.create();
+            int x = 0;
+            for (String token : tokens) {
+                if (x++ == 0 && !noStars.startsWith("@")) {
+                    tags.put(DESCRIPTION, token.trim());
+                } else {
+                    int spacePos = token.indexOf(' ');
+                    if (spacePos > -1) {
+                        String value = token.substring(spacePos + 1);
+                        StringBuilder sb = new StringBuilder();
+                        for (String line : value.split("(?:\\r\\n)|\\r|\\n")) {
+                            sb.append(line.trim()).append(" ");
+                        }
+                        tags.put(token.substring(0, spacePos).toLowerCase(), sb.toString().trim());
+                    } else {
+                        tags.put(token.toLowerCase(), token.toLowerCase());
+                    }
+                }
+            }
+            Map<String, Collection<String>> attributes = tags.asMap();
+            Collection<String> descriptions = attributes.remove(DESCRIPTION);
+            return new JsDoc(descriptions != null ? descriptions.iterator().next() : null, attributes);
+        } else {
+            throw new IllegalArgumentException();
+        }
+    }
+
+    static String stripStars(String jsDoc) {
+        String noStartOrEndStars = jsDoc != null ? jsDoc.replaceAll("^\\/\\*\\*|\\*\\/$", "") : "";
+        String result = Pattern.compile("^\\s*\\* ?", Pattern.MULTILINE).matcher(noStartOrEndStars).replaceAll("");
+        return result.trim();
+    }
+}

File src/krum/weaponm/js/impl/ScriptUtils.java

+/*
+ *  Copyright 2006 Hannes Wallnoefer <hannes@helma.at>
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package krum.weaponm.js.impl;
+
+import krum.weaponm.js.impl.ScriptableList;
+import krum.weaponm.js.impl.ScriptableMap;
+import org.mozilla.javascript.*;
+
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A collection of Rhino utility methods.
+ *
+ * This file is copied from RingoJs 0.8.0
+ */
+public class ScriptUtils {
+
+    /**
+     * Coerce/wrap a java object to a JS object, and mask Lists and Maps
+     * as native JS objects.
+     * @param obj the object to coerce/wrap
+     * @param scope the scope
+     * @return the wrapped/masked java object
+     */
+    @SuppressWarnings("unchecked")
+    public static Object javaToJS(Object obj, Scriptable scope) {
+        if (obj instanceof Scriptable) {
+            if (obj instanceof ScriptableObject
+                    && ((Scriptable) obj).getParentScope() == null
+                    && ((Scriptable) obj).getPrototype() == null) {
+                ScriptRuntime.setObjectProtoAndParent((ScriptableObject) obj, scope);
+            }
+            return obj;
+        } else if (obj instanceof List) {
+            return new ScriptableList(scope, (List) obj);
+        } else if (obj instanceof Map) {
+            return new ScriptableMap(scope, (Map) obj);
+        } else {
+            return Context.javaToJS(obj, scope);
+        }
+    }
+
+    /**
+     * Unwrap a JS object to a java object. This is much more conservative than
+     * Context.jsToJava in that it will preserve undefined values.
+     * @param obj a JavaScript value
+     * @return a Java object corresponding to obj
+     */
+    public static Object jsToJava(Object obj) {
+        while (obj instanceof Wrapper) {
+            obj = ((Wrapper) obj).unwrap();
+        }
+        return obj;
+    }
+
+    /**
+     * Return a class prototype, or the object prototype if the class
+     * is not defined.
+     * @param scope the scope
+     * @param className the class name
+     * @return the class or object prototype
+     */
+    public static Scriptable getClassOrObjectProto(Scriptable scope, String className) {
+        Scriptable proto = ScriptableObject.getClassPrototype(scope, className);
+        if (proto == null) {
+            proto = ScriptableObject.getObjectPrototype(scope);
+        }
+        return proto;
+    }
+
+    /**
+     * Make sure that number of arguments is valid.
+     * @param args the argument array
+     * @param min the minimum number of arguments
+     * @param max the maximum number of arguments
+     * @throws IllegalArgumentException if the number of arguments is not valid
+     */
+    public static void checkArguments(Object[] args, int min, int max) {
+        if (min > -1 && args.length < min)
+            throw new IllegalArgumentException();
+        if (max > -1 && args.length > max)
+            throw new IllegalArgumentException();
+    }
+
+    /**
+     * Get an argument as ScriptableObject
+     * @param args the argument array
+     * @param pos the position of the requested argument
+     * @return the argument as ScriptableObject
+     * @throws IllegalArgumentException if the argument can't be converted to a map
+     */
+    public static ScriptableObject getScriptableArgument(Object[] args, int pos, boolean allowNull)
+            throws IllegalArgumentException {
+        if (pos >= args.length || args[pos] == null || args[pos] == Undefined.instance) {
+            if (allowNull) return null;
+            throw ScriptRuntime.constructError("Error", "Argument " + (pos + 1) + " must not be null");
+        } if (args[pos] instanceof ScriptableObject) {
+            return (ScriptableObject) args[pos];
+        }
+        throw ScriptRuntime.constructError("Error", "Can't convert to ScriptableObject: " + args[pos]);
+    }
+
+    /**
+     * Get an argument as string
+     * @param args the argument array
+     * @param pos the position of the requested argument
+     * @return the argument as string
+     */
+    public static String getStringArgument(Object[] args, int pos, boolean allowNull) {
+        if (pos >= args.length || args[pos] == null || args[pos] == Undefined.instance) {
+            if (allowNull) return null;
+            throw ScriptRuntime.constructError("Error", "Argument " + (pos + 1) + " must not be null");
+        }
+        return ScriptRuntime.toString(args[pos].toString());
+    }
+
+    /**
+     * Get an argument as Map
+     * @param args the argument array
+     * @param pos the position of the requested argument
+     * @return the argument as map
+     * @throws IllegalArgumentException if the argument can't be converted to a map
+     */
+    public static Map getMapArgument(Object[] args, int pos, boolean allowNull)
+            throws IllegalArgumentException {
+        if (pos >= args.length || args[pos] == null || args[pos] == Undefined.instance) {
+            if (allowNull) return null;
+            throw ScriptRuntime.constructError("Error", "Argument " + (pos + 1) + " must not be null");
+        } if (args[pos] instanceof Map) {
+            return (Map) args[pos];
+        }
+        throw ScriptRuntime.constructError("Error", "Can't convert to java.util.Map: " + args[pos]);
+    }
+
+    /**
+     * Get an argument as object
+     * @param args the argument array
+     * @param pos the position of the requested argument
+     * @return the argument as object
+     */
+    public static Object getObjectArgument(Object[] args, int pos, boolean allowNull) {
+        if (pos >= args.length || args[pos] == null || args[pos] == Undefined.instance) {
+            if (allowNull) return null;
+            throw ScriptRuntime.constructError("Error", "Argument " + (pos + 1) + " must not be null");
+        }
+        return args[pos];
+    }
+
+    /**
+     * Try to convert an object to an int value, returning the default value if conversion fails.
+     * @param obj the value
+     * @param defaultValue the default value
+     * @return the converted value
+     */
+    public static int toInt(Object obj, int defaultValue) {
+        double d = ScriptRuntime.toNumber(obj);
+        if (d == ScriptRuntime.NaN || (int)d != d) {
+            return defaultValue;
+        }
+        return (int) d;
+    }
+
+
+    /**
+     * Get a snapshot of the current JavaScript evaluation state by creating
+     * an Error object and invoke the function on it passing along any arguments.
+     * Used to invoke console.trace() and friends because implementing this
+     * in JavaScript would mess with the evaluation state.
+     * @param function the function to call
+     * @param args optional arguments to pass to the function.
+     */
+    public static void traceHelper(Function function, Object... args) {
+        Context cx = Context.getCurrentContext();
+        Scriptable scope = ScriptableObject.getTopLevelScope(function);
+        EcmaError error = ScriptRuntime.constructError("Trace", "");
+        WrapFactory wrapFactory = cx.getWrapFactory();
+        Scriptable thisObj = wrapFactory.wrapAsJavaObject(cx, scope, error, null);
+        for (int i = 0; i < args.length; i++) {
+            args[i] = wrapFactory.wrap(cx, scope, args[i], null);
+        }
+        function.call(cx, scope, thisObj, args);
+    }
+
+    /**
+     * Helper for console.assert(). Implemented in Java in order not to
+     * modify the JavaScript stack.
+     * @param condition the condition to test
+     * @param args one or more message parts
+     */
+    public static void assertHelper(Object condition, Object... args) {
+        if (ScriptRuntime.toBoolean(condition)) {
+            return;
+        }
+        // assertion failed
+        String msg = "";
+        if (args.length > 0) {
+            msg = ScriptRuntime.toString(args[0]);
+            Pattern pattern = Pattern.compile("%[sdifo]");
+            for (int i = 1; i < args.length; i++) {
+                Matcher matcher = pattern.matcher(msg);
+                if (matcher.find()) {
+                    msg = matcher.replaceFirst(ScriptRuntime.toString(args[i]));
+                } else {
+                    msg = msg + " " + ScriptRuntime.toString(args[i]);
+                }
+            }
+        }
+        throw ScriptRuntime.constructError("AssertionError", msg);
+    }
+
+}

File src/krum/weaponm/js/impl/ScriptableList.java

+/*
+ *  Copyright 2006 Hannes Wallnoefer <hannes@helma.at>
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package krum.weaponm.js.impl;
+
+import org.mozilla.javascript.*;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * ScriptableList is a wrapper for java.util.List instances that allows developers
+ * to interact with them like it was a native JavaScript array.
+ *
+ * This file is copied from RingoJs 0.8.0
+ */
+public class ScriptableList extends NativeJavaObject {
+
+    List<Object> list;
+    static final String CLASSNAME = "ScriptableList";
+
+    // Set up a custom constructor, for this class is somewhere between a host class and
+    // a native wrapper, for which no standard constructor class exists
+    public static void init(Scriptable scope) throws NoSuchMethodException {
+        BaseFunction ctor = new BaseFunction(scope, ScriptableObject.getFunctionPrototype(scope)) {
+            @Override
+            public Scriptable construct(Context cx, Scriptable scope, Object[] args) {
+                if (args.length > 1) {
+                    throw new EvaluatorException("ScriptableList() requires a java.util.List argument");
+                }
+                return new ScriptableList(scope, args.length == 0 ? null : args[0]);
+            }
+            @Override
+            public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
+                return construct(cx, scope, args);
+            }
+        };
+        ScriptableObject.defineProperty(scope, CLASSNAME, ctor,
+                ScriptableObject.DONTENUM | ScriptableObject.READONLY);
+    }
+
+    /**
+     * Create a ScriptableList wrapper around a java.util.List
+     * @param scope the scope
+     * @param obj the list, possibly wrapped
+     */
+    @SuppressWarnings("unchecked")
+    private ScriptableList(Scriptable scope, Object obj) {
+        this.parent = scope;
+        if (obj instanceof Wrapper) {
+            obj = ((Wrapper) obj).unwrap();
+        }
+        if (obj instanceof List) {
+            this.javaObject = this.list = (List) obj;
+        } else if (obj instanceof Collection) {
+            this.javaObject = this.list = new ArrayList<Object>((Collection<?>) obj);
+        } else if (obj instanceof Map) {
+            this.javaObject = this.list = new ArrayList<Object>(((Map<?,?>)obj).values());
+        } else if (obj == null || obj == Undefined.instance) {
+            this.javaObject = this.list = new ArrayList<Object>();
+        } else {
+            throw new EvaluatorException("Invalid argument to ScriptableList(): " + obj);
+        }
+        this.staticType = this.list.getClass();
+        initMembers();
+        initPrototype(scope);
+    }
+
+
+    /**
+     * Create a ScriptableList wrapper around a java.util.List.
+     * @param scope the scope
+     * @param list the list instance
+     */
+    public ScriptableList(Scriptable scope, List<Object> list) {
+        super(scope, list, list.getClass());
+        this.list = list;
+        initPrototype(scope);
+    }
+
+    /**
+     * Set the prototype to the Array prototype so we can use array methds such as
+     * push, pop, shift, slice etc.
+     * @param scope the global scope for looking up the Array constructor
+     */
+    protected void initPrototype(Scriptable scope) {
+        Scriptable arrayProto = ScriptableObject.getClassPrototype(scope, "Array");
+        if (arrayProto != null) {
+            this.setPrototype(arrayProto);
+        }
+    }
+
+    public void delete(int index) {
+        if (list != null) {
+            try {
+                list.remove(index);
+            } catch (RuntimeException e) {
+                throw Context.throwAsScriptRuntimeEx(e);
+            }
+        } else {
+            super.delete(index);
+        }
+    }
+
+    public Object get(int index, Scriptable start) {
+        if (list == null)
+            return super.get(index, start);
+        try {
+            if (index < 0 || index >= list.size()) {
+                return Undefined.instance;
+            } else {
+                return ScriptUtils.javaToJS(list.get(index), getParentScope());
+            }
+        } catch (RuntimeException e) {
+            throw Context.throwAsScriptRuntimeEx(e);
+        }
+    }
+
+    public boolean has(int index, Scriptable start) {
+        if (list == null)
+            return super.has(index, start);
+        return index >= 0 && index < list.size();
+    }
+
+    public void put(String name, Scriptable start, Object value) {
+        if (list != null && "length".equals(name)) {
+            double d = ScriptRuntime.toNumber(value);
+            long longVal = ScriptRuntime.toUint32(d);
+            if (longVal != d) {
+                String msg = ScriptRuntime.getMessage0("msg.arraylength.bad");
+                throw ScriptRuntime.constructError("RangeError", msg);
+            }
+            int size = list.size();
+            if (longVal > size) {
+                for (int i = size; i < longVal; i++) {
+                    // push nulls as undefined is probably meaningless to java code
+                    list.add(null);
+                }
+            } else if (longVal < size) {
+                for (int i = size - 1; i >= longVal; i--) {
+                    list.remove(i);
+                }
+            }
+        } else {
+            super.put(name, start, value);
+        }
+    }
+
+    public void put(int index, Scriptable start, Object value) {
+        if (list != null) {
+            try {
+                if (index == list.size()) {
+                    list.add(ScriptUtils.jsToJava(value));
+                } else {
+                    list.set(index, ScriptUtils.jsToJava(value));
+                }
+            } catch (RuntimeException e) {
+                Context.throwAsScriptRuntimeEx(e);
+            }
+        } else {
+            super.put(index, start, value);
+        }
+    }
+
+    public Object get(String name, Scriptable start) {
+        if ("length".equals(name) && list != null) {
+            return new Integer(list.size());
+        }
+        return super.get(name, start);
+    }
+
+    public Object[] getIds() {
+        if (list == null)
+            return super.getIds();
+        int size = list.size();
+        Object[] ids = new Object[size];
+        for (int i = 0; i < size; ++i) {
+            ids[i] = new Integer(i);
+        }
+        return ids;
+    }
+
+    public String toString() {
+        if (list == null)
+            return super.toString();
+        return list.toString();
+    }
+
+    public Object getDefaultValue(Class typeHint) {
+        return toString();
+    }
+
+    public Object unwrap() {
+        return list;
+    }
+
+    public List getList() {
+        return list;
+    }
+
+    public String getClassName() {
+        return CLASSNAME;
+    }
+}

File src/krum/weaponm/js/impl/ScriptableMap.java

+/*
+ *  Copyright 2006 Hannes Wallnoefer <hannes@helma.at>
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package krum.weaponm.js.impl;
+
+import org.mozilla.javascript.*;
+
+import java.util.Map;
+import java.util.HashMap;
+
+/**
+ * ScriptableMap is a wrapper for java.util.Map instances that allows developers
+ * to interact with them as if it were a native JavaScript object.
+ *
+ * This file is copied from RingoJs 0.8.0
+ */
+public class ScriptableMap extends NativeJavaObject {
+
+    boolean reflect;
+    Map map;
+    final static String CLASSNAME = "ScriptableMap";
+
+    // Set up a custom constructor, for this class is somewhere between a host class and
+    // a native wrapper, for which no standard constructor class exists
+    public static void init(Scriptable scope) throws NoSuchMethodException {
+        BaseFunction ctor = new BaseFunction(scope, ScriptableObject.getFunctionPrototype(scope)) {
+            @Override
+            public Scriptable construct(Context cx, Scriptable scope, Object[] args) {
+                boolean reflect = false;
+                if (args.length > 2) {
+                    throw new EvaluatorException("ScriptableMap() called with too many arguments");
+                } if (args.length == 2) {
+                    reflect = ScriptRuntime.toBoolean(args[1]);
+                }
+                return new ScriptableMap(scope, args.length == 0 ? null : args[0], reflect);
+            }
+            @Override
+            public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
+                return construct(cx, scope, args);
+            }
+        };
+        ScriptableObject.defineProperty(scope, CLASSNAME, ctor,
+                ScriptableObject.DONTENUM | ScriptableObject.READONLY);
+    }
+
+    @SuppressWarnings("unchecked")
+    private ScriptableMap(Scriptable scope, Object obj, boolean reflect) {
+        this.parent = scope;
+        this.reflect = reflect;
+        if (obj instanceof Wrapper) {
+            obj = ((Wrapper) obj).unwrap();
+        }
+        if (obj instanceof Map) {
+            this.map = (Map) obj;
+        } else if (obj == null || obj == Undefined.instance) {
+            this.map = new HashMap();
+        } else if (obj instanceof Scriptable) {
+            this.map = new HashMap();
+            Scriptable s = (Scriptable) obj;
+            Object[] ids = s.getIds();
+            for (Object id: ids) {
+                if (id instanceof String) {
+                    map.put(id, s.get((String)id, s));
+                } else if (id instanceof Number) {
+                    map.put(id, s.get(((Number)id).intValue(), s));
+                }
+            }
+        } else {
+            throw new EvaluatorException("Invalid argument to ScriptableMap(): " + obj);
+        }
+        this.javaObject = this.map;
+        this.staticType = this.map.getClass();
+        initMembers();
+        initPrototype(scope);
+
+    }
+
+    public ScriptableMap(Scriptable scope, Map map) {
+        super(scope, map, map.getClass());
+        this.map = map;
+        initPrototype(scope);
+    }
+
+    /**
+     * Set the prototype to the Array prototype so we can use array methds such as
+     * push, pop, shift, slice etc.
+     * @param scope the global scope for looking up the Array constructor
+     */
+    protected void initPrototype(Scriptable scope) {
+        Scriptable arrayProto = ScriptableObject.getClassPrototype(scope, "Object");
+        if (arrayProto != null) {
+            this.setPrototype(arrayProto);
+        }
+    }
+
+    public Object get(String name, Scriptable start) {
+        if (map == null || (reflect && super.has(name, start))) {
+            return super.get(name, start);
+        }
+        return getInternal(name);
+    }
+
+    public Object get(int index, Scriptable start) {
+        if (map == null) {
+            return super.get(index, start);
+        }
+        return getInternal(new Integer(index));
+    }
+
+    private Object getInternal(Object key) {
+        Object value = map.get(key);
+        if (value == null) {
+            return Scriptable.NOT_FOUND;
+        }
+        return ScriptUtils.javaToJS(value, getParentScope());
+    }
+
+    public boolean has(String name, Scriptable start) {
+        if (map == null || (reflect && super.has(name, start))) {
+            return super.has(name, start);
+        } else {
+            return map.containsKey(name);
+        }
+    }
+
+    public boolean has(int index, Scriptable start) {
+        if (map == null) {
+            return super.has(index, start);
+        } else {
+            return map.containsKey(new Integer(index));
+        }
+    }
+
+    public void put(String name, Scriptable start, Object value) {
+        if (map == null || (reflect && super.has(name, start))) {
+            super.put(name, start, value);
+        } else {
+            putInternal(name, value);
+        }
+    }
+
+    public void put(int index, Scriptable start, Object value) {
+        if (map == null) {
+             super.put(index, start, value);
+         } else {
+             putInternal(new Integer(index), value);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private void putInternal(Object key, Object value) {
+        try {
+            map.put(key, ScriptUtils.jsToJava(value));
+        } catch (RuntimeException e) {
+            Context.throwAsScriptRuntimeEx(e);
+        }
+    }
+
+    public void delete(String name) {
+        if (map != null) {
+            try {
+                map.remove(name);
+            } catch (RuntimeException e) {
+                Context.throwAsScriptRuntimeEx(e);
+            }
+        } else {
+            super.delete(name);
+        }
+    }
+
+    public void delete(int index) {
+        if (map != null) {
+            try {
+                map.remove(new Integer(index));
+            } catch (RuntimeException e) {
+                Context.throwAsScriptRuntimeEx(e);
+            }
+        } else {
+            super.delete(index);
+        }
+    }
+
+    public Object[] getIds() {
+        if (map == null) {
+            return super.getIds();
+        } else {
+            return map.keySet().toArray();
+        }
+    }
+
+    public String toString() {
+        if (map == null)
+            return super.toString();
+        return map.toString();
+    }
+
+    public Object getDefaultValue(Class typeHint) {
+        return toString();
+    }
+
+    public Object unwrap() {
+        return map;
+    }
+
+    public Map getMap() {
+        return map;
+    }
+
+    public String getClassName() {
+        return CLASSNAME;
+    }
+}

File src/krum/weaponm/js/impl/WeaponMGlobal.java

+package krum.weaponm.js.impl;
+
+import krum.weaponm.database.Database;
+import krum.weaponm.emulation.Emulation;
+import krum.weaponm.network.NetworkManager;
+import krum.weaponm.script.NetworkLockedException;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Function;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+import org.mozilla.javascript.tools.ToolErrorReporter;
+import org.mozilla.javascript.tools.shell.Environment;
+import org.mozilla.javascript.tools.shell.Global;
+
+import java.io.IOException;
+import java.io.PrintStream;
+
+/**
+ *
+ */
+public class WeaponMGlobal extends Global {
+
+    private final NetworkManager networkManager;
+    private final Emulation emulation;
+
+    public WeaponMGlobal(NetworkManager networkManager, Database database, Emulation emulation, Context cx) {
+        this.networkManager = networkManager;
+        this.emulation = emulation;
+
+        // Define some global functions particular to the shell. Note
+        // that these functions are not part of ECMA.
+        initStandardObjects(cx, false);
+        String[] names = {
+                "print",
+                "readFile",
+                "readUrl",
+                "runCommand",
+                "spawn"
+        };
+        defineFunctionProperties(names, Global.class,
+                ScriptableObject.DONTENUM);
+
+        String[] localNames = {
+                "send",
+                "printAnsi"
+
+        };
+        defineFunctionProperties(localNames, WeaponMGlobal.class, ScriptableObject.DONTENUM);
+
+        // Set up "environment" in the global scope to provide access to the
+        // System environment variables.
+        Environment.defineClass(this);
+        Environment environment = new Environment(this);
+        defineProperty("environment", environment,
+                ScriptableObject.DONTENUM);
+
+        defineProperty("db", database, ScriptableObject.DONTENUM);
+    }
+
+    public static void send(Context cx, final Scriptable thisObj, Object[] args, Function funObj) throws IOException {
+        final WeaponMGlobal global = getInstance(funObj);
+
+        String text = Context.toString(args[0]);
+        text = text.replaceAll("\\*", "\r\n");
+        global.sendText(text);
+    }
+
+    public static void printAnsi(Context cx, final Scriptable thisObj, Object[] args, Function funObj) throws NetworkLockedException {
+        final WeaponMGlobal global = getInstance(funObj);
+
+        String text = Context.toString(args[0]);
+        global.emulation.printAnsi(text);
+    }
+
+    protected static <T> T getArg(Scriptable config, Scriptable thisObj, String propertyName, T defValue, Class<? extends T> type) {
+
+        Object value = config.get(propertyName, thisObj);
+        if (value == null) {
+            return defValue;
+        }
+        if (Integer.class.isAssignableFrom(type)) {
+            return (T)  Integer.valueOf(value.toString());
+        } else if (String.class.equals(type)) {
+            return (T) value.toString();
+        } else {
+            return (T) value;
+        }
+    }
+
+    public void sendText(String text) throws IOException {
+        networkManager.write(text);
+    }
+
+    @Override
+    public boolean has(String name, Scriptable start) {
+        return super.has(name, start);
+    }
+
+    /**
+     * Print the string values of its arguments.
+     * <p/>
+     * This method is defined as a JavaScript function.
+     * Note that its arguments are of the "varargs" form, which
+     * allows it to handle an arbitrary number of arguments
+     * supplied to the JavaScript function.
+     */
+    public static Object print(Context cx, Scriptable thisObj,
+                               Object[] args, Function funObj) {
+        PrintStream out = getInstance(funObj).getOut();
+        for (int i = 0; i < args.length; i++) {
+            if (i > 0)
+                out.print(" ");
+
+            // Convert the arbitrary JavaScript value into a string form.
+            String s = Context.toString(args[i]);
+
+            out.print(s);
+        }
+        out.println();
+        return Context.getUndefinedValue();
+    }
+
+    private static WeaponMGlobal getInstance(Function function) {
+        Scriptable scope = function.getParentScope();
+        if (!(scope instanceof WeaponMGlobal))
+            throw reportRuntimeError("msg.bad.shell.function.scope",
+                    String.valueOf(scope));
+        return (WeaponMGlobal) scope;
+    }
+
+    protected static RuntimeException reportRuntimeError(String msgId) {
+        String message = ToolErrorReporter.getMessage(msgId);
+        return Context.reportRuntimeError(message);
+    }
+
+    protected static RuntimeException reportRuntimeError(String msgId, String msgArg) {
+        String message = ToolErrorReporter.getMessage(msgId, msgArg);
+        return Context.reportRuntimeError(message);
+    }
+
+}

File src/krum/weaponm/plugin/AbstractModuleDescriptor.java

+package krum.weaponm.plugin;
+
+import com.google.common.base.Supplier;
+import krum.weaponm.plugin.ModuleDescriptor;
+import krum.weaponm.script.Script;
+
+/**
+ */
+public abstract class AbstractModuleDescriptor<T> implements ModuleDescriptor<T> {
+
+    private final String key;
+    private final String pluginKey;
+    private final Supplier<T> moduleSupplier;
+
+    public AbstractModuleDescriptor(String key, String pluginKey, Supplier<T> moduleSupplier) {
+        this.key = key;
+        this.pluginKey = pluginKey;
+        this.moduleSupplier = moduleSupplier;
+    }
+
+    @Override
+    public String getKey() {
+        return key;
+    }
+
+    @Override
+    public String getPluginKey() {
+        return pluginKey;
+    }
+
+    @Override
+    public String getFullKey() {
+        return pluginKey + ":" + key;
+    }
+
+    @Override
+    public T get() {
+        return moduleSupplier.get();
+    }
+}

File src/krum/weaponm/plugin/ModuleDescriptor.java

+package krum.weaponm.plugin;
+
+import com.google.common.base.Supplier;
+
+/**
+ */
+public interface ModuleDescriptor<T> extends Supplier<T> {
+    String getKey();
+
+    String getName();
+
+    String getDescription();
+
+    String getPluginKey();
+
+    String getFullKey();
+}

File src/krum/weaponm/plugin/Plugin.java

 package krum.weaponm.plugin;
 
+import java.util.Map;
+
 /**
  *
  */
     String getName();
     String getDescription();
 
-    <T> Iterable<T> getModules(Class<T> moduleClass);
+    Map<String, ModuleDescriptor> getDescriptors();
 
-    <T> Iterable<Class<? extends T>> getModuleClasses(Class<T> moduleClass);
+    ModuleDescriptor getDescriptor(String key);
 }

File src/krum/weaponm/plugin/PluginManager.java

     <T> Iterable<T> getModules(Class<T> moduleClass);
 
     <T> Iterable<Class<? extends T>> getModuleClasses(Class<T> moduleClass);
+
+    <T> T getModule(String fullKey);
+
+    ModuleDescriptor getModuleDescriptor(String fullKey);
+
 }

File src/krum/weaponm/plugin/js/RhinoPlugin.java

 import com.google.common.base.Function;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
+import krum.weaponm.plugin.ModuleDescriptor;
 import krum.weaponm.plugin.Plugin;
 import krum.weaponm.plugin.js.module.RhinoScriptFunction;
 import krum.weaponm.script.Script;
     private final String name;
     private final String description;
 
-    private final Map<Class<?>, Function<File, Class<?>>> moduleFunctions;
+    private final Map<Class<?>, Function<File, ModuleDescriptor<?>>> moduleFunctions;
 
     RhinoPlugin(File baseDir) {
 
     }
 
     @Override
-    public <T> Iterable<T> getModules(Class<T> moduleClass) {
-        return Iterables.transform(getModuleClasses(moduleClass), new Function<Class<? extends T>, T>() {
-            public T apply(Class<? extends T> input) {
-                try {
-                    // todo: handle errors better here
-                    return input.newInstance();
-                } catch (InstantiationException e) {
-                    throw new RuntimeException(e);
-                } catch (IllegalAccessException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-        });
-    }
-
-    @Override
     public <T> Iterable<Class<? extends T>> getModuleClasses(Class<T> moduleClass) {
         final List<Class<? extends T>> modules = newArrayList();
         final Function<File, Class<?>> moduleFunction = moduleFunctions.get(moduleClass);

File src/krum/weaponm/plugin/js/RhinoPluginManager.java

 
 import com.atlassian.util.concurrent.CopyOnWriteMap;
 import com.google.common.base.Function;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
 import com.google.common.collect.Iterables;
 import krum.weaponm.AppSettings;
+import krum.weaponm.WeaponM;
 import krum.weaponm.plugin.Plugin;
 import krum.weaponm.plugin.PluginManager;
 import org.apache.commons.io.FileUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.inject.Inject;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
-import java.util.ArrayList;
 import java.util.Collections;
-import java.util.List;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.zip.ZipEntry;
-import java.util.zip.ZipException;
 import java.util.zip.ZipFile;
 
 import static com.google.common.collect.Iterables.transform;
 
     private final Map<String, Plugin> plugins;
     private final File pluginsDir;
+
+    private final LoadingCache<Class<?>, Iterable<Class<?>>> moduleClassesCache = CacheBuilder.newBuilder()
+            .build(new CacheLoader<Class<?>, Iterable<Class<?>>>() {
+                @Override
+                public Iterable<Class<?>> load(final Class<?> moduleClass) throws Exception {
+                    return Iterables.concat(
+                            transform(plugins.values(), new Function<Plugin, Iterable<Class<?>>>() {
+                                @Override
+                                public Iterable<Class<?>> apply(Plugin input) {
+                                    return transform(input.getModuleClasses(moduleClass), new Function<Class<?>, Class<?>>() {
+                                        @Override
+                                        public Class<?> apply(Class<?> input) {
+                                            return input;
+                                        }
+                                    });
+                                }
+                            })
+                    );
+                }
+            });
+
+    private final LoadingCache<Class<?>, Iterable<?>> modulesCache = CacheBuilder.newBuilder()
+            .build(new CacheLoader<Class<?>, Iterable<?>>() {
+                @Override
+                public Iterable<?> load(final Class<?> moduleClass) throws Exception {
+                    return transform(moduleClassesCache.get(moduleClass), new Function<Class<?>, Object>() {
+                        @Override
+                        public Object apply(Class<?> input) {
+                            try {
+                                return input.newInstance();
+                            } catch (InstantiationException e) {
+                                throw new RuntimeException(e);
+                            } catch (IllegalAccessException e) {
+                                throw new RuntimeException(e);
+                            }
+                        }
+                    });
+                }
+            });
     private static final Logger log = LoggerFactory.getLogger(RhinoPluginManager.class);
 
-    public RhinoPluginManager() {
+    public RhinoPluginManager(WeaponM weaponM) {
         plugins = CopyOnWriteMap.<String, Plugin>builder().newHashMap();
 
         File homeDir = AppSettings.getHomeDirectory();
                 }
 
                 File outputFile = new File(pluginDir, entry.getName());
-                if (!outputFile.getParentFile().exists()){
+                if (!outputFile.getParentFile().exists()) {
                     outputFile.getParentFile().mkdirs();
                 }
 
     @Override
     public void rescan() {
         plugins.clear();
+        moduleClassesCache.invalidateAll();
+        modulesCache.invalidateAll();
         loadFromDirectory();
     }
 
 
     @Override
     public <T> Iterable<T> getModules(final Class<T> moduleClass) {
-        return Iterables.concat(
-                transform(plugins.values(), new Function<Plugin, Iterable<T>>() {
-                    @Override
-                    public Iterable<T> apply(Plugin input) {
-                        return input.getModules(moduleClass);
-                    }
-                })
-        );
+        return new Iterable<T>() {
+            @Override
+            public Iterator<T> iterator() {
+                return (Iterator<T>) modulesCache.getUnchecked(moduleClass).iterator();
+            }
+        };
     }
 
     @Override
     public <T> Iterable<Class<? extends T>> getModuleClasses(final Class<T> moduleClass) {
-        return Iterables.concat(
-                transform(plugins.values(), new Function<Plugin, Iterable<Class<? extends T>>>() {
+        return new Iterable<Class<? extends T>>() {
+            @Override
+            public Iterator<Class<? extends T>> iterator() {
+                Iterable<Class<?>> fromIterable = moduleClassesCache.getUnchecked(moduleClass);
+                return transform(fromIterable, new Function<Class<?>, Class<? extends T>>() {
                     @Override
-                    public Iterable<Class<? extends T>> apply(Plugin input) {
-                        return input.getModuleClasses(moduleClass);
+                    public Class<? extends T> apply(Class<?> input) {
+                        return (Class<? extends T>) input;
                     }
-                })
-        );
+                }).iterator();
+            }
+        };
     }
 }

File src/krum/weaponm/plugin/js/module/RhinoScriptFunction.java

 package krum.weaponm.plugin.js.module;
 
 import com.google.common.base.Function;
+import krum.weaponm.plugin.ModuleDescriptor;
 import krum.weaponm.script.Script;
 import krum.weaponm.script.loader.rhino.ScriptClassLoader;
 
 /**
  *
  */
-public class RhinoScriptFunction implements Function<File, Class<?>> {
-    private final ScriptClassLoader scriptClassLoader = new ScriptClassLoader();
+public class RhinoScriptFunction implements Function<File, ModuleDescriptor<Script>> {
     private final File scriptsDir;
 
     public RhinoScriptFunction(File pluginDir) {
     }
 
     @Override
-    public Class<?> apply(File input) {
+    public ModuleDescriptor<Script> apply(File input) {
 
+        return new ScriptModule
         return scriptClassLoader.generateScript(scriptsDir.getAbsolutePath(), input.getAbsolutePath());
     }
 }

File src/krum/weaponm/script/Script.java

 import java.util.LinkedList;
 import java.util.List;
 
+import javax.inject.Inject;
 import javax.swing.*;
 
 import krum.jplex.UnderflowException;
 import krum.weaponm.database.Database;
+import krum.weaponm.database.DatabaseManager;
 import krum.weaponm.database.Sector;
+import krum.weaponm.emulation.Emulation;
+import krum.weaponm.gui.GUI;
 import krum.weaponm.gui.ParametersDialog;
 
+import krum.weaponm.network.NetworkManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
  * {@link #endScript()} to ensure that the resources are closed when the
  * script is unloaded.
  */
-public abstract class Script {
+public abstract class Script implements Comparable<Script> {
 	/** A network newline. */
 	public static final String RETURN = "\r\n"; // suggested by Tweety :P
 	
 	private static final Logger log = LoggerFactory.getLogger(Script.class);
-	private final ScriptManager manager;
 	private final List<Parameter> parameters = new LinkedList<Parameter>();
 	private final StringBuilder burst = new StringBuilder();
 	private volatile boolean initialized = false;
 
-	public Script() {
-		manager = ScriptManager.getManagerForScript(this);
-	}
-	
+    @Inject
+    protected ScriptManager manager;
+    
+    @Inject
+    protected GUI gui;
+    
+    @Inject
+    protected NetworkManager networkManager;
+    
+    @Inject
+    protected DatabaseManager dbm;
+