001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.spi.lifecycle;
003
004import java.util.List;
005import java.util.Objects;
006import java.util.concurrent.ExecutionException;
007import java.util.concurrent.ExecutorService;
008import java.util.concurrent.Executors;
009import java.util.concurrent.Future;
010
011import org.openstreetmap.josm.tools.JosmRuntimeException;
012import org.openstreetmap.josm.tools.Logging;
013import org.openstreetmap.josm.tools.Utils;
014import org.openstreetmap.josm.tools.bugreport.BugReport;
015
016/**
017 * JOSM lifecycle.
018 * @since 14125
019 */
020public final class Lifecycle {
021
022    private static volatile InitStatusListener initStatusListener;
023
024    private static volatile Runnable shutdownSequence;
025
026    private Lifecycle() {
027        // Hide constructor
028    }
029
030    /**
031     * Gets initialization task listener.
032     * @return initialization task listener
033     */
034    public static InitStatusListener getInitStatusListener() {
035        return initStatusListener;
036    }
037
038    /**
039     * Sets initialization task listener.
040     * @param listener initialization task listener. Must not be null
041     */
042    public static void setInitStatusListener(InitStatusListener listener) {
043        initStatusListener = Objects.requireNonNull(listener);
044    }
045
046    /**
047     * Gets shutdown sequence.
048     * @return shutdown sequence
049     * @since 14140
050     */
051    public static Runnable getShutdownSequence() {
052        return shutdownSequence;
053    }
054
055    /**
056     * Sets shutdown sequence.
057     * @param sequence shutdown sequence. Must not be null
058     * @since 14140
059     */
060    public static void setShutdownSequence(Runnable sequence) {
061        shutdownSequence = Objects.requireNonNull(sequence);
062    }
063
064    /**
065     * Initializes the main object. A lot of global variables are initialized here.
066     * @param initSequence Initialization sequence
067     * @since 14139
068     */
069    public static void initialize(InitializationSequence initSequence) {
070        // Initializes tasks that must be run before parallel tasks
071        runInitializationTasks(initSequence.beforeInitializationTasks());
072
073        // Initializes tasks to be executed (in parallel) by a ExecutorService
074        try {
075            ExecutorService service = Executors.newFixedThreadPool(
076                    Runtime.getRuntime().availableProcessors(), Utils.newThreadFactory("main-init-%d", Thread.NORM_PRIORITY));
077            for (Future<Void> i : service.invokeAll(initSequence.parallelInitializationTasks())) {
078                i.get();
079            }
080            // asynchronous initializations to be completed eventually
081            initSequence.asynchronousRunnableTasks().forEach(service::submit);
082            initSequence.asynchronousCallableTasks().forEach(service::submit);
083            try {
084                service.shutdown();
085            } catch (SecurityException e) {
086                Logging.log(Logging.LEVEL_ERROR, "Unable to shutdown executor service", e);
087            }
088        } catch (InterruptedException | ExecutionException ex) {
089            throw new JosmRuntimeException(ex);
090        }
091
092        // Initializes tasks that must be run after parallel tasks
093        runInitializationTasks(initSequence.afterInitializationTasks());
094    }
095
096    private static void runInitializationTasks(List<InitializationTask> tasks) {
097        for (InitializationTask task : tasks) {
098            try {
099                task.call();
100            } catch (JosmRuntimeException e) {
101                // Can happen if the current projection needs NTV2 grid which is not available
102                // In this case we want the user be able to change his projection
103                BugReport.intercept(e).warn();
104            }
105        }
106    }
107
108    /**
109     * Closes JOSM and optionally terminates the Java Virtual Machine (JVM).
110     * @param exit If {@code true}, the JVM is terminated by running {@link System#exit} with a given return code.
111     * @param exitCode The return code
112     * @return {@code true}
113     * @since 14140
114     */
115    public static boolean exitJosm(boolean exit, int exitCode) {
116        if (shutdownSequence != null) {
117            shutdownSequence.run();
118        }
119
120        if (exit) {
121            System.exit(exitCode);
122        }
123        return true;
124    }
125}