utybo.branchingstorytree.swing.OpenBST.java Source code

Java tutorial

Introduction

Here is the source code for utybo.branchingstorytree.swing.OpenBST.java

Source

/**
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This Source Code Form is "Incompatible With Secondary Licenses", as
 * defined by the Mozilla Public License, v. 2.0.
 */
package utybo.branchingstorytree.swing;

import java.awt.Color;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.lang.reflect.Type;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ExecutionException;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.SwingWorker;

import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import utybo.branchingstorytree.api.Experimental;
import utybo.branchingstorytree.swing.ext.ComparableVersion;
import utybo.branchingstorytree.swing.impl.IMGClient;
import utybo.branchingstorytree.swing.utils.AlphanumComparator;
import utybo.branchingstorytree.swing.utils.Lang;
import utybo.branchingstorytree.swing.utils.OutputStreamToOutputAndPrint;
import utybo.branchingstorytree.swing.visuals.JBannerPanel;
import utybo.branchingstorytree.swing.visuals.Splashscreen;

/**
 * OpenBST is an open source implementation of the BST language that aims to be
 * fully compatible with every single feature of BST.
 * <p>
 * This class is both the main class and the main JFrame.
 *
 * @author utybo
 *
 */
public class OpenBST {
    /**
     * Version number of OpenBST
     */
    public static final String VERSION;
    static {
        String s = OpenBST.class.getPackage().getImplementationVersion();
        if (s == null) {
            VERSION = "<unknown version>";
        } else {
            VERSION = s;
        }
    }

    private static Map<String, String> internalFiles;

    public static final Logger LOG;
    private static ByteArrayOutputStream logOutput;
    static {
        logOutput = new ByteArrayOutputStream();

        PrintStream sysout = System.out;
        OutputStreamToOutputAndPrint newout = new OutputStreamToOutputAndPrint(logOutput, sysout);
        PrintStream ps = new PrintStream(newout);
        System.setOut(ps);

        PrintStream syserr = System.err;
        OutputStreamToOutputAndPrint newerr = new OutputStreamToOutputAndPrint(logOutput, syserr);
        PrintStream pserr = new PrintStream(newerr);
        System.setErr(pserr);

        LOG = LogManager.getLogger("OpenBST");
    }

    // --- IMAGES ---

    /**
     * Launch OpenBST
     *
     * @param args
     *            Arguments. The first argument is the language code to be used
     */
    public static void main(final String[] args) {
        // Before we do anything, setup system properties
        // First one to ensure Java 9's scaling system gets out of the way
        System.setProperty("sun.java2d.uiScale", "1.0");
        // Second one to force hardware acceleration
        System.setProperty("sun.java2d.opengl", "true");

        LOG.info("OpenBST version " + VERSION + ", part of the BST project");
        LOG.trace("[ INIT ]");
        LOG.trace("Loading language files");
        loadLang(args.length > 0 ? args[0] : null);

        LOG.trace("Applying Look and Feel");
        OpenBSTGUI.initializeLaF();
        VisualsUtils.fixTextFontScaling();

        LOG.trace("Loading scaling factor");
        Icons.loadScalingFactor();

        Splashscreen sc = Splashscreen.start();
        SwingWorker<Void, String> sw = new SwingWorker<Void, String>() {

            @Override
            protected Void doInBackground() {
                LOG.trace("Initializing JavaFX");
                publish(Lang.get("splash.init"));
                // Necessary - because we are killing Scenes all the time with WebViews in NodePanels,
                // JFX may think we just ended our application.
                // OpenBST exits with a dirty System.exit() anyway.
                Platform.setImplicitExit(false);
                // Initialize JavaFX
                new JFXPanel();
                VisualsUtils.invokeJfxAndWait(() -> {
                    // Initialize the web engine
                    new WebEngine();
                    // Initialize a view
                    new WebView();
                });

                LOG.info("Loading icons...");
                publish(Lang.get("splash.icons"));
                long timeAtIconStart = System.currentTimeMillis();
                Icons.load();
                LOG.info("Time taken to load icons : " + (System.currentTimeMillis() - timeAtIconStart) + " ms");

                LOG.info("Loading backgrounds...");
                publish(Lang.get("splash.loadbg"));
                Icons.loadBackgrounds();

                // $EXPERIMENTAL
                LOG.info("Caching backgrounds...");
                publish(Lang.get("splash.processbg"));
                IMGClient.initInternal();

                // $EXPERIMENTAL
                LOG.info("Loading internal BTS files...");
                publish(Lang.get("splash.loadinternalbst"));
                loadInternalBSTFiles();

                return null;
            }

            @Override
            protected void process(List<String> chunks) {
                String s = chunks.get(chunks.size() - 1);
                sc.setText(s);
            }

        };

        sw.execute();
        try {
            sw.get();
        } catch (InterruptedException | ExecutionException e1) {
            OpenBST.LOG.error(e1);
        }

        VisualsUtils.invokeSwingAndWait(() -> {
            sc.setText(Lang.get("splash.launch"));
            sc.lock();
            sc.stop();
        });
        LOG.trace("Launching app...");
        OpenBSTGUI.launch();

        VisualsUtils.invokeSwingAndWait(() -> sc.dispose());

        LOG.trace("Checking versions...");
        if (!"<unknown version>".equals(VERSION)) {
            SwingWorker<UpdateInfo, Void> worker = new SwingWorker<UpdateInfo, Void>() {

                @Override
                protected UpdateInfo doInBackground() throws Exception {
                    URL updateInfoSite = new URL("https://utybo.github.io/BST/version.json");
                    UpdateInfo info = new Gson().fromJson(
                            new InputStreamReader(updateInfoSite.openStream(), StandardCharsets.UTF_8),
                            UpdateInfo.class);
                    return info;
                }

                @Override
                protected void done() {
                    try {
                        UpdateInfo remoteVersion = this.get();
                        ComparableVersion remoteUnstable = new ComparableVersion(remoteVersion.unstable),
                                remoteStable = new ComparableVersion(remoteVersion.stable);
                        ComparableVersion local = new ComparableVersion(VERSION.substring(0, VERSION.length() - 1));

                        if (VERSION.endsWith("u")) {
                            // Local version is unstable
                            // Show updates to either the most recent unstable or the most recent stable
                            if (local.compareTo(remoteStable) < 0 && remoteStable.compareTo(remoteUnstable) < 0) {
                                // local (unstable) < stable < unstable
                                // Give options for both unstable and stable
                                JButton stablebtn = new JButton(Lang.get("up.moreinfostable"));
                                stablebtn.addActionListener(e -> {
                                    VisualsUtils.browse(remoteVersion.stableurl);
                                });
                                JButton unstablebtn = new JButton(Lang.get("up.moreinfounstable"));
                                unstablebtn.addActionListener(e -> {
                                    VisualsUtils.browse(remoteVersion.unstableurl);
                                });
                                OpenBSTGUI.getInstance()
                                        .addBanner(new JBannerPanel(
                                                new ImageIcon(Icons.getImage("Installing Updates", 48)),
                                                new Color(142, 255, 159), Lang.get("up.message1"), stablebtn, false,
                                                unstablebtn));
                            } else if (remoteStable.compareTo(local) < 0 && local.compareTo(remoteUnstable) < 0) {
                                // stable < local (unstable) < unstable
                                JButton unstablebtn = new JButton(Lang.get("up.moreinfo"));
                                unstablebtn.addActionListener(e -> {
                                    VisualsUtils.browse(remoteVersion.unstableurl);
                                });
                                OpenBSTGUI.getInstance().addBanner(new JBannerPanel(
                                        new ImageIcon(Icons.getImage("Installing Updates", 48)),
                                        new Color(142, 255, 159), Lang.get("up.message2"), unstablebtn, false));
                            } else if (remoteUnstable.compareTo(remoteStable) < 0
                                    && local.compareTo(remoteStable) < 0) {
                                // local (unstable) < stable
                                // and unstable < stable
                                JButton stablebtn = new JButton(Lang.get("up.moreinfo"));
                                stablebtn.addActionListener(e -> {
                                    VisualsUtils.browse(remoteVersion.stableurl);
                                });
                                OpenBSTGUI.getInstance().addBanner(new JBannerPanel(
                                        new ImageIcon(Icons.getImage("Installing Updates", 48)),
                                        new Color(142, 255, 159), Lang.get("up.message3"), stablebtn, false));
                            }
                        } else {
                            // If we're not running an unstable version, the only interesting case is local < stable
                            if (local.compareTo(remoteStable) < 0) {
                                // local (stable) < stable
                                JButton stablebtn = new JButton(Lang.get("up.moreinfo"));
                                stablebtn.addActionListener(e -> {
                                    VisualsUtils.browse(remoteVersion.stableurl);
                                });
                                OpenBSTGUI.getInstance().addBanner(new JBannerPanel(
                                        new ImageIcon(Icons.getImage("Installing Updates", 48)),
                                        new Color(142, 255, 159), Lang.get("up.message4"), stablebtn, false));
                            }
                        }
                    }

                    catch (InterruptedException | ExecutionException e) {
                        LOG.warn("Failed to read update information", e);
                        JButton showDetails = new JButton(Lang.get("up.showdetails"));
                        showDetails.addActionListener(ev -> Messagers.showException(OpenBSTGUI.getInstance(),
                                Lang.get("up.failedmessage"), e));
                        OpenBSTGUI.getInstance()
                                .addBanner(new JBannerPanel(new ImageIcon(Icons.getImage("Cancel", 16)),
                                        new Color(255, 144, 144), Lang.get("up.failedbanner"), showDetails, false));
                    }
                }
            };
            worker.execute();
        }
    }

    public static class UpdateInfo {
        private String stable, stableurl, unstable, unstableurl;
    }

    /**
     * Load the default language (which should be English) as well as the user's
     * language. We avoid loading all the language files to avoid having our RAM
     * usage blowing up.
     *
     * @param userCustomLanguage
     *            The language to use in the application, which must be one
     *            defined in the langs.json file
     */
    private static void loadLang(final String userCustomLanguage) {
        final Map<String, String> languages = new Gson()
                .fromJson(
                        new InputStreamReader(OpenBST.class.getResourceAsStream(
                                "/utybo/branchingstorytree/swing/lang/langs.json"), StandardCharsets.UTF_8),
                        new TypeToken<Map<String, String>>() {
                        }.getType());
        try {
            Lang.loadTranslationsFromFile(Lang.getDefaultLanguage(), OpenBST.class
                    .getResourceAsStream("/utybo/branchingstorytree/swing/lang/" + languages.get("default")));
        } catch (final Exception e) {
            LOG.warn("Exception while loading language file : " + languages.get("default"), e);
        }
        if (userCustomLanguage != null) {
            Lang.setSelectedLanguage(new Locale(userCustomLanguage));
        }
        final Locale userLanguage = Lang.getSelectedLanguage();
        languages.forEach((k, v) -> {
            if (userLanguage.equals(new Locale(k)) && !v.equals(languages.get("default"))) {
                try {
                    Lang.loadTranslationsFromFile(userLanguage,
                            OpenBST.class.getResourceAsStream("/utybo/branchingstorytree/swing/lang/" + v));
                } catch (final Exception e) {
                    LOG.warn("Exception while loading language file : " + v, e);
                }
            }
        });
    }

    public static String getAllLogs() {
        return logOutput.toString(Charset.defaultCharset());
    }

    @Experimental
    private static void loadInternalBSTFiles() {
        Type type = new TypeToken<Map<String, String>>() {
        }.getType();
        internalFiles = new TreeMap<String, String>(new AlphanumComparator());
        internalFiles.putAll(new Gson().fromJson(
                new InputStreamReader(OpenBST.class.getResourceAsStream("/bst/list.json"), StandardCharsets.UTF_8),
                type));

    }

    @Experimental
    public static Map<String, String> getInternalFiles() {
        return Collections.unmodifiableMap(internalFiles);
    }

    private OpenBST() {
    }
}