1 /* 2 * Copyright (c) 2012, 2014, Oracle and/or its affiliates. 3 * All rights reserved. Use is subject to license terms. 4 * 5 * This file is available and licensed under the following license: 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * - Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * - Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the distribution. 16 * - Neither the name of Oracle Corporation nor the names of its 17 * contributors may be used to endorse or promote products derived 18 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package com.oracle.javafx.scenebuilder.app.util; 33 34 import java.io.ByteArrayInputStream; 35 import java.io.ByteArrayOutputStream; 36 import java.io.IOException; 37 import java.io.ObjectInputStream; 38 import java.io.ObjectOutputStream; 39 import java.io.Serializable; 40 import java.nio.file.AccessDeniedException; 41 import java.nio.file.Files; 42 import java.nio.file.Path; 43 import java.nio.file.Paths; 44 import java.nio.file.StandardCopyOption; 45 46 /** 47 * This class implements an simple IPC. 48 * 49 * If n processes start together and performs: 50 * 1) MessageBox mb = new MessageBox() 51 * 2) boolean grabbed = mb.grab(...) 52 * only one of the n processed will get grabbed = true : it's message box owner. 53 * 54 * Other processes (which got grabbed == false) can then perform: 55 * 3) mb.sendMessage(myMessage) 56 * 57 * The message box owner will then receive the messages through 58 * its Delegate instance. 59 * 60 * @param <T> 61 */ 62 public class MessageBox<T extends Serializable> { 63 64 public static final long NAP_TIME = 100; // ms 65 66 final private String folder; 67 final private Class<T> messageClass; 68 final int pollingTime; // milliseconds 69 final Path messageFile; 70 final FileMutex boxMutex; 71 final FileMutex messageMutex; 72 73 private PollingThread<T> pollingThread; 74 private Delegate<T> delegate; 75 76 public MessageBox(String folder, Class<T> messageClass, int pollingTime) { 77 assert folder != null; 78 assert messageClass != null; 79 assert pollingTime > 0; 80 81 this.folder = folder; 82 this.messageClass = messageClass; 83 this.pollingTime = pollingTime; 84 this.messageFile = Paths.get(folder, "message.dat"); //NOI18N 85 this.boxMutex = new FileMutex(Paths.get(folder, "box.mtx")); //NOI18N 86 this.messageMutex = new FileMutex(Paths.get(folder,"message.mtx")); //NOI18N 87 88 if (Files.exists(Paths.get(folder)) == false) { 89 throw new IllegalArgumentException(folder + " does not exist"); //NOI18N 90 } 91 } 92 93 public String getFolder() { 94 return folder; 95 } 96 97 public boolean grab(Delegate<T> delegate) 98 throws IOException { 99 assert boxMutex.isLocked() == false; 100 assert pollingThread == null; 101 assert delegate != null; 102 103 if (boxMutex.tryLock()) { 104 this.delegate = delegate; 105 this.pollingThread = new PollingThread<>(this); 106 this.pollingThread.setDaemon(true); 107 this.pollingThread.start(); 108 } 109 110 return boxMutex.isLocked(); 111 } 112 113 114 public void release() { 115 assert boxMutex.isLocked(); 116 assert pollingThread != null; 117 assert pollingThread.isAlive(); 118 119 pollingThread.interrupt(); 120 pollingThread = null; 121 122 try { 123 boxMutex.unlock(); 124 } catch(IOException x) { 125 // Strange 126 x.printStackTrace(); 127 } 128 } 129 130 131 public void sendMessage(T message) throws IOException, InterruptedException { 132 assert boxMutex.isLocked() == false; 133 assert messageMutex.isLocked() == false; 134 135 final Path transientFile = Files.createTempFile(Paths.get(folder), null, null); 136 Files.write(transientFile, serializeMessage(message)); 137 138 messageMutex.lock(100L * pollingTime); 139 boolean retry; 140 int accessDeniedCount = 0; 141 do { 142 if (Files.exists(messageFile) == false) { 143 try { 144 Files.move(transientFile, messageFile, StandardCopyOption.ATOMIC_MOVE); 145 retry = false; 146 } catch(AccessDeniedException x) { 147 // Sometime on Windows, move is denied (?). 148 // So we retry a few times... 149 if (accessDeniedCount++ <= 10) { 150 retry = true; 151 } else { 152 throw x; 153 } 154 } 155 } else { 156 retry = true; 157 } 158 if (retry) { 159 Thread.sleep(NAP_TIME); 160 } 161 } while (retry); 162 messageMutex.unlock(); 163 } 164 165 public interface Delegate<T> { 166 public void messageBoxDidGetMessage(T message); 167 public void messageBoxDidCatchException(Exception x); 168 } 169 170 public Path getMessagePath() { 171 return messageFile; 172 } 173 174 public Path getBoxMutexPath() { 175 return boxMutex.getLockFile(); 176 } 177 178 public Path getMessageMutexPath() { 179 return messageMutex.getLockFile(); 180 } 181 182 183 /* 184 * Private 185 */ 186 187 private byte[] serializeMessage(T message) throws IOException { 188 final byte[] result; 189 190 try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { 191 try (ObjectOutputStream oos = new ObjectOutputStream(bos)) { 192 oos.writeObject(message); 193 result = bos.toByteArray(); 194 } 195 } 196 197 return result; 198 } 199 200 201 private T unserializeMessage(byte[] bytes) throws IOException { 202 final T result; 203 204 try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes)) { 205 try (ObjectInputStream ois = new ObjectInputStream(bis)) { 206 try { 207 result = messageClass.cast(ois.readObject()); 208 } catch(ClassNotFoundException x) { 209 // Strange 210 throw new IOException(x); 211 } 212 } 213 } 214 215 return result; 216 } 217 218 219 private static class PollingThread<T extends Serializable> extends Thread { 220 221 private final MessageBox<T> messageBox; 222 223 public PollingThread(MessageBox<T> messageBox) { 224 super("MessageBox[" + messageBox.getFolder() + "]"); //NOI18N 225 this.messageBox = messageBox; 226 } 227 228 @Override 229 public void run() { 230 231 try { 232 do { 233 if (Files.exists(messageBox.messageFile)) { 234 try { 235 final byte[] messageBytes = Files.readAllBytes(messageBox.messageFile); 236 final T message = messageBox.unserializeMessage(messageBytes); 237 messageBox.delegate.messageBoxDidGetMessage(message); 238 } catch(IOException x) { 239 messageBox.delegate.messageBoxDidCatchException(x); 240 } finally { 241 try { 242 Files.delete(messageBox.messageFile); 243 } catch(IOException x) { 244 messageBox.delegate.messageBoxDidCatchException(x); 245 } 246 } 247 Thread.sleep(NAP_TIME); 248 } else { 249 Thread.sleep(messageBox.pollingTime); 250 } 251 } while (true); 252 253 } catch(InterruptedException x) { 254 } 255 } 256 } 257 }