onAlreadyRunning(Consumer)   A
last analyzed

Complexity

Conditions 2

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 3.1852

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 7
c 1
b 0
f 0
dl 0
loc 9
ccs 2
cts 6
cp 0.3333
rs 10
cc 2
crap 3.1852
1
/*
2
 * This file is part of SingleInstance.
3
 *
4
 * SingleInstance is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU Lesser General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * SingleInstance is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public License
15
 * along with SingleInstance.  If not, see <https://www.gnu.org/licenses/>.
16
 *
17
 * Copyright (c) 2020 Vincent Quatrevieux
18
 */
19
20
package fr.quatrevieux.singleinstance;
21
22
import fr.quatrevieux.singleinstance.ipc.InstanceServer;
23
import fr.quatrevieux.singleinstance.ipc.Message;
24
import org.slf4j.Logger;
25
import org.slf4j.LoggerFactory;
26
27
import java.io.IOException;
28
import java.util.concurrent.ExecutorService;
29
import java.util.concurrent.Executors;
30
import java.util.function.Consumer;
31
32
/**
33
 * Facade for a simple usage of {@link InstanceManager}
34
 *
35
 * Usage:
36
 * <pre>{@code
37
 *     public static void main(String[] args) {
38
 *         // Check if an instance is running
39
 *         SingleInstance.onAlreadyRunning(instance -> {
40
 *             // Send the argument to the instance and stop the current process
41
 *             instance.send("Open", args[0].getBytes());
42
 *             System.exit(0);
43
 *         });
44
 *
45
 *         // Initialize application
46
 *         MyApp app = xxx;
47
 *
48
 *         // Start the IPC server
49
 *         SingleInstance.onMessage(message -> {
50
 *             // Receive an "Open" message
51
 *             if (message.name().equals("Open")) {
52
 *                 app.open(new String(message.data()));
53
 *             }
54
 *         });
55
 *     }
56
 * }</pre>
57
 */
58
final public class SingleInstance {
59
    static private InstanceManager manager;
60
    static private InstanceServer server;
61
    static private ExecutorService executor;
62
    static private DistantInstance distantInstance;
63
    static private Thread shutdownHook;
64
65 1
    final static private Logger LOGGER = LoggerFactory.getLogger(SingleInstance.class);
66
67
    /**
68
     * Initialize the SingleInstance system
69
     * Calling this method is not required : any other call will implicitly call this method
70
     *
71
     * @throws IOException When error occurs during acquiring the lock
72
     */
73
    static public void init() throws IOException {
74 1
        init(null);
75 1
    }
76
77
    /**
78
     * Initialize the SingleInstance system by specifying the lock file
79
     *
80
     * @param lockFile The lock file to use
81
     *
82
     * @throws IOException When error occurs during acquiring the lock
83
     */
84
    static public void init(LockFile lockFile) throws IOException {
85 1
        if (manager != null) {
86 1
            return;
87
        }
88
89 1
        if (lockFile == null) {
90
            lockFile = new LockFile();
91
        }
92
93 1
        manager = new InstanceManager(lockFile);
94 1
        manager.acquire();
95
96 1
        if (shutdownHook == null) {
97 1
            Runtime.getRuntime().addShutdownHook(shutdownHook = new Thread(SingleInstance::close));
98
        }
99 1
    }
100
101
    /**
102
     * Check if the current process is the first running instance
103
     *
104
     * Usage:
105
     * <pre>{@code
106
     *     public static void main(String[] args) {
107
     *         if (!SingleInstance.isFirst()) {
108
     *             System.err.println("Already running");
109
     *             return;
110
     *         }
111
     *     }
112
     * }</pre>
113
     *
114
     * @return true if the current process is the first running instance
115
     *
116
     * @throws IOException When cannot initialize system
117
     * @see SingleInstance#onAlreadyRunning(Consumer) For perform action when an instance is already running
118
     */
119
    static public boolean isFirst() throws IOException {
120 1
        return manager().acquire();
121
    }
122
123
    /**
124
     * Start the IPC server if the current process is the first running instance,
125
     * and handle received messages
126
     *
127
     * If the current process is not the first running instance, this method will be a noop.
128
     *
129
     * Note: This method will start a new thread to consume messages
130
     *
131
     * <pre>{@code
132
     *     SingleInstance.onMessage(message -> {
133
     *         if (message.name().equals("MyMessage")) {
134
     *             // Handle message
135
     *         }
136
     *     });
137
     * }</pre>
138
     *
139
     * @param consumer The action to perform when receiving messages
140
     *
141
     * @throws IOException When cannot start the server
142
     * @throws IllegalStateException When the server is already started
143
     */
144
    static public void onMessage(Consumer<Message> consumer) throws IOException {
145 1
        if (server != null) {
146 1
            throw new IllegalStateException("IPC Server is already started");
147
        }
148
149 1
        manager().server().ifPresent(server -> {
150 1
            SingleInstance.server = server;
151 1
            executor = Executors.newSingleThreadExecutor();
152 1
            executor.execute(() -> {
153
                try {
154 1
                    server.consume(consumer);
155
                } catch (IOException e) {
156
                    LOGGER.error("Error during reading message on IPC server", e);
157 1
                }
158 1
            });
159 1
        });
160 1
    }
161
162
    /**
163
     * Perform action is an already running instance is found
164
     *
165
     * Usage:
166
     * <pre>{@code
167
     *     SingleInstance.onAlreadyRunning(instance -> {
168
     *         instance.send("MyMessage"); // Send a message to the running instance
169
     *         System.exit(0); // Stop the current process
170
     *     });
171
     * }</pre>
172
     *
173
     * @param action Action to perform
174
     *
175
     * @throws IOException When cannot initialize the system
176
     */
177
    static public void onAlreadyRunning(Consumer<DistantInstance> action) throws IOException {
178 1
        if (distantInstance != null) {
179
            action.accept(distantInstance);
180
            return;
181
        }
182
183 1
        manager().find().ifPresent(instance -> {
184
            distantInstance = instance;
185
            action.accept(distantInstance);
186
        });
187 1
    }
188
189
    /**
190
     * Get (or create) the instance manager instance
191
     *
192
     * @return The manager instance
193
     * @throws IOException When cannot initialize the system
194
     */
195
    static public InstanceManager manager() throws IOException {
196 1
        init();
197
198 1
        return manager;
199
    }
200
201
    /**
202
     * Close the SingleInstance system and release the lock
203
     * This method should not be called manually : when init is called, a shutdown hook is registered
204
     */
205
    static public void close() {
206 1
        if (manager == null) {
207
            return;
208
        }
209
210 1
        if (server != null) {
211
            try {
212 1
                server.close();
213
            } catch (IOException e) {
214
                LOGGER.error("Error during closing the IPC server", e);
215 1
            }
216 1
            server = null;
217
        }
218
219 1
        if (executor != null) {
220 1
            executor.shutdown();
221 1
            executor = null;
222
        }
223
224 1
        distantInstance = null;
225
226 1
        manager.release();
227 1
    }
228
}
229