For a larger project, I need to build a bridge between a Python and a Java programme. I decided to use Py4J and constructed a smaller problem to familiarise myself with the first steps. Java is started first. When Python is started, it continuously delivers a pair of values that are displayed graphically in Java. The Java programme has the option of changing a parameter in Python. Is there anything that can be improved?
Java main program:
package py4j.examples;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import py4j.examples.gui.AusgabeController;
public class PythonTrigonometrie extends Application {
/**
* @param args Programparameters
*/
public static void main(final String[] args) {
launch(args);
}
@Override
public void start(final Stage primaryStage) throws Exception {
final Scene scene = new Scene(new AusgabeController(primaryStage).getView(), 640, 480);
primaryStage.setTitle("JavaFX Py4J Beispiel");
primaryStage.setAlwaysOnTop(true);
primaryStage.setScene(scene);
primaryStage.show();
}
}
The java controller:
package py4j.examples.gui;
import javafx.application.Platform;
import javafx.scene.Parent;
import javafx.stage.Stage;
import py4j.ClientServer;
public class AusgabeController extends GatewayImpl {
private final ClientServer clientServer = new ClientServer(null);
private final AusgabeView view = new AusgabeView();
public AusgabeController(final Stage stage) {
view.addFrequenzListener((obs, oldValue, newValue) -> {
notifyAllListeners(new Object[] { "frequenz", newValue });
});
clientServer.getPythonServerEntryPoint(new Class[] { IGateway.class });
stage.setOnCloseRequest(e -> {
notifyAllListeners("close");
});
}
/**
* @return the view
*/
public Parent getView() {
return view;
}
@Override
public void setOnClose() {
Platform.runLater(() -> clientServer.shutdown());
}
}
The JavaFX Viewer:
package py4j.examples.gui;
import java.util.Map;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.control.Spinner;
import javafx.scene.control.SpinnerValueFactory;
import javafx.scene.control.SpinnerValueFactory.DoubleSpinnerValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.text.Text;
public class AusgabeView extends StackPane {
private static final double SCALE = 100.0;
private final static Line lineSinus = new Line(SCALE, 0, SCALE, 0);
private final static Line lineCosinus = new Line(0, 0, SCALE, 0);
private final static Line lineHypothenuse = new Line(0, 0, 0, 0);
private final Circle circle = new Circle(0, 0, SCALE, Color.TRANSPARENT);
private final Group gArc = new Group(circle, lineHypothenuse, lineSinus, lineCosinus);
private final Text textFreq = new Text("f:");
private final SpinnerValueFactory<Double> frequenzValueFactory = //
new SpinnerValueFactory.DoubleSpinnerValueFactory(0.01, Double.MAX_VALUE, 1d);
private final Spinner<Double> spinnerFrequenz = new Spinner<>();
private final HBox hBox = new HBox(10, textFreq, spinnerFrequenz);
private final VBox vBox = new VBox(20, gArc, hBox);
public AusgabeView() {
circle.setStroke(Color.BLACK);
lineSinus.setStroke(Color.RED);
lineCosinus.setStroke(Color.BLUE);
((DoubleSpinnerValueFactory) frequenzValueFactory).setAmountToStepBy(0.5);
spinnerFrequenz.setValueFactory(frequenzValueFactory);
GatewayImpl.setValueProperty(spinnerFrequenz.valueProperty());
spinnerFrequenz.setEditable(true);
spinnerFrequenz.setPrefWidth(70);
hBox.setPadding(new Insets(0, 0, 0, 55));
getChildren().add(vBox);
}
/**
* @param value Map with sin and cos value
*/
public static void setArc(final Map<String, Double> value) {
final Double sin = value.get("sin") * -SCALE;
final Double cos = value.get("cos") * SCALE;
lineCosinus.setEndX(cos);
lineSinus.setStartX(cos);
lineSinus.setEndX(cos);
lineSinus.setEndY(sin);
lineHypothenuse.setEndX(cos);
lineHypothenuse.setEndY(sin);
}
/**
* @param changeListener to set to spinnerFrequenz
*/
public void addFrequenzListener(final ChangeListener<Double> changeListener) {
spinnerFrequenz.valueProperty().addListener(changeListener);
}
/**
* @return the root node
*/
public Node getRootNode() {
return vBox;
}
}
The java gateway interface:
package py4j.examples.gui;
import java.util.Map;
public interface IGateway {
/**
* @param listener PhytonListener to register
*/
void registerListener(final IPhytonListener listener);
/**
* @param listener PhytonListener to remove
*/
void removeListener(final IPhytonListener listener);
/**
* @param source source id
*/
void notifyAllListeners(Object source);
void setOnClose();
/**
* @return the frequenz
*/
double getFrequenz();
/**
* @param value Map<String, Double> to set
*/
void setValue(final Map<String, Double> value);
}
The implementation of java gateway:
package py4j.examples.gui;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyObjectProperty;
public class GatewayImpl implements IGateway {
private final static List<IPhytonListener> listeners = new ArrayList<>();
private static ReadOnlyObjectProperty<Double> frequenzProperty;
@Override
public void registerListener(final IPhytonListener listener) {
Platform.runLater(() -> listeners.add(listener));
}
@Override
public void removeListener(final IPhytonListener listener) {
listeners.remove(listener);
}
@Override
public void notifyAllListeners(final Object source) {
for (final IPhytonListener listener : listeners) {
listener.notify(source);
}
}
@Override
public void setValue(final Map<String, Double> value) {
Platform.runLater(() -> AusgabeView.setArc(value));
}
@Override
public double getFrequenz() {
final double freq = frequenzProperty.get();
return freq;
}
/**
* @param frequenzProperty the valueProperty to set
*/
public static void setValueProperty(final ReadOnlyObjectProperty<Double> frequenzProperty) {
GatewayImpl.frequenzProperty = frequenzProperty;
}
@Override
public void setOnClose() {/* Overridden in Controller */
}
}
The java python listener:
package py4j.examples.gui;
public interface IPhytonListener {
Object notify(Object source);
}
And the Python program:
from py4j.clientserver import ClientServer, JavaParameters, PythonParameters
import math
import time
DELTA_ARC = math.pi / 180
DELTA_TIME = 1.0 / 360
FULL_CIRCLE = math.pi * 2
class PythonListener(object):
def __init__(self, gateway):
self.gateway = gateway
def notify(self, obj):
if obj == "close":
Trigonometrie.end()
return
elif obj[0] == "frequenz":
frequenz = obj[1]
Trigonometrie.setFrequenz(frequenz)
return
class Java:
implements = ["py4j.examples.gui.IPhytonListener"]
class Trigonometrie(PythonListener):
def start(self, frequenz=1, phi=0):
Trigonometrie.frequenz = frequenz
Trigonometrie.phi = phi
Trigonometrie.running = True
while Trigonometrie.running:
# phi = phi if phi <= FULL_CIRCLE else phi - FULL_CIRCLE
sinus = math.sin(phi)
cosinus = math.cos(phi)
try:
if Trigonometrie.running == True:
input_dict = gateway.jvm.java.util.HashMap()
input_dict["sin"] = sinus
input_dict["cos"] = cosinus
java_app.setValue(input_dict)
phi += DELTA_ARC
time.sleep(DELTA_TIME / Trigonometrie.frequenz)
except:
Trigonometrie.running = False
@staticmethod
def setFrequenz(frequenz):
Trigonometrie.frequenz = frequenz
@staticmethod
def end():
Trigonometrie.running = False
class Java:
implements = ["py4j.examples.gui.IGateway"]
gateway = ClientServer(
java_parameters=JavaParameters(),
python_parameters=PythonParameters())
java_app = gateway.jvm.py4j.examples.gui.GatewayImpl()
listener = PythonListener(gateway)
java_app.registerListener(listener)
frequenz = java_app.getFrequenz()
trigonometrie = Trigonometrie(listener)
trigonometrie.start(frequenz)
java_app.setOnClose();
gateway.shutdown()