Skip to content

Commit

Permalink
Added tst case for returning Java objects from Lua.
Browse files Browse the repository at this point in the history
  • Loading branch information
skiwi2 committed Aug 13, 2014
1 parent ebbf322 commit 7c64449
Show file tree
Hide file tree
Showing 7 changed files with 445 additions and 1 deletion.
@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.web.*?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<GridPane prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.skiwi.javaluainteroptest.CallbackFunctionsCompositeObjectContentController">
<children>
<SplitPane fx:id="splitPane" dividerPositions="0.6" orientation="VERTICAL">
<items>
<TextArea fx:id="codeTextArea" prefHeight="200.0" prefWidth="200.0" />
<AnchorPane prefHeight="200.0" prefWidth="200.0">
<children>
<SplitPane dividerPositions="0.75" prefHeight="160.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<items>
<AnchorPane>
<children>
<TableView fx:id="callbackTableView" editable="true" prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="30.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<columns>
<TableColumn fx:id="keyTableColumn" prefWidth="150.0" text="Key" />
<TableColumn fx:id="valueTableColumn" editable="true" prefWidth="150.0" text="Value" />
<TableColumn fx:id="resultTableColumn" prefWidth="150.0" text="Result" />
</columns>
</TableView>
<GridPane layoutX="113.0" layoutY="151.0" prefHeight="40.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0">
<columnConstraints>
<ColumnConstraints hgrow="NEVER" minWidth="10.0" percentWidth="33.333" prefWidth="100.0" />
<ColumnConstraints hgrow="NEVER" minWidth="10.0" percentWidth="33.333" prefWidth="100.0" />
<ColumnConstraints hgrow="NEVER" minWidth="10.0" percentWidth="33.333" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<AnchorPane prefHeight="200.0" prefWidth="200.0">
<children>
<Label layoutX="14.0" layoutY="16.0" text="Key: " AnchorPane.leftAnchor="14.0">
<font>
<Font size="15.0" />
</font>
</Label>
<TextField fx:id="keyTextField" layoutX="46.0" layoutY="14.0" prefWidth="100.0" AnchorPane.leftAnchor="46.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="1">
<children>
<Label layoutX="14.0" layoutY="16.0" text="Value: " AnchorPane.leftAnchor="14.0">
<font>
<Font size="15.0" />
</font>
</Label>
<TextField fx:id="valueTextField" layoutX="59.0" layoutY="14.0" prefWidth="100.0" AnchorPane.leftAnchor="59.0" />
</children>
</AnchorPane>
<AnchorPane prefHeight="200.0" prefWidth="200.0" GridPane.columnIndex="2">
<children>
<Button fx:id="addButton" layoutX="132.0" layoutY="14.0" mnemonicParsing="false" onAction="#handleAddButtonAction" prefWidth="75.0" text="Add" AnchorPane.rightAnchor="14.0" />
</children>
</AnchorPane>
</children>
</GridPane>
</children>
</AnchorPane>
<TextArea fx:id="outputTextArea" prefHeight="200.0" prefWidth="200.0" />
</items>
</SplitPane>
</children>
</AnchorPane>
</items>
</SplitPane>
<AnchorPane GridPane.rowIndex="1">
<children>
<Button fx:id="runButton" layoutX="711.0" layoutY="8.0" mnemonicParsing="false" onAction="#handleRunButtonAction" prefWidth="75.0" text="Run" AnchorPane.rightAnchor="14.0" />
</children>
</AnchorPane>
</children>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="ALWAYS" />
<RowConstraints minHeight="10.0" prefHeight="40.0" vgrow="SOMETIMES" />
</rowConstraints>
</GridPane>
@@ -0,0 +1,257 @@

package com.skiwi.javaluainteroptest;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.StringReader;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Supplier;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.TextFieldTableCell;
import org.controlsfx.dialog.DialogStyle;
import org.controlsfx.dialog.Dialogs;
import org.luaj.vm2.Globals;
import org.luaj.vm2.lib.jse.CoerceJavaToLua;
import org.luaj.vm2.lib.jse.JsePlatform;
import org.luaj.vm2.luajc.LuaJC;

/**
* FXML Controller class
*
* @author Frank van Heeswijk
*/
public class CallbackFunctionsCompositeObjectContentController implements Initializable {
@FXML
private TextArea codeTextArea;

@FXML
private TextArea outputTextArea;

@FXML
private TableView<TableEntry> callbackTableView;

@FXML
private TableColumn<TableEntry, String> keyTableColumn;

@FXML
private TableColumn<TableEntry, String> valueTableColumn;

@FXML
private TableColumn<TableEntry, String> resultTableColumn;

@FXML
private TextField keyTextField;

@FXML
private TextField valueTextField;

private Globals globals;

private ExecutorService luaService;

@Override
public void initialize(final URL url, final ResourceBundle resourceBundle) {
setupLayout();
setupLua();
}

private void setupLayout() {
keyTableColumn.prefWidthProperty().bind(callbackTableView.widthProperty().multiply(0.333d));
valueTableColumn.prefWidthProperty().bind(callbackTableView.widthProperty().multiply(0.333d));
resultTableColumn.prefWidthProperty().bind(callbackTableView.widthProperty().multiply(0.333d));

keyTableColumn.setCellValueFactory(cellData -> cellData.getValue().keyProperty());
valueTableColumn.setCellValueFactory(cellData -> {
StringProperty valueProperty = cellData.getValue().valueProperty();
valueProperty.addListener((observableValue, oldValue, newValue) -> luaService.submit(this::runLuaCode));
return valueProperty;
});
resultTableColumn.setCellValueFactory(cellData -> cellData.getValue().resultProperty());

valueTableColumn.setCellFactory(TextFieldTableCell.forTableColumn());
valueTableColumn.setOnEditCommit(cellEditEvent -> {
TableEntry tableEntry = cellEditEvent.getTableView().getItems().get(cellEditEvent.getTablePosition().getRow());
tableEntry.setValue(cellEditEvent.getNewValue());
outputTextArea.clear();
});

MenuItem deleteMenuItem = new MenuItem("Delete");
deleteMenuItem.setOnAction(actionEvent -> {
ObservableList<TableEntry> selectedEntriesInTableView = callbackTableView.getSelectionModel().getSelectedItems();
ObservableList<TableEntry> selectedEntries = FXCollections.observableArrayList();
selectedEntriesInTableView.forEach(selectedEntries::add);
FXCollections.copy(selectedEntries, selectedEntriesInTableView);
selectedEntries.forEach(todoEntry -> {
callbackTableView.getItems().remove(todoEntry);
callbackTableView.sort();
});
callbackTableView.getSelectionModel().clearSelection();
});
ContextMenu contextMenu = new ContextMenu(deleteMenuItem);
callbackTableView.setContextMenu(contextMenu);

callbackTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);

codeTextArea.appendText("-- Always name this function \"initCallbacks\"");
codeTextArea.appendText(System.lineSeparator());
codeTextArea.appendText("function initCallbacks()");
codeTextArea.appendText(System.lineSeparator());
codeTextArea.appendText(" -- Put your callbackStore:addCallback(key:string, function:function(supplier<supplier<string>> -> supplier<string>)) calls here");
codeTextArea.appendText(System.lineSeparator());
codeTextArea.appendText("end");
}

private void setupLua() {
luaService = Executors.newCachedThreadPool(runnable -> {
Thread thread = new Thread(runnable);
thread.setDaemon(true);
return thread;
});

globals = JsePlatform.standardGlobals();
LuaJC.install(globals);

globals.STDOUT = new PrintStream(new OutputStream() {
@Override
public void write(int b) throws IOException {
String text = new String(new int[]{b}, 0, 1);
Platform.runLater(() -> outputTextArea.appendText(text));
}
});
globals.STDERR = new PrintStream(new OutputStream() {
@Override
public void write(int b) throws IOException {
String text = new String(new int[]{b}, 0, 1);
Platform.runLater(() -> outputTextArea.appendText(text));
}
});
}

@FXML
private void handleRunButtonAction(final ActionEvent actionEvent) {
Platform.runLater(outputTextArea::clear);
luaService.submit(this::runLuaCode);
}

private void runLuaCode() {
Platform.runLater(() -> {
try {
globals.load(new StringReader(codeTextArea.getText()), "interopTest").call();
CallbackStore callbackStore = new CallbackStore();
globals.set("callbackStore", CoerceJavaToLua.coerce(callbackStore));
globals.get("initCallbacks").call();
callbackTableView.getItems().forEach(tableEntry -> {
tableEntry.setResult(
callbackStore.<Supplier<Supplier<String>>, Supplier<String>>performCallback(
tableEntry.getKey(),
() -> () -> tableEntry.getValue(),
(Class<Supplier<String>>)(Object)Supplier.class,
Supplier::get)
.toString()
);
});
} catch (Throwable ex) {
Dialogs.create()
.style(DialogStyle.NATIVE)
.lightweight()
.title("Lua Exception")
.masthead((ex instanceof Error) ? "An error has occured" : "An exception has occured")
.showException(ex);
}
});
}

@FXML
private void handleAddButtonAction(final ActionEvent actionEvent) {
String key = keyTextField.getText();
String value = valueTextField.getText();
if (key.isEmpty()) {
Dialogs.create()
.style(DialogStyle.NATIVE)
.lightweight()
.title("Invalid Action")
.masthead("Action is not allowed")
.message("You cannot add an empty key.")
.showError();
return;
}
if (callbackTableView.getItems().stream().anyMatch(tableEntry -> tableEntry.getKey().equals(key))) {
Dialogs.create()
.style(DialogStyle.NATIVE)
.lightweight()
.title("Invalid Action")
.masthead("Action is not allowed")
.message("An entry with key \"" + key + "\" already exists.")
.showError();
return;
}
TableEntry tableEntry = new TableEntry(key, value);
callbackTableView.getItems().add(tableEntry);
outputTextArea.clear();
luaService.submit(this::runLuaCode);
keyTextField.clear();
valueTextField.clear();
}

public static class TableEntry {
private final StringProperty keyProperty;
private final StringProperty valueProperty;
private final StringProperty resultProperty;

private TableEntry(final String key, final String value) {
this.keyProperty = new SimpleStringProperty(key);
this.valueProperty = new SimpleStringProperty(value);
this.resultProperty = new SimpleStringProperty();
}

public StringProperty keyProperty() {
return keyProperty;
}

public String getKey() {
return keyProperty.getValue();
}

public StringProperty valueProperty() {
return valueProperty;
}

public String getValue() {
return valueProperty.getValue();
}

public void setValue(final String value) {
valueProperty.setValue(value);
}

public StringProperty resultProperty() {
return resultProperty;
}

public void setResult(final String result) {
resultProperty.setValue(result);
}

public String getResult() {
return resultProperty.getValue();
}
}
}
19 changes: 19 additions & 0 deletions src/com/skiwi/javaluainteroptest/CallbackStore.java
Expand Up @@ -7,9 +7,11 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.jse.CoerceJavaToLua;
import org.luaj.vm2.lib.jse.CoerceLuaToJava;

/**
*
Expand All @@ -32,4 +34,21 @@ public <T> List<String> performCallback(final String key, final T value) {
.map(function -> function.invoke(CoerceJavaToLua.coerce(value)).tojstring())
.collect(Collectors.toList());
}

public <T, U> List<String> performCallback(final String key, final T value, final Class<U> clazz, final Function<U, String> stringifyFunction) {
Objects.requireNonNull(key, "key");
Objects.requireNonNull(value, "value");
Objects.requireNonNull(clazz, "clazz");
Objects.requireNonNull(stringifyFunction, "stringifyFunction");
return callbackMapping.getOrDefault(key, Collections.emptyList()).stream()
.map(function -> CoerceLuaToJava.coerce(function.invoke(CoerceJavaToLua.coerce(value)).arg1(), clazz))
.map(obj -> {
//safe because CoerceLuaToJava.coerce already performs a cast
@SuppressWarnings("unchecked")
U u = (U)obj;
return u;
})
.map(stringifyFunction)
.collect(Collectors.toList());
}
}
1 change: 1 addition & 0 deletions src/com/skiwi/javaluainteroptest/JavaLuaInteropTest.fxml
Expand Up @@ -14,5 +14,6 @@
<Tab fx:id="luaFunctionTab" closable="false" text="Lua Function" />
<Tab fx:id="callbackFunctionsTab" closable="false" text="Callback Functions" />
<Tab fx:id="callbackFunctionsObjectTab" closable="false" text="Callback Functions Object" />
<Tab fx:id="callbackFunctionsCompositeObjectTab" closable="false" text="Callback Functions Composite Object" />
</tabs>
</TabPane>

0 comments on commit 7c64449

Please sign in to comment.