Exception handling in static initializer - java

Normally I handle my exceptions by showing some custom Alert (JavaFX) with details, but JavaFX runtime is not initialized at all when the static initializer of my class runs.
Is there any way to handle such exception without printing its content to output like an animal?
public class MyStaticInitializedClass {
static {
try {
//do the things that may throw exception
} catch(Exception ex) {
ExceptionHandler.showException(ex);
}
}
}
public class ExceptionHandler {
public static void showException(Exception ex) {
//constructs JavaFX alert with exception details
alert.show();
}
}

First consder if you shouldn't let the application simply crash and log the reason. A failure in a static initializer typically means there's something seriously wrong with the environment, which is not likely something you can recover from. Also, as far as I know, once a class fails to load it can't ever be loaded by the same ClassLoader again.
That said, if you want to show errors to your user in an alert, even if the error occurs before the JavaFX runtime has been initialized, then you need to save the error somewhere. Then, once you launch JavaFX, check wherever you stored the error(s) and show them. For example:
Main.java:
import javafx.application.Application;
public class Main {
public static void main(String[] args) {
// an "error" before JavaFX is launched
App.notifyUserOfError(new RuntimeException("OOPS!"));
Application.launch(App.class, args);
}
}
App.java:
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayDeque;
import java.util.Objects;
import java.util.Queue;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
public class App extends Application {
private static Queue<Throwable> errorQueue;
private static App appInstance;
public static synchronized void notifyUserOfError(Throwable throwable) {
Objects.requireNonNull(throwable);
if (appInstance == null) {
if (errorQueue == null) {
errorQueue = new ArrayDeque<>();
}
errorQueue.add(throwable);
} else {
if (Platform.isFxApplicationThread()) {
appInstance.showErrorAlert(throwable);
} else {
Platform.runLater(() -> appInstance.showErrorAlert(throwable));
}
}
}
private static synchronized Queue<Throwable> setAppInstance(App instance) {
if (appInstance != null) {
throw new IllegalStateException();
}
appInstance = instance;
var queue = errorQueue;
errorQueue = null; // no longer needed
return queue;
}
private Stage primaryStage;
#Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
var scene = new Scene(new StackPane(new Label("Hello, World!")), 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
var errors = setAppInstance(this);
if (errors != null) {
// if non-null then should be non-empty
do {
showErrorAlert(errors.remove());
} while (!errors.isEmpty());
// possibly exit the application if you can't recover
}
}
private void showErrorAlert(Throwable error) {
var alert = new Alert(AlertType.ERROR);
alert.initOwner(primaryStage);
alert.setContentText(error.toString());
var sw = new StringWriter();
error.printStackTrace(new PrintWriter(sw));
var area = new TextArea(sw.toString());
area.setEditable(false);
area.setFont(Font.font("Monospaced", 12));
var details = new VBox(5, new Label("Stack trace:"), area);
VBox.setVgrow(area, Priority.ALWAYS);
alert.getDialogPane().setExpandableContent(details);
alert.showAndWait();
}
}
The above puts the error in a queue if JavaFX has not been initialized yet. At the end of the start method the queue is checked for any errors and they're displayed to the user one after the other. If JavaFX has already been initialized then the error is immediately shown to the user.

Related

JavaFX error "Application launch must not be called more than once" while running 2 Test cases

I am using JavaFX to write a sample video player. But the tests fails when run together, ( Note: individually test passes).
Error: Unexpected exception thrown: java.lang.IllegalStateException: Application launch must not be called more than once
I understand that calling launch() twice on same Application Instance is causing this issue as per this . But I am not able to understand, that after one test completes, why the app is still running ? Why new instance is not getting created for 2nd test. testUnsupportedVideoFileThrowsError() succeeds but testSupportedVideoFilePlaysSuccessfully() fails.
package media;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;
import javafx.stage.Stage;
import java.io.File;
public class PlayVideoSnippet extends Application {
private static String source;
private Media media = null;
private MediaPlayer mediaPlayer = null;
private MediaView mediaView = null;
private Scene scene = null;
public static void playVideo(String source) {
PlayVideoSnippet.source = source;
PlayVideoSnippet.launch();
}
#Override
public void start(Stage stage) throws InterruptedException {
try {
media = new Media(new File(source).toURI().toString());
//onError close the app
media.setOnError(() -> {
Platform.exit();
});
//Create MediaPlayer, with media
mediaPlayer = new MediaPlayer(media);
mediaPlayer.setAutoPlay(true);
//onEnd of media, close the app
mediaPlayer.setOnEndOfMedia(() -> {
Platform.exit();
});
//Create media viewer
mediaView = new MediaView(mediaPlayer);
//create scene
scene = new Scene(new Group(), media.getWidth(), media.getHeight());
stage.setScene(scene);
stage.setTitle("Video Player");
//attach the mediaView to the scene root
((Group) scene.getRoot()).getChildren().add(mediaView);
stage.show();
} finally {
System.out.println("Inside finally");
stage.close();
}
}
}
package media;
import javafx.scene.media.MediaException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class PlayVideoSnippetTest {
/**
* Tests for {#link PlayVideoSnippet#playVideo(String)}.
*/
final String PATH_OF_SUPPORTED_VIDEO_FILE = "src/test/resources/file_example_MP4_480_1_5MG.mp4";
final String PATH_OF_UNSUPPORTED_VIDEO_FILE = "src/test/resources/file_example_WMV_480_1_2MB.wmv";
#Test
void testSupportedVideoFilePlaysSuccessfully() {
assertDoesNotThrow(() -> PlayVideoSnippet.playVideo(PATH_OF_SUPPORTED_VIDEO_FILE));
}
#Test
void testUnsupportedVideoFileThrowsError() {
RuntimeException exception = assertThrows(RuntimeException.class, () -> PlayVideoSnippet.playVideo(PATH_OF_UNSUPPORTED_VIDEO_FILE));
assertTrue(exception.getCause().getClass().equals(MediaException.class));
}
}
I was able to fix the issue with help of below inputs:
Using TestFX for testing purpose as suggested by #jewelsea.
Inputs from #James_D
StackOver flow issue
ApplicationTest.launch() method internally takes care of setting up primary stage and cleaning before each test.
Solution:
import org.junit.jupiter.api.Test;
import org.testfx.framework.junit5.ApplicationTest;
class VideoTest extends ApplicationTest {
final String pathOfSupportedFile = "video.mp4";
final String pathOfUnsupportedFile = "video.wmv";
#Test
void testSupportedVideoFilePlaysSuccessfully() throws Exception {
assertDoesNotThrow(() -> PlayVideoSnippetTest.launch(PlayVideoSnippet.class,
new String[]{pathOfSupportedFile}));
}
#Test
void testUnsupportedVideoFileThrowsError() throws Exception {
RuntimeException exception = assertThrows(RuntimeException.class,
() -> PlayVideoSnippetTest.launch(PlayVideoSnippet.class,
new String[]{pathOfUnsupportedFile}));
assertTrue(exception.getCause().getCause().getClass().equals(MediaException.class));
}
}

Block texture load only when placed, but not in inventory

I've read some other 'similar' questions but their problems is exactly the opposite. I've also read the docs but they won't provide anything useful to this problem.
When I /give myself the block, it shows a missing texture in my inventory as a item. But when I place it, its texture is shown in the world as a block.
Screenshot:
Main mod class:
package com.byethost8.code2828.mcmods.chemc;
import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block;
import net.minecraft.block.OreBlock;
import net.minecraft.block.material.Material;
import net.minecraft.block.material.MaterialColor;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item.Properties;
import net.minecraft.item.ItemGroup;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
#Mod(CheMC_.modid)
public class CheMC_ {
public static final String modid = "chemc";
public static OreBlock ore_lithium = (OreBlock) new OreBlock(
AbstractBlock.Properties
.create(Material.ROCK, MaterialColor.PINK_TERRACOTTA)
.harvestLevel(1)
.hardnessAndResistance(1, 1)
.setLightLevel(
light -> {
return 1;
}
)
)
.setRegistryName("chemc", "lithium_ore");
public static BlockItem i_ore_lithium = (BlockItem) new BlockItem(
ore_lithium,
new Properties().group(ItemGroup.BUILDING_BLOCKS)
)
.setRegistryName(ore_lithium.getRegistryName());
public static Block block_lithium = new Block(
AbstractBlock.Properties
.create(Material.IRON, MaterialColor.PINK_TERRACOTTA)
.harvestLevel(1)
.hardnessAndResistance(1.2F, 1)
.setLightLevel(
light -> {
return 1;
}
)
)
.setRegistryName("chemc", "lithium_block");
public static BlockItem i_block_lithium = (BlockItem) new BlockItem(
block_lithium,
new Properties().group(ItemGroup.BUILDING_BLOCKS)
)
.setRegistryName(block_lithium.getRegistryName());
public CheMC_() {
FMLJavaModLoadingContext
.get()
.getModEventBus()
.addListener(this::setup);
MinecraftForge.EVENT_BUS.register(this);
}
private void setup(final FMLCommonSetupEvent event) {}
// You can use EventBusSubscriber to automatically subscribe events on the
// contained class (this is subscribing to the MOD
// Event bus for receiving Registry Events)
#Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD)
public static class RegistryEvents {
#SubscribeEvent
public static void onBlocksRegistry(
final RegistryEvent.Register<Block> blockRegistryEvent
) {
// register a new block here
blockRegistryEvent
.getRegistry()
.registerAll(ore_lithium, block_lithium);
}
}
}
Some codes are removed to make the main problem clear.
Following texts only will say about Lithium Block, but same things apply for Lithium Ore.
Model File:
{
"parent": "block/cube_all",
"textures": {
"all": "chemc:block/lithium_block"
}
}
Folder structure of src/main/resources:
Blockstate:
{
"variants": {
"": [
{ "model": "chemc:block/lithium_block" }
]
}
}
I can't believe that I was stupid enough to register a Item and do nothing to assets/chemc/resources/models/item/ folder. See this for more. I have the exactly same problem as that OP.

When I run the Codename One HelloWorld Java Program from the Codename One HelloWorld Tutorial, I get an error

When I run the Codename One HelloWorld Java Program from the Codename One HelloWorld Tutorial video, I get this error:
java: cannot find symbol
symbol: class Button
location: class com.acmecorp.appname.AppName
I think I'm missing an import for class Button. How do I add the import statement for class Button so that the compilation won't have this error?
Here is my source code:
package com.acmecorp.appname;
import static com.codename1.ui.CN.*;
import com.codename1.ui.Display;
import com.codename1.ui.Form;
import com.codename1.ui.Dialog;
import com.codename1.ui.Label;
import com.codename1.ui.plaf.UIManager;
import com.codename1.ui.util.Resources;
import com.codename1.io.Log;
import com.codename1.ui.Toolbar;
import java.io.IOException;
import com.codename1.ui.layouts.BoxLayout;
import com.codename1.io.NetworkEvent;
/**
* This file was generated by Codename One for the purpose
* of building native mobile applications using Java.
*/
public class AppName {
private Form current;
private Resources theme;
public void init(Object context) {
// use two network threads instead of one
updateNetworkThreadCount(2);
theme = UIManager.initFirstTheme("/theme");
// Enable Toolbar on all Forms by default
Toolbar.setGlobalToolbar(true);
// Pro only feature
Log.bindCrashProtection(true);
addNetworkErrorListener(err -> {
// prevent the event from propagating
err.consume();
if(err.getError() != null) {
Log.e(err.getError());
}
Log.sendLogAsync();
Dialog.show("Connection Error", "There was a networking error in the connection to " + err.getConnectionRequest().getUrl(), "OK", null);
});
}
public void start() {
if(current != null){
current.show();
return;
}
Form hi = new Form("Hi World", BoxLayout.y());
hi.add(new Label("Hi World"));
Button b = new Button("Show Dialog");
hi.add(b);
b.addActionListener((e) -> Dialog.show("Dialog Title", "Hi", "OK", null));
hi.show();
}
public void stop() {
current = getCurrentForm();
if(current instanceof Dialog) {
((Dialog)current).dispose();
current = getCurrentForm();
}
}
public void destroy() {
}
}
I guess this com.codename1.ui.Button might works for u.

How would I make a GUI for my RMI client/server application?

So I am new to java RMI and making a GUI, but I got the RMI working. Now I have an Interface class called MessageService, a Server class called MessageServer and a Client class called MessageClient. I'd like to make a GUI with a field, where I can write a message, that will then be displayed on the server side. How is this achieved?
EDIT:
I have now done some kind of GUI. I also added a function to the Server. I also edited the Client code, so that when I run it, then it would launch my GUI class.
How would I implement it so I could write text into the input field and when sent, it would be seen on the server?
Here is the code for GUI:
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
public class TheGUI extends Application{
Button sendButton;
TextField input;
public static void main(String[] args) throws RemoteException, NotBoundException {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("GUI");
input = new TextField();
sendButton = new Button();
sendButton.setText("send");
AnchorPane layout = new AnchorPane();
HBox hbox = new HBox(5, input, sendButton);
layout.getChildren().addAll(hbox);
AnchorPane.setTopAnchor(hbox, 10d);
EventHandler<ActionEvent> event = new EventHandler<ActionEvent>() {
public void handle(ActionEvent e)
{
String message = input.getText();
input.setText("");
}
};
input.setOnAction(event);
sendButton.setOnAction(event);
Scene scene = new Scene(layout, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
}
This is my Edited Interface:
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface MessageService extends Remote {
public void newMessage (String clientID, String message) throws RemoteException;
}
This is my edited server class:
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
public class MessageServer extends UnicastRemoteObject implements MessageService {
protected MessageServer() throws RemoteException {
super();
}
#Override
public void newMessage(String clientID, String message) throws RemoteException {
System.out.println(clientID + " " + message);
}
public static void main (String[] argv)
{
try
{
Registry registry = LocateRegistry.getRegistry(1099);
MessageServer messageServer = new MessageServer();
registry.rebind("MessageService", messageServer); //register with naming service(bind with registry)
System.out.println("Server is Ready");
}
catch (RemoteException e)
{
System.out.println("ERROR: Could not create registry");
e.printStackTrace();
}
}
}
This is my Edited Client class:
import javafx.application.Application;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class MessageClient
{
public static void main (String[] argv) throws RemoteException, NotBoundException {
try
{
Registry registry = LocateRegistry.getRegistry("127.0.0.1");
MessageService messageService= (MessageService) registry.lookup("MessageService");
Application.launch(TheGUI.class);
}
catch (Exception e)
{
System.out.println ("MessageClient exception: " + e);
}
}
}
You are very close. Your code is almost complete.
RMI means communication between two, separate JVMs. The first JVM is your server process - which you have completed the coding for, hence I will not repeat it here. The second JVM is your client process. You can combine the GUI code with the RMI client code. And the GUI code you posted can be simplified.
Here is my version of your desired GUI to act as your RMI client.
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class MessageClientGui extends Application {
private static final String CLIENT_ID = "George";
private static MessageService msgService;
private TextField message;
#Override
public void start(Stage primaryStage) throws Exception {
Label prompt = new Label("Message");
message = new TextField();
Button send = new Button("_Send");
send.setOnAction(this::sendMessage);
send.setTooltip(new Tooltip("Sends message to [RMI] server."));
HBox root = new HBox(5.0D, prompt, message, send);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
private void sendMessage(ActionEvent actnEvnt) {
try {
msgService.newMessage(CLIENT_ID, message.getText());
}
catch (RemoteException x) {
x.printStackTrace();
}
}
public static void main(String[] args) {
try {
Registry registry = LocateRegistry.getRegistry("127.0.0.1");
msgService = (MessageService) registry.lookup("MessageService");
launch(args);
}
catch (Exception x) {
x.printStackTrace();
}
}
}
Here is an image of the running client.
In method main(), I initialize the MessageService client and save it in a static class member variable. When the user clicks on the Send button, method sendMessage() is called. In this method the remote method newMessage() is called with the contents of the TextField and some arbitrary client ID, namely George. As a result, in the console (i.e. standard output) of the server JVM, a string is printed that starts with: George

Open External Application From JavaFX

I found a way to open a link on default browser using HostServices.
getHostServices().showDocument("http://www.google.com");
Is there any way to open a media in default media player?
Is there any way to launch a specific File or Application?
Generally speaking, you can use Desktop#open(file) to open a file natively as next:
final Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
if (desktop != null && desktop.isSupported(Desktop.Action.OPEN)) {
desktop.open(file);
} else {
throw new UnsupportedOperationException("Open action not supported");
}
Launches the associated application to open the file. If the specified
file is a directory, the file manager of the current platform is
launched to open it.
More specifically, in case of a browser you can use directly Desktop#browse(uri), as next:
final Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) {
desktop.browse(uri);
} else {
throw new UnsupportedOperationException("Browse action not supported");
}
Launches the default browser to display a URI. If the default browser
is not able to handle the specified URI, the application registered
for handling URIs of the specified type is invoked. The application is
determined from the protocol and path of the URI, as defined by the
URI class. If the calling thread does not have the necessary
permissions, and this is invoked from within an applet,
AppletContext.showDocument() is used. Similarly, if the calling does
not have the necessary permissions, and this is invoked from within a
Java Web Started application, BasicService.showDocument() is used.
If you want to either open a URL which has an http: scheme in the browser, or open a file using the default application for that file type, the HostServices.showDocument(...) method you referenced provides a "pure JavaFX" way to do this. Note that you can't use this (as far as I can tell) to download a file from a web server and open it with the default application.
To open a file with the default application, you must convert the file to the string representation of the file: URL. Here is a simple example:
import java.io.File;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
public class OpenResourceNatively extends Application {
#Override
public void start(Stage primaryStage) {
TextField textField = new TextField("http://stackoverflow.com/questions/39898704");
Button openURLButton = new Button("Open URL");
EventHandler<ActionEvent> handler = e -> open(textField.getText());
textField.setOnAction(handler);
openURLButton.setOnAction(handler);
FileChooser fileChooser = new FileChooser();
Button openFileButton = new Button("Open File...");
openFileButton.setOnAction(e -> {
File file = fileChooser.showOpenDialog(primaryStage);
if (file != null) {
open(file.toURI().toString());
}
});
VBox root = new VBox(5,
new HBox(new Label("URL:"), textField, openURLButton),
new HBox(openFileButton)
);
root.setPadding(new Insets(20));
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
private void open(String resource) {
getHostServices().showDocument(resource);
}
public static void main(String[] args) {
launch(args);
}
}
Only the solution with java.awt.Desktop worked for me to open a file from JavaFX.
However, at first, my application got stuck and I had to figure out that it is necessary to call Desktop#open(File file) from a new thread. Calling the method from the current thread or the JavaFX application thread Platform#runLater(Runnable runnable) resulted in the application to hang indefinitely without an exception being thrown.
This is a small sample JavaFX application with the working file open solution:
import java.awt.Desktop;
import java.io.File;
import java.io.IOException;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
public class FileOpenDemo extends Application {
#Override
public void start(Stage primaryStage) {
final Button button = new Button("Open file");
button.setOnAction(event -> {
final FileChooser fileChooser = new FileChooser();
final File file = fileChooser.showOpenDialog(primaryStage.getOwner());
if (file == null)
return;
System.out.println("File selected: " + file.getName());
if (!Desktop.isDesktopSupported()) {
System.out.println("Desktop not supported");
return;
}
if (!Desktop.getDesktop().isSupported(Desktop.Action.OPEN)) {
System.out.println("File opening not supported");
return;
}
final Task<Void> task = new Task<Void>() {
#Override
public Void call() throws Exception {
try {
Desktop.getDesktop().open(file);
} catch (IOException e) {
System.err.println(e.toString());
}
return null;
}
};
final Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
});
primaryStage.setScene(new Scene(button));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The other proposed solution with javafx.application.HostServices did not work at all. I am using OpenJFX 8u141 on Ubuntu 17.10 amd64 and I got the following exception when invoking HostServices#showDocument(String uri):
java.lang.ClassNotFoundException: com.sun.deploy.uitoolkit.impl.fx.HostServicesFactory
Obviously, JavaFX HostServices is not yet properly implemented on all platforms. On this topic see also: https://github.com/Qabel/qabel-desktop/issues/420

Categories

Resources