Removed all method reflections

This commit is contained in:
Brendan Robert 2023-08-01 12:17:29 -05:00
parent e21c9ac5e6
commit e867ab2611
10 changed files with 652 additions and 124 deletions

View File

@ -29,6 +29,14 @@
<configuration>
<source>17</source>
<target>17</target>
<annotationProcessorPaths>
<path>
<groupId>org.8bitbunch</groupId>
<artifactId>lawlesslegends</artifactId>
<version>3.0-SNAPSHOT</version>
</path>
</annotationProcessorPaths>
<annotationProcessors>jace.config.InvokableActionAnnotationProcessor</annotationProcessors>
</configuration>
<version>3.11.0</version>
</plugin>
@ -113,18 +121,6 @@
}
</moduleInfoSource>
</module>
<module>
<artifact>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.10.2</version>
</artifact>
<moduleInfoSource>
module reflections {
exports org.reflections;
}
</moduleInfoSource>
</module>
</modules>
<overwriteExistingFiles>true</overwriteExistingFiles>
</configuration>

View File

@ -13,6 +13,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -259,8 +260,8 @@ public class JaceUIController {
private void connectButtons(Node n) {
if (n instanceof Button button) {
Runnable action = Utility.getNamedInvokableAction(button.getText());
button.setOnMouseClicked(evt -> action.run());
Function<Boolean, Boolean> action = Utility.getNamedInvokableAction(button.getText());
button.setOnMouseClicked(evt -> action.apply(false));
} else if (n instanceof Parent parent) {
parent.getChildrenUnmodifiable().forEach(child -> connectButtons(child));
}

View File

@ -30,7 +30,6 @@ import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
@ -64,15 +63,6 @@ import javafx.scene.image.ImageView;
*/
public class Configuration implements Reconfigurable {
private static Method findAnyMethodByName(Class<? extends Reconfigurable> aClass, String m) {
for (Method method : aClass.getMethods()) {
if (method.getName().equals(m)) {
return method;
}
}
return null;
}
static ConfigurableField getConfigurableFieldInfo(Reconfigurable subject, String settingName) {
Field f;
try {
@ -88,15 +78,6 @@ public class Configuration implements Reconfigurable {
return (f != null && !f.shortName().equals("")) ? f.shortName() : longName;
}
public static InvokableAction getInvokableActionInfo(Reconfigurable subject, String actionName) {
for (Method m : subject.getClass().getMethods()) {
if (m.getName().equals(actionName) && m.isAnnotationPresent(InvokableAction.class)) {
return m.getAnnotation(InvokableAction.class);
}
}
return null;
}
public static Optional<ImageView> getChangedIcon() {
return Utility.loadIcon("icon_exclaim.gif").map(ImageView::new);
}
@ -301,13 +282,19 @@ public class Configuration implements Reconfigurable {
return;
}
for (Method m : node.subject.getClass().getMethods()) {
if (!m.isAnnotationPresent(InvokableAction.class)) {
continue;
InvokableActionRegistry registry = InvokableActionRegistry.getInstance();
registry.getStaticMethodNames(node.subject.getClass()).stream().forEach((name) -> {
InvokableAction action = registry.getStaticMethodInfo(name);
if (action != null) {
node.hotkeys.put(name, action.defaultKeyMapping());
}
InvokableAction action = m.getDeclaredAnnotation(InvokableAction.class);
node.hotkeys.put(m.getName(), action.defaultKeyMapping());
}
});
registry.getInstanceMethodNames(node.subject.getClass()).stream().forEach((name) -> {
InvokableAction action = registry.getInstanceMethodInfo(name);
if (action != null) {
node.hotkeys.put(name, action.defaultKeyMapping());
}
});
for (Field f : node.subject.getClass().getFields()) {
// System.out.println("Evaluating field " + f.getName());
@ -530,12 +517,18 @@ public class Configuration implements Reconfigurable {
private static void doApply(ConfigNode node) {
List<String> removeList = new ArrayList<>();
Keyboard.unregisterAllHandlers(node.subject);
node.hotkeys.keySet().stream().forEach((m) -> {
Method method = findAnyMethodByName(node.subject.getClass(), m);
if (method != null) {
InvokableAction action = method.getAnnotation(InvokableAction.class);
for (String code : node.hotkeys.get(m)) {
Keyboard.registerInvokableAction(action, node.subject, method, code);
InvokableActionRegistry registry = InvokableActionRegistry.getInstance();
node.hotkeys.keySet().stream().forEach((name) -> {
InvokableAction action = registry.getStaticMethodInfo(name);
if (action != null) {
for (String code : node.hotkeys.get(name)) {
Keyboard.registerInvokableAction(action, name, registry.getStaticFunction(name), code);
}
}
action = registry.getInstanceMethodInfo(name);
if (action != null) {
for (String code : node.hotkeys.get(name)) {
Keyboard.registerInvokableAction(action, name, registry.getInstanceFunction(name), code);
}
}
});

View File

@ -1,18 +1,5 @@
package jace.config;
import jace.config.Configuration.ConfigNode;
import javafx.beans.Observable;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.util.StringConverter;
import java.io.File;
import java.io.Serializable;
import java.lang.reflect.Field;
@ -25,6 +12,26 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import jace.config.Configuration.ConfigNode;
import javafx.beans.Observable;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.SplitPane;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.util.StringConverter;
public class ConfigurationUIController {
public static final String DELIMITER = "~!~";
@ -177,7 +184,8 @@ public class ConfigurationUIController {
}
private Node buildKeyShortcutRow(ConfigNode node, String actionName, String[] values) {
InvokableAction actionInfo = Configuration.getInvokableActionInfo(node.subject, actionName);
InvokableActionRegistry registry = InvokableActionRegistry.getInstance();
InvokableAction actionInfo = registry.getInstanceMethodInfo(actionName);
if (actionInfo == null) {
return null;
}

View File

@ -0,0 +1,168 @@
package jace.config;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
// Compile-time annotation processor which creates a registry of all static methods annotated with @InvokableAction.
@SupportedSourceVersion(SourceVersion.RELEASE_17)
@SupportedAnnotationTypes("jace.config.InvokableAction")
public class InvokableActionAnnotationProcessor extends AbstractProcessor {
Messager messager;
Map<InvokableAction, ExecutableElement> staticMethods = new HashMap<>();
Map<InvokableAction, ExecutableElement> instanceMethods = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.messager = processingEnv.getMessager();
messager.printMessage(javax.tools.Diagnostic.Kind.NOTE, "InvokableActionAnnotationProcessor init()");
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
messager.printMessage(javax.tools.Diagnostic.Kind.NOTE, "InvokableActionAnnotationProcessor process()");
// Get list of methods annotated with @InvokableAction.
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(InvokableAction.class);
for (Element element : elements) {
if (element.getModifiers().contains(javax.lang.model.element.Modifier.STATIC)) {
// If the annotation method is static, add it to the static method registry.
trackStaticMethod(element);
} else {
// For non-static methods, track in a separate registry.
trackInstanceMethod(element);
}
try {
// Write class that contains static and instance methods.
writeRegistryClass();
} catch (IOException ex) {
messager.printMessage(javax.tools.Diagnostic.Kind.ERROR, "Error writing InvokableActionRegistry.java: " + ex.getMessage());
}
}
return true;
}
private void trackStaticMethod(Element element) {
// Store the method in the static method registry.
staticMethods.put(element.getAnnotation(InvokableAction.class), (ExecutableElement) element);
}
private void trackInstanceMethod(Element element) {
// Store the method in the instance method registry.
instanceMethods.put(element.getAnnotation(InvokableAction.class), (ExecutableElement) element);
}
private String serializeArrayOfStrings(String... strings) {
return Arrays.stream(strings).map(s -> "\"" + s + "\"").collect(Collectors.joining(","));
}
private void serializeInvokableAction(InvokableAction annotation, String variableName, PrintWriter writer) {
writer.append("""
%s = createInvokableAction("%s", "%s", "%s", "%s", %s, %s, new String[] {%s});
""".formatted(
variableName,
annotation.name(),
annotation.category(),
annotation.description(),
annotation.alternatives(),
annotation.consumeKeyEvent(),
annotation.notifyOnRelease(),
serializeArrayOfStrings(annotation.defaultKeyMapping())
));
}
// Write the registry class.
private void writeRegistryClass() throws IOException {
Files.createDirectories(new File("target/generated-sources/jace/config").toPath());
try (PrintWriter writer = new PrintWriter(new FileWriter("target/generated-sources/jace/config/InvokableActionRegistryImpl.java"))) {
writer.write("""
package jace.config;
import java.util.logging.Level;
public class InvokableActionRegistryImpl extends InvokableActionRegistry {
@Override
public void init() {
InvokableAction annotation;
""");
for (Map.Entry<InvokableAction, ExecutableElement> entry : staticMethods.entrySet()) {
InvokableAction annotation = entry.getKey();
ExecutableElement method = entry.getValue();
String packageName = method.getEnclosingElement().getEnclosingElement().toString();
String className = method.getEnclosingElement().getSimpleName().toString();
String fqnClassName = packageName + "." + className;
serializeInvokableAction(annotation, "annotation", writer);
boolean takesBoolenParameter = method.getParameters().size() == 1 && method.getParameters().get(0).asType().toString().equalsIgnoreCase("boolean");
boolean returnsBoolean = method.getReturnType().toString().equalsIgnoreCase("boolean");
writer.write("""
putStaticAction(annotation.name(), %s.class, annotation, (b) -> {
try {
%s %s.%s(%s);
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking %s", ex);
%s
}
});
""".formatted(
fqnClassName,
returnsBoolean ? "return " : "",
fqnClassName,
method.getSimpleName(),
takesBoolenParameter ? "b" : "",
fqnClassName + "." + method.getSimpleName(),
returnsBoolean ? "return false;" : ""
));
}
// Now for the instance methods, do the same except use a biconsumer which takes the instance as well as the boolean parameter.
for (Map.Entry<InvokableAction, ExecutableElement> entry : instanceMethods.entrySet()) {
InvokableAction annotation = entry.getKey();
ExecutableElement method = entry.getValue();
String packageName = method.getEnclosingElement().getEnclosingElement().toString();
String className = method.getEnclosingElement().getSimpleName().toString();
String fqnClassName = packageName + "." + className;
serializeInvokableAction(annotation, "annotation", writer);
boolean takesBoolenParameter = method.getParameters().size() == 1 && method.getParameters().get(0).asType().toString().equalsIgnoreCase("boolean");
boolean returnsBoolean = method.getReturnType().toString().equalsIgnoreCase("boolean");
writer.write("""
putInstanceAction(annotation.name(), %s.class, annotation, (o, b) -> {
try {
%s ((%s) o).%s(%s);
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking %s", ex);
%s
}
});
""".formatted(
fqnClassName,
returnsBoolean ? "return " : "",
fqnClassName,
method.getSimpleName(),
takesBoolenParameter ? "b" : "",
fqnClassName + "." + method.getSimpleName(),
returnsBoolean ? "return false;" : ""
));
}
writer.write("}\n}");
}
}
}

View File

@ -0,0 +1,148 @@
package jace.config;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.logging.Logger;
public abstract class InvokableActionRegistry {
protected static final Logger logger = Logger.getLogger(InvokableActionRegistry.class.getName());
private final Map<Class, Set<String>> staticMethodNames = new HashMap<>();
private final Map<String, InvokableAction> staticMethodInfo = new HashMap<>();
private final Map<String, Function<Boolean, Boolean>> staticMethodCallers = new HashMap<>();
private final Map<Class, Set<String>> instanceMethodNames = new HashMap<>();
private final Map<String, InvokableAction> instanceMethodInfo = new HashMap<>();
private final Map<String, BiFunction<Object, Boolean, Boolean>> instanceMethodCallers = new HashMap<>();
protected static InvokableActionRegistry instance;
public static InvokableActionRegistry getInstance() {
if (instance == null) {
instance = new InvokableActionRegistryImpl();
instance.init();
}
return instance;
}
abstract public void init();
final public void putStaticAction(String name, Class c, InvokableAction action, Consumer<Boolean> caller) {
putStaticAction(name, c, action, (b) -> {
caller.accept(b);
return false;
});
}
final public void putStaticAction(String name, Class c, InvokableAction action, Function<Boolean, Boolean> caller) {
staticMethodInfo.put(name, action);
staticMethodCallers.put(name, caller);
staticMethodNames.computeIfAbsent(c, k -> new TreeSet<>()).add(name);
}
public void putInstanceAction(String name, Class c, InvokableAction action, BiConsumer<Object, Boolean> caller) {
putInstanceAction(name, c, action, (o, b) -> {
caller.accept(o, b);
return false;
});
}
public void putInstanceAction(String name, Class c, InvokableAction action, BiFunction<Object, Boolean, Boolean> caller) {
instanceMethodInfo.put(name, action);
instanceMethodCallers.put(name, caller);
instanceMethodNames.computeIfAbsent(c, k -> new TreeSet<>()).add(name);
}
public Set<String> getStaticMethodNames(Class c) {
// Build a set of all the method names for this class and all its superclasses.
Set<String> result = new TreeSet<>();
Class current = c;
while (current != null) {
result.addAll(staticMethodNames.getOrDefault(current, Collections.EMPTY_SET));
current = current.getSuperclass();
}
return result;
}
public Set<String> getInstanceMethodNames(Class c) {
// Build a set of all the method names for this class and all its superclasses.
Set<String> result = new TreeSet<>();
Class current = c;
while (current != null) {
result.addAll(instanceMethodNames.getOrDefault(current, Collections.EMPTY_SET));
current = current.getSuperclass();
}
return result;
}
public InvokableAction getStaticMethodInfo(String name) {
return staticMethodInfo.get(name);
}
public InvokableAction getInstanceMethodInfo(String name) {
return instanceMethodInfo.get(name);
}
public Function<Boolean, Boolean> getStaticFunction(String name) {
return staticMethodCallers.get(name);
}
public BiFunction<Object, Boolean, Boolean> getInstanceFunction(String name) {
return instanceMethodCallers.get(name);
}
public Set<InvokableAction> getAllStaticActions() {
return new HashSet<>(staticMethodInfo.values());
}
protected InvokableAction createInvokableAction(String name, String category, String description, String alternatives, boolean consumeKeyEvent, boolean notifyOnRelease, String[] defaultKeyMapping) {
return new InvokableAction() {
@Override
public String name() {
return name;
}
@Override
public String category() {
return category;
}
@Override
public String description() {
return description;
}
@Override
public String alternatives() {
return alternatives;
}
@Override
public boolean consumeKeyEvent() {
return consumeKeyEvent;
}
@Override
public boolean notifyOnRelease() {
return notifyOnRelease;
}
@Override
public String[] defaultKeyMapping() {
return defaultKeyMapping;
}
@Override
public Class<? extends java.lang.annotation.Annotation> annotationType() {
return InvokableAction.class;
}
};
}
}

View File

@ -0,0 +1,233 @@
package jace.config;
import java.util.logging.Level;
// NOTE: This is generated code. Do not edit.
public class InvokableActionRegistryImpl extends InvokableActionRegistry {
@Override
public void init() {
InvokableAction annotation;
annotation = createInvokableAction("Resize window", "general", "Resize the screen to 1x/1.5x/2x/3x video size", "Aspect;Adjust screen;Adjust window size;Adjust aspect ratio;Fix screen;Fix window size;Fix aspect ratio;Correct aspect ratio;", true, false, new String[]{"ctrl+shift+a"});
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
try {
jace.EmulatorUILogic.scaleIntegerRatio();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.scaleIntegerRatio", ex);
}
});
annotation = createInvokableAction("Rewind", "General", "Go back 1 second", "Timewarp", true, false, new String[]{"ctrl+shift+Open Bracket"});
putStaticAction(annotation.name(), jace.state.StateManager.class, annotation, (b) -> {
try {
jace.state.StateManager.beKindRewind();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.state.StateManager.beKindRewind", ex);
}
});
annotation = createInvokableAction("Configuration", "general", "Edit emulator configuraion", "Reconfigure;Preferences;Settings;Config", true, false, new String[]{"f4", "ctrl+shift+c"});
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
try {
jace.EmulatorUILogic.showConfig();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.showConfig", ex);
}
});
annotation = createInvokableAction("Load settings", "general", "Load all configuration settings previously saved", "load preferences;revert settings;revert preferences", true, false, new String[]{"meta+ctrl+r"});
putStaticAction(annotation.name(), jace.config.Configuration.class, annotation, (b) -> {
try {
jace.config.Configuration.loadSettings();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.config.Configuration.loadSettings", ex);
}
});
annotation = createInvokableAction("About", "general", "Display about window", "info;credits", true, false, new String[]{"ctrl+shift+."});
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
try {
jace.EmulatorUILogic.showAboutWindow();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.showAboutWindow", ex);
}
});
annotation = createInvokableAction("Record sound", "sound", "Toggles recording (saving) sound output to a file", "", true, false, new String[]{"ctrl+shift+w"});
putStaticAction(annotation.name(), jace.apple2e.Speaker.class, annotation, (b) -> {
try {
jace.apple2e.Speaker.toggleFileOutput();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.apple2e.Speaker.toggleFileOutput", ex);
}
});
annotation = createInvokableAction("BRUN file", "file", "Loads a binary file in memory and executes it. File should end with #06xxxx, where xxxx is the start address in hex", "Execute program;Load binary;Load program;Load rom;Play single-load game", true, false, new String[]{"ctrl+shift+b"});
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
try {
jace.EmulatorUILogic.runFile();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.runFile", ex);
}
});
annotation = createInvokableAction("Save Raw Screenshot", "general", "Save raw (RAM) format of visible screen", "screendump;raw screenshot", true, false, new String[]{"ctrl+shift+z"});
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
try {
jace.EmulatorUILogic.saveScreenshotRaw();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.saveScreenshotRaw", ex);
}
});
annotation = createInvokableAction("Save settings", "general", "Save all configuration settings as defaults", "save preferences;save defaults", true, false, new String[]{"meta+ctrl+s"});
putStaticAction(annotation.name(), jace.config.Configuration.class, annotation, (b) -> {
try {
jace.config.Configuration.saveSettings();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.config.Configuration.saveSettings", ex);
}
});
annotation = createInvokableAction("Toggle fullscreen", "general", "Activate/deactivate fullscreen mode", "fullscreen;maximize", true, false, new String[]{"ctrl+shift+f"});
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
try {
jace.EmulatorUILogic.toggleFullscreen();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.toggleFullscreen", ex);
}
});
annotation = createInvokableAction("Toggle Debug", "debug", "Show/hide the debug panel", "Show Debug;Hide Debug;Inspect", true, false, new String[]{"ctrl+shift+d"});
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
try {
jace.EmulatorUILogic.toggleDebugPanel();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.toggleDebugPanel", ex);
}
});
annotation = createInvokableAction("Refresh screen", "display", "Marks screen contents as changed, forcing full screen redraw", "redraw", true, false, new String[]{"ctrl+shift+r"});
putStaticAction(annotation.name(), jace.core.Video.class, annotation, (b) -> {
try {
jace.core.Video.forceRefresh();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.core.Video.forceRefresh", ex);
}
});
annotation = createInvokableAction("Toggle video mode", "video", "", "Gfx mode;color;b&w;monochrome", true, false, new String[]{"ctrl+shift+g"});
putStaticAction(annotation.name(), jace.apple2e.VideoNTSC.class, annotation, (b) -> {
try {
jace.apple2e.VideoNTSC.changeVideoMode();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.apple2e.VideoNTSC.changeVideoMode", ex);
}
});
annotation = createInvokableAction("Save Screenshot", "general", "Save image of visible screen", "Save image;save framebuffer;screenshot", true, false, new String[]{"ctrl+shift+s"});
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
try {
jace.EmulatorUILogic.saveScreenshot();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.saveScreenshot", ex);
}
});
annotation = createInvokableAction("Paste clipboard", "Keyboard", "", "paste", true, false, new String[]{"Ctrl+Shift+V", "Shift+Insert"});
putStaticAction(annotation.name(), jace.core.Keyboard.class, annotation, (b) -> {
try {
jace.core.Keyboard.pasteFromClipboard();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.core.Keyboard.pasteFromClipboard", ex);
}
});
annotation = createInvokableAction("Open IDE", "development", "Open new IDE window for Basic/Assembly/Plasma coding", "IDE;dev;development;acme;assembler;editor", true, false, new String[]{"ctrl+shift+i"});
putStaticAction(annotation.name(), jace.EmulatorUILogic.class, annotation, (b) -> {
try {
jace.EmulatorUILogic.showIDE();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.EmulatorUILogic.showIDE", ex);
}
});
annotation = createInvokableAction("Up", "joystick", "", "", true, true, new String[]{"up"});
putInstanceAction(annotation.name(), jace.hardware.Joystick.class, annotation, (o, b) -> {
try {
return ((jace.hardware.Joystick) o).joystickUp(b);
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.hardware.Joystick.joystickUp", ex);
return false;
}
});
annotation = createInvokableAction("Open Apple Key", "Keyboard", "", "OA", false, true, new String[]{"Alt"});
putInstanceAction(annotation.name(), jace.core.Keyboard.class, annotation, (o, b) -> {
try {
((jace.core.Keyboard) o).openApple(b);
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.core.Keyboard.openApple", ex);
}
});
annotation = createInvokableAction("Left", "joystick", "", "", true, true, new String[]{"left"});
putInstanceAction(annotation.name(), jace.hardware.Joystick.class, annotation, (o, b) -> {
try {
return ((jace.hardware.Joystick) o).joystickLeft(b);
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.hardware.Joystick.joystickLeft", ex);
return false;
}
});
annotation = createInvokableAction("Right", "joystick", "", "", true, true, new String[]{"right"});
putInstanceAction(annotation.name(), jace.hardware.Joystick.class, annotation, (o, b) -> {
try {
return ((jace.hardware.Joystick) o).joystickRight(b);
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.hardware.Joystick.joystickRight", ex);
return false;
}
});
annotation = createInvokableAction("Closed Apple Key", "Keyboard", "", "CA", false, true, new String[]{"Shortcut", "Meta", "Command"});
putInstanceAction(annotation.name(), jace.core.Keyboard.class, annotation, (o, b) -> {
try {
((jace.core.Keyboard) o).solidApple(b);
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.core.Keyboard.solidApple", ex);
}
});
annotation = createInvokableAction("Down", "joystick", "", "", true, true, new String[]{"down"});
putInstanceAction(annotation.name(), jace.hardware.Joystick.class, annotation, (o, b) -> {
try {
return ((jace.hardware.Joystick) o).joystickDown(b);
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.hardware.Joystick.joystickDown", ex);
return false;
}
});
annotation = createInvokableAction("Pause", "General", "Stops the computer, allowing reconfiguration of core elements", "freeze;halt", true, false, new String[]{"meta+pause", "alt+pause"});
putInstanceAction(annotation.name(), jace.core.Computer.class, annotation, (o, b) -> {
try {
return ((jace.core.Computer) o).pause();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.core.Computer.pause", ex);
return false;
}
});
annotation = createInvokableAction("Cold boot", "general", "Process startup sequence from power-up", "Full reset;reset emulator", true, false, new String[]{"Ctrl+Shift+Backspace", "Ctrl+Shift+Delete"});
putInstanceAction(annotation.name(), jace.core.Computer.class, annotation, (o, b) -> {
try {
((jace.core.Computer) o).invokeColdStart();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.core.Computer.invokeColdStart", ex);
}
});
annotation = createInvokableAction("Warm boot", "general", "Process user-initatiated reboot (ctrl+apple+reset)", "reboot;reset;three-finger-salute;restart", true, false, new String[]{"Ctrl+Ignore Alt+Ignore Meta+Backspace", "Ctrl+Ignore Alt+Ignore Meta+Delete"});
putInstanceAction(annotation.name(), jace.core.Computer.class, annotation, (o, b) -> {
try {
((jace.core.Computer) o).invokeWarmStart();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.core.Computer.invokeWarmStart", ex);
}
});
annotation = createInvokableAction("Toggle Cheats", "General", "", "cheat;Plug-in", true, false, new String[]{"ctrl+shift+m"});
putInstanceAction(annotation.name(), jace.cheat.Cheats.class, annotation, (o, b) -> {
try {
((jace.cheat.Cheats) o).toggleCheats();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.cheat.Cheats.toggleCheats", ex);
}
});
annotation = createInvokableAction("Resume", "General", "Resumes the computer if it was previously paused", "unpause;unfreeze;resume;play", true, false, new String[]{"meta+shift+pause", "alt+shift+pause"});
putInstanceAction(annotation.name(), jace.core.Computer.class, annotation, (o, b) -> {
try {
((jace.core.Computer) o).resume();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error invoking jace.core.Computer.resume", ex);
}
});
}
}

View File

@ -20,13 +20,12 @@ package jace.core;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -96,8 +95,14 @@ public class Keyboard implements Reconfigurable {
private static final Map<KeyCode, Set<KeyHandler>> keyHandlersByKey = new HashMap<>();
private static final Map<Object, Set<KeyHandler>> keyHandlersByOwner = new HashMap<>();
public static void registerInvokableAction(InvokableAction action, Object owner, Method method, String code) {
boolean isStatic = Modifier.isStatic(method.getModifiers());
/**
*
* @param action
* @param owner
* @param method
* @param code
*/
public static void registerInvokableAction(InvokableAction action, Object owner, Function<Boolean, Boolean> method, String code) {
registerKeyHandler(new KeyHandler(code) {
@Override
public boolean handleKeyUp(KeyEvent e) {
@ -105,41 +110,40 @@ public class Keyboard implements Reconfigurable {
if (action == null || !action.notifyOnRelease()) {
return false;
}
// System.out.println("Key up: "+method.toString());
Object returnValue = null;
try {
if (method.getParameterCount() > 0) {
returnValue = method.invoke(isStatic ? null : owner, false);
} else {
returnValue = method.invoke(isStatic ? null : owner);
}
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
Logger.getLogger(Keyboard.class.getName()).log(Level.SEVERE, null, ex);
}
if (returnValue != null) {
return (Boolean) returnValue;
}
return action.consumeKeyEvent();
return method.apply(false) || action.consumeKeyEvent();
}
@Override
public boolean handleKeyDown(KeyEvent e) {
// System.out.println("Key down: "+method.toString());
Emulator.withComputer(c -> c.getKeyboard().shiftPressed = e.isShiftDown());
Object returnValue = null;
try {
if (method.getParameterCount() > 0) {
returnValue = method.invoke(isStatic ? null : owner, true);
} else {
returnValue = method.invoke(isStatic ? null : owner);
}
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
Logger.getLogger(Keyboard.class.getName()).log(Level.SEVERE, null, ex);
if (action == null) {
return false;
}
if (returnValue != null) {
return (Boolean) returnValue;
return method.apply(true) || action.consumeKeyEvent();
}
}, owner);
}
public static void registerInvokableAction(InvokableAction action, Object owner, BiFunction<Object, Boolean, Boolean> method, String code) {
registerKeyHandler(new KeyHandler(code) {
@Override
public boolean handleKeyUp(KeyEvent e) {
Emulator.withComputer(c -> c.getKeyboard().shiftPressed = e.isShiftDown());
if (action == null || !action.notifyOnRelease()) {
return false;
}
return action != null ? action.consumeKeyEvent() : null;
return method.apply(owner, false) || action.consumeKeyEvent();
}
@Override
public boolean handleKeyDown(KeyEvent e) {
// System.out.println("Key down: "+method.toString());
Emulator.withComputer(c -> c.getKeyboard().shiftPressed = e.isShiftDown());
if (action == null) {
return false;
}
return method.apply(owner, true) || action.consumeKeyEvent();
}
}, owner);
}

View File

@ -20,8 +20,6 @@ package jace.core;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -30,12 +28,10 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.function.Function;
import jace.config.Configuration;
import jace.config.InvokableAction;
import jace.config.InvokableActionRegistry;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.control.Alert;
@ -406,38 +402,15 @@ public class Utility {
return null;
}
static final Map<InvokableAction, Runnable> allActions = new ConcurrentHashMap<>();
public static Map<InvokableAction, Runnable> getAllInvokableActions() {
synchronized (allActions) {
if (allActions.isEmpty()) {
Configuration.BASE.getTreeAsStream().forEach((Configuration.ConfigNode node) -> {
for (Method m : node.subject.getClass().getMethods()) {
if (m.isAnnotationPresent(InvokableAction.class)) {
allActions.put(m.getAnnotation(InvokableAction.class), () -> {
try {
m.invoke(node.subject);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
Logger.getLogger(Utility.class.getName()).log(Level.SEVERE, null, ex);
}
});
}
}
});
}
}
return allActions;
}
public static Runnable getNamedInvokableAction(String action) {
Map<InvokableAction, Runnable> actions = getAllInvokableActions();
List<InvokableAction> actionsList = new ArrayList(actions.keySet());
public static Function<Boolean, Boolean> getNamedInvokableAction(String action) {
InvokableActionRegistry registry = InvokableActionRegistry.getInstance();
List<InvokableAction> actionsList = new ArrayList(registry.getAllStaticActions());
actionsList.sort((a, b) -> Integer.compare(getActionNameMatch(action, a), getActionNameMatch(action, b)));
// for (InvokableAction a : actionsList) {
// String actionName = a.alternatives() == null ? a.name() : (a.name() + ";" + a.alternatives());
// System.out.println("Score for " + action + " evaluating " + a.name() + ": " + getActionNameMatch(action, a));
// }
return actions.get(actionsList.get(0));
return registry.getStaticFunction(actionsList.get(0).name());
}
private static int getActionNameMatch(String str, InvokableAction action) {

View File

@ -21,6 +21,7 @@ module lawlesslegends {
requires java.desktop;
requires java.datatransfer;
requires java.scripting;
requires static java.compiler;
requires javafx.fxmlEmpty;
requires javafx.fxml;
requires javafx.swing;
@ -46,5 +47,8 @@ module lawlesslegends {
uses javax.sound.sampled.SourceDataLine;
provides javax.annotation.processing.Processor with jace.config.InvokableActionAnnotationProcessor;
exports jace;
exports jace.config;
}