first commit
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
package org.openautonomousconnection.luascript.runtime;
|
||||
|
||||
import org.openautonomousconnection.luascript.events.LuaEventDispatcher;
|
||||
import org.openautonomousconnection.luascript.events.UiEventRegistry;
|
||||
import org.openautonomousconnection.luascript.hosts.DomHost;
|
||||
import org.openautonomousconnection.luascript.hosts.EventHost;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Scans the DOM for attributes in variant B form: on:EVENT="path.to.handler"
|
||||
* and binds them automatically via LuaEventDispatcher + EventHost subscriptions.
|
||||
*/
|
||||
public final class LuaDomBinder {
|
||||
|
||||
private static final String ATTR_PREFIX = "on:";
|
||||
|
||||
private final DomHost dom;
|
||||
private final EventHost eventHost;
|
||||
private final LuaEventDispatcher dispatcher;
|
||||
|
||||
public LuaDomBinder(DomHost dom, EventHost eventHost, LuaEventDispatcher dispatcher) {
|
||||
this.dom = Objects.requireNonNull(dom, "dom");
|
||||
this.eventHost = Objects.requireNonNull(eventHost, "eventHost");
|
||||
this.dispatcher = Objects.requireNonNull(dispatcher, "dispatcher");
|
||||
}
|
||||
|
||||
public void bindAll() {
|
||||
for (String elementId : dom.getAllElementIds()) {
|
||||
Map<String, String> attrs = dom.getAttributes(elementId);
|
||||
if (attrs == null || attrs.isEmpty()) continue;
|
||||
|
||||
for (Map.Entry<String, String> e : attrs.entrySet()) {
|
||||
String attr = e.getKey();
|
||||
if (attr == null) continue;
|
||||
|
||||
String a = attr.trim().toLowerCase();
|
||||
if (!a.startsWith(ATTR_PREFIX)) continue;
|
||||
|
||||
String eventName = UiEventRegistry.normalize(a.substring(ATTR_PREFIX.length()));
|
||||
String handlerPath = (e.getValue() == null) ? "" : e.getValue().trim();
|
||||
if (handlerPath.isEmpty()) {
|
||||
throw new IllegalStateException("Empty handler for attribute '" + attr + "' on element '" + elementId + "'");
|
||||
}
|
||||
|
||||
dispatcher.bind(elementId, eventName, handlerPath);
|
||||
eventHost.addListener(elementId, eventName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.openautonomousconnection.luascript.runtime;
|
||||
|
||||
import org.openautonomousconnection.luascript.events.LuaEventDispatcher;
|
||||
import org.openautonomousconnection.luascript.events.UiEvent;
|
||||
import org.openautonomousconnection.luascript.events.UiEventRegistry;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Single entry point for the host to forward events into Lua.
|
||||
*/
|
||||
public final class LuaEventRouter {
|
||||
|
||||
private final LuaEventDispatcher dispatcher;
|
||||
|
||||
public LuaEventRouter(LuaEventDispatcher dispatcher) {
|
||||
this.dispatcher = Objects.requireNonNull(dispatcher, "dispatcher");
|
||||
}
|
||||
|
||||
public boolean emit(String elementId, String eventName, Map<String, Object> data) {
|
||||
Objects.requireNonNull(elementId, "elementId");
|
||||
Objects.requireNonNull(eventName, "eventName");
|
||||
|
||||
String evt = UiEventRegistry.normalize(eventName);
|
||||
Map<String, Object> payload = (data == null) ? Collections.emptyMap() : data;
|
||||
return dispatcher.dispatch(new UiEvent(elementId, evt, payload));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.openautonomousconnection.luascript.runtime;
|
||||
|
||||
import org.luaj.vm2.Globals;
|
||||
import org.openautonomousconnection.luascript.events.LuaEventDispatcher;
|
||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
||||
import org.openautonomousconnection.luascript.security.LuaExecutionPolicy;
|
||||
import org.openautonomousconnection.luascript.security.LuaSecurityManager;
|
||||
import org.openautonomousconnection.luascript.tables.console.ConsoleTable;
|
||||
import org.openautonomousconnection.luascript.tables.DomTable;
|
||||
import org.openautonomousconnection.luascript.tables.EventsTable;
|
||||
import org.openautonomousconnection.luascript.tables.UiTable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* High-level entry point for wiring Lua tables + bootstrap + event routing.
|
||||
*/
|
||||
public final class LuaRuntime implements AutoCloseable {
|
||||
|
||||
private final Globals globals;
|
||||
private final HostServices services;
|
||||
|
||||
private final LuaSecurityManager securityManager;
|
||||
private final LuaEventDispatcher dispatcher;
|
||||
private final LuaEventRouter eventRouter;
|
||||
|
||||
public LuaRuntime(Globals globals, HostServices services) {
|
||||
this(globals, services, LuaExecutionPolicy.uiDefault());
|
||||
}
|
||||
|
||||
public LuaRuntime(Globals globals, HostServices services, LuaExecutionPolicy policy) {
|
||||
this.globals = Objects.requireNonNull(globals, "globals");
|
||||
this.services = Objects.requireNonNull(services, "services");
|
||||
Objects.requireNonNull(policy, "policy");
|
||||
|
||||
this.securityManager = new LuaSecurityManager();
|
||||
this.dispatcher = new LuaEventDispatcher(globals, securityManager, policy);
|
||||
this.eventRouter = new LuaEventRouter(dispatcher);
|
||||
}
|
||||
|
||||
public Globals globals() {
|
||||
return globals;
|
||||
}
|
||||
|
||||
public LuaEventRouter eventRouter() {
|
||||
return eventRouter;
|
||||
}
|
||||
|
||||
public void installStdTables(boolean overwrite) {
|
||||
new UiTable().inject(globals, services, overwrite);
|
||||
new ConsoleTable().inject(globals, services, overwrite);
|
||||
new EventsTable(dispatcher).inject(globals, services, overwrite);
|
||||
new DomTable().inject(globals, services, overwrite);
|
||||
}
|
||||
|
||||
public void bootstrapFromDom() {
|
||||
LuaScriptBootstrap.bootstrap(globals, services, dispatcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
securityManager.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.openautonomousconnection.luascript.runtime;
|
||||
|
||||
import org.luaj.vm2.Globals;
|
||||
import org.openautonomousconnection.luascript.events.LuaEventDispatcher;
|
||||
import org.openautonomousconnection.luascript.hosts.DomHost;
|
||||
import org.openautonomousconnection.luascript.hosts.EventHost;
|
||||
import org.openautonomousconnection.luascript.hosts.HostServices;
|
||||
import org.openautonomousconnection.luascript.hosts.ResourceHost;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Bootstrap that:
|
||||
* 1) Executes all Lua scripts found in DOM (<script type="lua"> and <script type="lua" src="...">)
|
||||
* 2) Scans DOM for on:* handlers and binds them automatically.
|
||||
*/
|
||||
public final class LuaScriptBootstrap {
|
||||
|
||||
private LuaScriptBootstrap() { }
|
||||
|
||||
public static void bootstrap(Globals globals, HostServices services, LuaEventDispatcher dispatcher) {
|
||||
Objects.requireNonNull(globals, "globals");
|
||||
Objects.requireNonNull(services, "services");
|
||||
Objects.requireNonNull(dispatcher, "dispatcher");
|
||||
|
||||
DomHost dom = services.dom().orElseThrow(() -> new IllegalStateException("DomHost not provided"));
|
||||
EventHost eventHost = services.events().orElseThrow(() -> new IllegalStateException("EventHost not provided"));
|
||||
ResourceHost resources = services.resources().orElseThrow(() -> new IllegalStateException("ResourceHost not provided"));
|
||||
|
||||
executeAllScripts(globals, dom, resources);
|
||||
new LuaDomBinder(dom, eventHost, dispatcher).bindAll();
|
||||
}
|
||||
|
||||
private static void executeAllScripts(Globals globals, DomHost dom, ResourceHost resources) {
|
||||
for (String elementId : dom.getAllElementIds()) {
|
||||
String tag = safeLower(dom.getTagName(elementId));
|
||||
if (!"script".equals(tag)) continue;
|
||||
|
||||
Map<String, String> attrs = dom.getAttributes(elementId);
|
||||
if (attrs == null) continue;
|
||||
|
||||
String type = safeLower(attrs.getOrDefault("type", ""));
|
||||
String src = attrs.get("src");
|
||||
|
||||
boolean isLuaByType = "lua".equals(type) || "text/lua".equals(type) || "application/lua".equals(type);
|
||||
if (!isLuaByType) continue; // IMPORTANT: only run scripts explicitly marked as Lua
|
||||
|
||||
boolean hasSrc = src != null && !src.trim().isEmpty();
|
||||
if (hasSrc) {
|
||||
String path = src.trim();
|
||||
try {
|
||||
String code = resources.readText(path);
|
||||
LuaScriptExecutor.execute(globals, code, path);
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException("Failed to load lua script src='" + path + "': " + ex.getMessage(), ex);
|
||||
}
|
||||
} else {
|
||||
String inline = dom.getTextContent(elementId);
|
||||
if (inline == null) inline = "";
|
||||
LuaScriptExecutor.execute(globals, inline, "inline:" + elementId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String safeLower(String s) {
|
||||
return s == null ? "" : s.trim().toLowerCase();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.openautonomousconnection.luascript.runtime;
|
||||
|
||||
import org.luaj.vm2.Globals;
|
||||
import org.luaj.vm2.LuaError;
|
||||
import org.luaj.vm2.LuaValue;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Loads and executes Lua source code in a given Globals environment.
|
||||
*/
|
||||
public final class LuaScriptExecutor {
|
||||
|
||||
private LuaScriptExecutor() { }
|
||||
|
||||
public static void execute(Globals globals, String source, String chunkName) {
|
||||
Objects.requireNonNull(globals, "globals");
|
||||
Objects.requireNonNull(source, "source");
|
||||
Objects.requireNonNull(chunkName, "chunkName");
|
||||
|
||||
try {
|
||||
LuaValue chunk = globals.load(source, chunkName);
|
||||
chunk.call();
|
||||
} catch (LuaError e) {
|
||||
throw new LuaError("Lua error in '" + chunkName + "': " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user