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(x -> {
082                if (x != null) service.submit(x);
083            });
084            initSequence.asynchronousCallableTasks().forEach(x -> {
085                if (x != null) service.submit(x);
086            });
087            try {
088                service.shutdown();
089            } catch (SecurityException e) {
090                Logging.log(Logging.LEVEL_ERROR, "Unable to shutdown executor service", e);
091            }
092        } catch (InterruptedException | ExecutionException ex) {
093            throw new JosmRuntimeException(ex);
094        }
095
096        // Initializes tasks that must be run after parallel tasks
097        runInitializationTasks(initSequence.afterInitializationTasks());
098    }
099
100    private static void runInitializationTasks(List<InitializationTask> tasks) {
101        for (InitializationTask task : tasks) {
102            try {
103                task.call();
104            } catch (JosmRuntimeException e) {
105                // Can happen if the current projection needs NTV2 grid which is not available
106                // In this case we want the user be able to change his projection
107                BugReport.intercept(e).warn();
108            }
109        }
110    }
111
112    /**
113     * Closes JOSM and optionally terminates the Java Virtual Machine (JVM).
114     * @param exit If {@code true}, the JVM is terminated by running {@link System#exit} with a given return code.
115     * @param exitCode The return code
116     * @return {@code true}
117     * @since 14140
118     */
119    public static boolean exitJosm(boolean exit, int exitCode) {
120        if (shutdownSequence != null) {
121            shutdownSequence.run();
122        }
123
124        if (exit) {
125            System.exit(exitCode);
126        }
127        return true;
128    }
129}