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