Craig Raw
4 years ago
6 changed files with 783 additions and 14 deletions
@ -0,0 +1,531 @@ |
|||||
|
/** |
||||
|
* Copyright 2019 Pratanu Mandal |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
* |
||||
|
*/ |
||||
|
|
||||
|
package com.sparrowwallet.sparrow.instance; |
||||
|
|
||||
|
import java.io.BufferedReader; |
||||
|
import java.io.BufferedWriter; |
||||
|
import java.io.DataInputStream; |
||||
|
import java.io.DataOutputStream; |
||||
|
import java.io.File; |
||||
|
import java.io.FileInputStream; |
||||
|
import java.io.FileNotFoundException; |
||||
|
import java.io.FileOutputStream; |
||||
|
import java.io.IOException; |
||||
|
import java.io.InputStream; |
||||
|
import java.io.InputStreamReader; |
||||
|
import java.io.OutputStream; |
||||
|
import java.io.OutputStreamWriter; |
||||
|
import java.io.RandomAccessFile; |
||||
|
import java.net.InetAddress; |
||||
|
import java.net.ServerSocket; |
||||
|
import java.net.Socket; |
||||
|
import java.net.SocketException; |
||||
|
import java.net.UnknownHostException; |
||||
|
import java.nio.channels.FileChannel; |
||||
|
import java.nio.channels.FileLock; |
||||
|
|
||||
|
/** |
||||
|
* The <code>Instance</code> class is the primary logical entry point to the library.<br> |
||||
|
* It allows to create an application lock or free it and send and receive messages between first and subsequent instances.<br><br> |
||||
|
* |
||||
|
* <pre> |
||||
|
* // unique application ID
|
||||
|
* String APP_ID = "tk.pratanumandal.unique4j-mlsdvo-20191511-#j.6"; |
||||
|
* |
||||
|
* // create Instance instance
|
||||
|
* Instance unique = new Instance(APP_ID) { |
||||
|
* @Override |
||||
|
* protected void receiveMessage(String message) { |
||||
|
* // print received message (timestamp)
|
||||
|
* System.out.println(message); |
||||
|
* } |
||||
|
* |
||||
|
* @Override |
||||
|
* protected String sendMessage() { |
||||
|
* // send timestamp as message
|
||||
|
* Timestamp ts = new Timestamp(new Date().getTime()); |
||||
|
* return "Another instance launch attempted: " + ts.toString(); |
||||
|
* } |
||||
|
* }; |
||||
|
* |
||||
|
* // try to obtain lock
|
||||
|
* try { |
||||
|
* unique.acquireLock(); |
||||
|
* } catch (InstanceException e) { |
||||
|
* e.printStackTrace(); |
||||
|
* } |
||||
|
* |
||||
|
* ... |
||||
|
* |
||||
|
* // try to free the lock before exiting program
|
||||
|
* try { |
||||
|
* unique.freeLock(); |
||||
|
* } catch (InstanceException e) { |
||||
|
* e.printStackTrace(); |
||||
|
* } |
||||
|
* </pre> |
||||
|
* |
||||
|
* @author Pratanu Mandal |
||||
|
* @since 1.3 |
||||
|
* |
||||
|
*/ |
||||
|
public abstract class Instance { |
||||
|
|
||||
|
// starting position of port check
|
||||
|
private static final int PORT_START = 7221; |
||||
|
|
||||
|
// system temporary directory path
|
||||
|
private static final String TEMP_DIR = System.getProperty("java.io.tmpdir"); |
||||
|
|
||||
|
/** |
||||
|
* Unique string representing the application ID.<br><br> |
||||
|
* |
||||
|
* The APP_ID must be as unique as possible. |
||||
|
* Avoid generic names like "my_app_id" or "hello_world".<br> |
||||
|
* A good strategy is to use the entire package name (group ID + artifact ID) along with some random characters. |
||||
|
*/ |
||||
|
public final String APP_ID; |
||||
|
|
||||
|
// auto exit from application or not
|
||||
|
private final boolean AUTO_EXIT; |
||||
|
|
||||
|
// lock server port
|
||||
|
private int port; |
||||
|
|
||||
|
// lock server socket
|
||||
|
private ServerSocket server; |
||||
|
|
||||
|
// lock file RAF object
|
||||
|
private RandomAccessFile lockRAF; |
||||
|
|
||||
|
// file lock for the lock file RAF object
|
||||
|
private FileLock fileLock; |
||||
|
|
||||
|
/** |
||||
|
* Parameterized constructor.<br> |
||||
|
* This constructor configures to automatically exit the application for subsequent instances.<br><br> |
||||
|
* |
||||
|
* The APP_ID must be as unique as possible. |
||||
|
* Avoid generic names like "my_app_id" or "hello_world".<br> |
||||
|
* A good strategy is to use the entire package name (group ID + artifact ID) along with some random characters. |
||||
|
* |
||||
|
* @param APP_ID Unique string representing the application ID |
||||
|
*/ |
||||
|
public Instance(final String APP_ID) { |
||||
|
this(APP_ID, true); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Parameterized constructor.<br> |
||||
|
* This constructor allows to explicitly specify the exit strategy for subsequent instances.<br><br> |
||||
|
* |
||||
|
* The APP_ID must be as unique as possible. |
||||
|
* Avoid generic names like "my_app_id" or "hello_world".<br> |
||||
|
* A good strategy is to use the entire package name (group ID + artifact ID) along with some random characters. |
||||
|
* |
||||
|
* @since 1.2 |
||||
|
* |
||||
|
* @param APP_ID Unique string representing the application ID |
||||
|
* @param AUTO_EXIT If true, automatically exit the application for subsequent instances |
||||
|
*/ |
||||
|
public Instance(final String APP_ID, final boolean AUTO_EXIT) { |
||||
|
this.APP_ID = APP_ID; |
||||
|
this.AUTO_EXIT = AUTO_EXIT; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Try to obtain lock. If not possible, send data to first instance. |
||||
|
* |
||||
|
* @deprecated Use <code>acquireLock()</code> instead. |
||||
|
* @throws InstanceException throws InstanceException if it is unable to start a server or connect to server |
||||
|
*/ |
||||
|
@Deprecated |
||||
|
public void lock() throws InstanceException { |
||||
|
acquireLock(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Try to obtain lock. If not possible, send data to first instance. |
||||
|
* |
||||
|
* @since 1.2 |
||||
|
* |
||||
|
* @return true if able to acquire lock, false otherwise |
||||
|
* @throws InstanceException throws InstanceException if it is unable to start a server or connect to server |
||||
|
*/ |
||||
|
public boolean acquireLock() throws InstanceException { |
||||
|
// try to obtain port number from lock file
|
||||
|
port = lockFile(); |
||||
|
|
||||
|
if (port == -1) { |
||||
|
// failed to fetch port number
|
||||
|
// try to start server
|
||||
|
startServer(); |
||||
|
} |
||||
|
else { |
||||
|
// port number fetched from lock file
|
||||
|
// try to start client
|
||||
|
doClient(); |
||||
|
} |
||||
|
|
||||
|
return (server != null); |
||||
|
} |
||||
|
|
||||
|
// start the server
|
||||
|
private void startServer() throws InstanceException { |
||||
|
// try to create server
|
||||
|
port = PORT_START; |
||||
|
while (true) { |
||||
|
try { |
||||
|
server = new ServerSocket(port, 50, InetAddress.getByName(null)); |
||||
|
break; |
||||
|
} catch (IOException e) { |
||||
|
port++; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// try to lock file
|
||||
|
lockFile(port); |
||||
|
|
||||
|
// server created successfully; this is the first instance
|
||||
|
// keep listening for data from other instances
|
||||
|
Thread thread = new Thread() { |
||||
|
@Override |
||||
|
public void run() { |
||||
|
while (!server.isClosed()) { |
||||
|
try { |
||||
|
// establish connection
|
||||
|
final Socket socket = server.accept(); |
||||
|
|
||||
|
// handle socket on a different thread to allow parallel connections
|
||||
|
Thread thread = new Thread() { |
||||
|
@Override |
||||
|
public void run() { |
||||
|
try { |
||||
|
// open writer
|
||||
|
OutputStream os = socket.getOutputStream(); |
||||
|
DataOutputStream dos = new DataOutputStream(os); |
||||
|
|
||||
|
// open reader
|
||||
|
InputStream is = socket.getInputStream(); |
||||
|
DataInputStream dis = new DataInputStream(is); |
||||
|
|
||||
|
// read message length from client
|
||||
|
int length = dis.readInt(); |
||||
|
|
||||
|
// read message string from client
|
||||
|
String message = null; |
||||
|
if (length > -1) { |
||||
|
byte[] messageBytes = new byte[length]; |
||||
|
int bytesRead = dis.read(messageBytes, 0, length); |
||||
|
message = new String(messageBytes, 0, bytesRead, "UTF-8"); |
||||
|
} |
||||
|
|
||||
|
// write response to client
|
||||
|
if (APP_ID == null) { |
||||
|
dos.writeInt(-1); |
||||
|
} |
||||
|
else { |
||||
|
byte[] appId = APP_ID.getBytes("UTF-8"); |
||||
|
|
||||
|
dos.writeInt(appId.length); |
||||
|
dos.write(appId); |
||||
|
} |
||||
|
dos.flush(); |
||||
|
|
||||
|
// close writer and reader
|
||||
|
dos.close(); |
||||
|
dis.close(); |
||||
|
|
||||
|
// perform user action on message
|
||||
|
receiveMessage(message); |
||||
|
|
||||
|
// close socket
|
||||
|
socket.close(); |
||||
|
} catch (IOException e) { |
||||
|
handleException(new InstanceException(e)); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// start socket thread
|
||||
|
thread.start(); |
||||
|
} catch (SocketException e) { |
||||
|
if (!server.isClosed()) { |
||||
|
handleException(new InstanceException(e)); |
||||
|
} |
||||
|
} catch (IOException e) { |
||||
|
handleException(new InstanceException(e)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
thread.start(); |
||||
|
} |
||||
|
|
||||
|
// do client tasks
|
||||
|
private void doClient() throws InstanceException { |
||||
|
// get localhost address
|
||||
|
InetAddress address = null; |
||||
|
try { |
||||
|
address = InetAddress.getByName(null); |
||||
|
} catch (UnknownHostException e) { |
||||
|
throw new InstanceException(e); |
||||
|
} |
||||
|
|
||||
|
// try to establish connection to server
|
||||
|
Socket socket = null; |
||||
|
try { |
||||
|
socket = new Socket(address, port); |
||||
|
} catch (IOException e) { |
||||
|
// connection failed try to start server
|
||||
|
startServer(); |
||||
|
} |
||||
|
|
||||
|
// connection successful try to connect to server
|
||||
|
if (socket != null) { |
||||
|
try { |
||||
|
// get message to be sent to first instance
|
||||
|
String message = sendMessage(); |
||||
|
|
||||
|
// open writer
|
||||
|
OutputStream os = socket.getOutputStream(); |
||||
|
DataOutputStream dos = new DataOutputStream(os); |
||||
|
|
||||
|
// open reader
|
||||
|
InputStream is = socket.getInputStream(); |
||||
|
DataInputStream dis = new DataInputStream(is); |
||||
|
|
||||
|
// write message to server
|
||||
|
if (message == null) { |
||||
|
dos.writeInt(-1); |
||||
|
} |
||||
|
else { |
||||
|
byte[] messageBytes = message.getBytes("UTF-8"); |
||||
|
|
||||
|
dos.writeInt(messageBytes.length); |
||||
|
dos.write(messageBytes); |
||||
|
} |
||||
|
|
||||
|
dos.flush(); |
||||
|
|
||||
|
// read response length from server
|
||||
|
int length = dis.readInt(); |
||||
|
|
||||
|
// read response string from server
|
||||
|
String response = null; |
||||
|
if (length > -1) { |
||||
|
byte[] responseBytes = new byte[length]; |
||||
|
int bytesRead = dis.read(responseBytes, 0, length); |
||||
|
response = new String(responseBytes, 0, bytesRead, "UTF-8"); |
||||
|
} |
||||
|
|
||||
|
// close writer and reader
|
||||
|
dos.close(); |
||||
|
dis.close(); |
||||
|
|
||||
|
if (response.equals(APP_ID)) { |
||||
|
// validation successful
|
||||
|
if (AUTO_EXIT) { |
||||
|
// perform pre-exit tasks
|
||||
|
beforeExit(); |
||||
|
// exit this instance
|
||||
|
System.exit(0); |
||||
|
} |
||||
|
} |
||||
|
else { |
||||
|
// validation failed, this is the first instance
|
||||
|
startServer(); |
||||
|
} |
||||
|
} catch (IOException e) { |
||||
|
throw new InstanceException(e); |
||||
|
} finally { |
||||
|
// close socket
|
||||
|
try { |
||||
|
if (socket != null) socket.close(); |
||||
|
} catch (IOException e) { |
||||
|
throw new InstanceException(e); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// try to get port from lock file
|
||||
|
private int lockFile() throws InstanceException { |
||||
|
// lock file path
|
||||
|
String filePath = TEMP_DIR + File.separator + APP_ID + ".lock"; |
||||
|
File file = new File(filePath); |
||||
|
|
||||
|
// try to get port from lock file
|
||||
|
if (file.exists()) { |
||||
|
BufferedReader br = null; |
||||
|
try { |
||||
|
br = new BufferedReader(new InputStreamReader(new FileInputStream(file))); |
||||
|
return Integer.parseInt(br.readLine()); |
||||
|
} catch (IOException e) { |
||||
|
throw new InstanceException(e); |
||||
|
} catch (NumberFormatException e) { |
||||
|
// do nothing
|
||||
|
} finally { |
||||
|
try { |
||||
|
if (br != null) br.close(); |
||||
|
} catch (IOException e) { |
||||
|
throw new InstanceException(e); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return -1; |
||||
|
} |
||||
|
|
||||
|
// try to write port to lock file
|
||||
|
private void lockFile(int port) throws InstanceException { |
||||
|
// lock file path
|
||||
|
String filePath = TEMP_DIR + File.separator + APP_ID + ".lock"; |
||||
|
File file = new File(filePath); |
||||
|
|
||||
|
// try to write port to lock file
|
||||
|
BufferedWriter bw = null; |
||||
|
try { |
||||
|
bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))); |
||||
|
bw.write(String.valueOf(port)); |
||||
|
} catch (IOException e) { |
||||
|
throw new InstanceException(e); |
||||
|
} finally { |
||||
|
try { |
||||
|
if (bw != null) bw.close(); |
||||
|
} catch (IOException e) { |
||||
|
throw new InstanceException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// try to obtain file lock
|
||||
|
try { |
||||
|
lockRAF = new RandomAccessFile(file, "rw"); |
||||
|
FileChannel fc = lockRAF.getChannel(); |
||||
|
fileLock = fc.tryLock(0, Long.MAX_VALUE, true); |
||||
|
if (fileLock == null) { |
||||
|
throw new InstanceException("Failed to obtain file lock"); |
||||
|
} |
||||
|
} catch (FileNotFoundException e) { |
||||
|
throw new InstanceException(e); |
||||
|
} catch (IOException e) { |
||||
|
throw new InstanceException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Free the lock if possible. This is only required to be called from the first instance. |
||||
|
* |
||||
|
* @deprecated Use <code>freeLock()</code> instead. |
||||
|
* @throws InstanceException throws InstanceException if it is unable to stop the server or release file lock |
||||
|
*/ |
||||
|
@Deprecated |
||||
|
public void free() throws InstanceException { |
||||
|
freeLock(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Free the lock if possible. This is only required to be called from the first instance. |
||||
|
* |
||||
|
* @since 1.2 |
||||
|
* |
||||
|
* @return true if able to release lock, false otherwise |
||||
|
* @throws InstanceException throws InstanceException if it is unable to stop the server or release file lock |
||||
|
*/ |
||||
|
public boolean freeLock() throws InstanceException { |
||||
|
try { |
||||
|
// close server socket
|
||||
|
if (server != null) { |
||||
|
server.close(); |
||||
|
|
||||
|
// lock file path
|
||||
|
String filePath = TEMP_DIR + File.separator + APP_ID + ".lock"; |
||||
|
File file = new File(filePath); |
||||
|
|
||||
|
// try to release file lock
|
||||
|
if (fileLock != null) { |
||||
|
fileLock.release(); |
||||
|
} |
||||
|
|
||||
|
// try to close lock file RAF object
|
||||
|
if (lockRAF != null) { |
||||
|
lockRAF.close(); |
||||
|
} |
||||
|
|
||||
|
// try to delete lock file
|
||||
|
if (file.exists()) { |
||||
|
file.delete(); |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} catch (IOException e) { |
||||
|
throw new InstanceException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Method used in first instance to receive messages from subsequent instances.<br><br> |
||||
|
* |
||||
|
* This method is not synchronized. |
||||
|
* |
||||
|
* @param message message received by first instance from subsequent instances |
||||
|
*/ |
||||
|
protected abstract void receiveMessage(String message); |
||||
|
|
||||
|
/** |
||||
|
* Method used in subsequent instances to send message to first instance.<br><br> |
||||
|
* |
||||
|
* It is not recommended to perform blocking (long running) tasks here. Use <code>beforeExit()</code> method instead.<br> |
||||
|
* One exception to this rule is if you intend to perform some user interaction before sending the message.<br><br> |
||||
|
* |
||||
|
* This method is not synchronized. |
||||
|
* |
||||
|
* @return message sent from subsequent instances |
||||
|
*/ |
||||
|
protected abstract String sendMessage(); |
||||
|
|
||||
|
/** |
||||
|
* Method to receive and handle exceptions occurring while first instance is listening for subsequent instances.<br><br> |
||||
|
* |
||||
|
* By default prints stack trace of all exceptions. Override this method to handle exceptions explicitly.<br><br> |
||||
|
* |
||||
|
* This method is not synchronized. |
||||
|
* |
||||
|
* @param exception exception occurring while first instance is listening for subsequent instances |
||||
|
*/ |
||||
|
protected void handleException(Exception exception) { |
||||
|
exception.printStackTrace(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* This method is called before exiting from subsequent instances.<br><br> |
||||
|
* |
||||
|
* Override this method to perform blocking tasks before exiting from subsequent instances.<br> |
||||
|
* This method is not invoked if auto exit is turned off.<br><br> |
||||
|
* |
||||
|
* This method is not synchronized. |
||||
|
* |
||||
|
* @since 1.2 |
||||
|
*/ |
||||
|
protected void beforeExit() {} |
||||
|
|
||||
|
} |
@ -0,0 +1,69 @@ |
|||||
|
/** |
||||
|
* Copyright 2019 Pratanu Mandal |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
* |
||||
|
*/ |
||||
|
|
||||
|
package com.sparrowwallet.sparrow.instance; |
||||
|
|
||||
|
/** |
||||
|
* The <code>InstanceException</code> class is a wrapper for all exceptions thrown from Instance. |
||||
|
* |
||||
|
* @author Pratanu Mandal |
||||
|
* @since 1.1 |
||||
|
* |
||||
|
*/ |
||||
|
public class InstanceException extends Exception { |
||||
|
|
||||
|
private static final long serialVersionUID = 268060627071973613L; |
||||
|
|
||||
|
/** |
||||
|
* Constructs a new exception with null as its detail message. |
||||
|
*/ |
||||
|
public InstanceException() { |
||||
|
super(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Constructs a new exception with the specified detail message.<br> |
||||
|
* The cause is not initialized, and may subsequently be initialized by a call to {@link #initCause}. |
||||
|
* |
||||
|
* @param message the detail message. The detail message is saved for later retrieval by the {@link #getMessage()} method. |
||||
|
*/ |
||||
|
public InstanceException(String message) { |
||||
|
super(message); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Constructs a new exception with the specified detail message and cause.<br><br> |
||||
|
* Note that the detail message associated with cause is not automatically incorporated in this exception's detail message. |
||||
|
* |
||||
|
* @param message the detail message (which is saved for later retrieval by the {@link #getMessage()} method). |
||||
|
* @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). (A null value is permitted, and indicates that the cause is nonexistent or unknown.) |
||||
|
*/ |
||||
|
public InstanceException(String message, Throwable cause) { |
||||
|
super(message, cause); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Constructs a new exception with the specified cause and a detail message of (cause==null ? null : cause.toString()) (which typically contains the class and detail message of cause).<br> |
||||
|
* This constructor is useful for exceptions that are little more than wrappers for other throwables (for example, {@link java.security.PrivilegedActionException}). |
||||
|
* |
||||
|
* @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). (A null value is permitted, and indicates that the cause is nonexistent or unknown.) |
||||
|
*/ |
||||
|
public InstanceException(Throwable cause) { |
||||
|
super(cause); |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,172 @@ |
|||||
|
package com.sparrowwallet.sparrow.instance; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
|
||||
|
import com.google.gson.JsonArray; |
||||
|
import com.google.gson.JsonElement; |
||||
|
import com.google.gson.JsonParser; |
||||
|
|
||||
|
/** |
||||
|
* The <code>InstanceList</code> class is a logical entry point to the library which extends the functionality of the <code>Instance</code> class.<br> |
||||
|
* It allows to create an application lock or free it and send and receive messages between first and subsequent instances.<br><br> |
||||
|
* |
||||
|
* This class is intended for passing a list of strings instead of a single string from the subsequent instance to the first instance.<br><br> |
||||
|
* |
||||
|
* <pre> |
||||
|
* // unique application ID
|
||||
|
* String APP_ID = "tk.pratanumandal.unique4j-mlsdvo-20191511-#j.6"; |
||||
|
* |
||||
|
* // create Instance instance
|
||||
|
* Instance unique = new InstanceList(APP_ID) { |
||||
|
* @Override |
||||
|
* protected List<String> sendMessageList() { |
||||
|
* List<String> messageList = new ArrayList<String>(); |
||||
|
* |
||||
|
* messageList.add("Message 1"); |
||||
|
* messageList.add("Message 2"); |
||||
|
* messageList.add("Message 3"); |
||||
|
* messageList.add("Message 4"); |
||||
|
* |
||||
|
* return messageList; |
||||
|
* } |
||||
|
* |
||||
|
* @Override |
||||
|
* protected void receiveMessageList(List<String> messageList) { |
||||
|
* for (String message : messageList) { |
||||
|
* System.out.println(message); |
||||
|
* } |
||||
|
* } |
||||
|
* }; |
||||
|
* |
||||
|
* // try to obtain lock
|
||||
|
* try { |
||||
|
* unique.acquireLock(); |
||||
|
* } catch (InstanceException e) { |
||||
|
* e.printStackTrace(); |
||||
|
* } |
||||
|
* |
||||
|
* ... |
||||
|
* |
||||
|
* // try to free the lock before exiting program
|
||||
|
* try { |
||||
|
* unique.freeLock(); |
||||
|
* } catch (InstanceException e) { |
||||
|
* e.printStackTrace(); |
||||
|
* } |
||||
|
* </pre> |
||||
|
* |
||||
|
* @author Pratanu Mandal |
||||
|
* @since 1.3 |
||||
|
* |
||||
|
*/ |
||||
|
public abstract class InstanceList extends Instance { |
||||
|
|
||||
|
/** |
||||
|
* Parameterized constructor.<br> |
||||
|
* This constructor configures to automatically exit the application for subsequent instances.<br><br> |
||||
|
* |
||||
|
* The APP_ID must be as unique as possible. |
||||
|
* Avoid generic names like "my_app_id" or "hello_world".<br> |
||||
|
* A good strategy is to use the entire package name (group ID + artifact ID) along with some random characters. |
||||
|
* |
||||
|
* @param APP_ID Unique string representing the application ID |
||||
|
*/ |
||||
|
public InstanceList(String APP_ID) { |
||||
|
super(APP_ID); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Parameterized constructor.<br> |
||||
|
* This constructor allows to explicitly specify the exit strategy for subsequent instances.<br><br> |
||||
|
* |
||||
|
* The APP_ID must be as unique as possible. |
||||
|
* Avoid generic names like "my_app_id" or "hello_world".<br> |
||||
|
* A good strategy is to use the entire package name (group ID + artifact ID) along with some random characters. |
||||
|
* |
||||
|
* @param APP_ID Unique string representing the application ID |
||||
|
* @param AUTO_EXIT If true, automatically exit the application for subsequent instances |
||||
|
*/ |
||||
|
public InstanceList(String APP_ID, boolean AUTO_EXIT) { |
||||
|
super(APP_ID, AUTO_EXIT); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Internal method used in first instance to receive and parse messages from subsequent instances.<br> |
||||
|
* The use of this method directly in <code>InstanceList</code> is discouraged. Use <code>receiveMessageList()</code> instead.<br><br> |
||||
|
* |
||||
|
* This method is not synchronized. |
||||
|
* |
||||
|
* @param message message received by first instance from subsequent instances |
||||
|
*/ |
||||
|
@Override |
||||
|
protected final void receiveMessage(String message) { |
||||
|
if (message == null) { |
||||
|
receiveMessageList(null); |
||||
|
} |
||||
|
else { |
||||
|
// parse the JSON array string into an array of string arguments
|
||||
|
JsonArray jsonArgs = JsonParser.parseString(message).getAsJsonArray(); |
||||
|
|
||||
|
List<String> stringArgs = new ArrayList<String>(jsonArgs.size()); |
||||
|
|
||||
|
for (int i = 0; i < jsonArgs.size(); i++) { |
||||
|
JsonElement element = jsonArgs.get(i); |
||||
|
stringArgs.add(element.getAsString()); |
||||
|
} |
||||
|
|
||||
|
// return the parsed string list
|
||||
|
receiveMessageList(stringArgs); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Internal method used in subsequent instances to parse and send message to first instance.<br> |
||||
|
* The use of this method directly in <code>InstanceList</code> is discouraged. Use <code>sendMessageList()</code> instead.<br><br> |
||||
|
* |
||||
|
* It is not recommended to perform blocking (long running) tasks here. Use <code>beforeExit()</code> method instead.<br> |
||||
|
* One exception to this rule is if you intend to perform some user interaction before sending the message.<br><br> |
||||
|
* |
||||
|
* This method is not synchronized. |
||||
|
* |
||||
|
* @return message sent from subsequent instances |
||||
|
*/ |
||||
|
@Override |
||||
|
protected final String sendMessage() { |
||||
|
// convert arguments to JSON array string
|
||||
|
JsonArray jsonArgs = new JsonArray(); |
||||
|
|
||||
|
List<String> stringArgs = sendMessageList(); |
||||
|
|
||||
|
if (stringArgs == null) return null; |
||||
|
|
||||
|
for (String arg : stringArgs) { |
||||
|
jsonArgs.add(arg); |
||||
|
} |
||||
|
|
||||
|
// return the JSON array string
|
||||
|
return jsonArgs.toString(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Method used in first instance to receive list of messages from subsequent instances.<br><br> |
||||
|
* |
||||
|
* This method is not synchronized. |
||||
|
* |
||||
|
* @param messageList list of messages received by first instance from subsequent instances |
||||
|
*/ |
||||
|
protected abstract void receiveMessageList(List<String> messageList); |
||||
|
|
||||
|
/** |
||||
|
* Method used in subsequent instances to send list of messages to first instance.<br><br> |
||||
|
* |
||||
|
* It is not recommended to perform blocking (long running) tasks here. Use <code>beforeExit()</code> method instead.<br> |
||||
|
* One exception to this rule is if you intend to perform some user interaction before sending the message.<br><br> |
||||
|
* |
||||
|
* This method is not synchronized. |
||||
|
* |
||||
|
* @return list of messages sent from subsequent instances |
||||
|
*/ |
||||
|
protected abstract List<String> sendMessageList(); |
||||
|
|
||||
|
} |
Loading…
Reference in new issue