1 /* 2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.nashorn.tools.jjs; 27 28 import java.io.IOException; 29 import java.nio.charset.Charset; 30 import java.nio.file.ClosedWatchServiceException; 31 import java.nio.file.FileSystems; 32 import java.nio.file.Files; 33 import java.nio.file.Path; 34 import java.nio.file.WatchKey; 35 import java.nio.file.WatchService; 36 import java.util.List; 37 import java.util.function.Consumer; 38 import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; 39 import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; 40 import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; 41 42 final class ExternalEditor { 43 private final Consumer<String> errorHandler; 44 private final Consumer<String> saveHandler; 45 private final Console input; 46 47 private WatchService watcher; 48 private Thread watchedThread; 49 private Path dir; 50 private Path tmpfile; 51 52 ExternalEditor(final Consumer<String> errorHandler, final Consumer<String> saveHandler, final Console input) { 53 this.errorHandler = errorHandler; 54 this.saveHandler = saveHandler; 55 this.input = input; 56 } 57 58 private void edit(final String cmd, final String initialText) { 59 try { 60 setupWatch(initialText); 61 launch(cmd); 62 } catch (IOException ex) { 63 errorHandler.accept(ex.getMessage()); 64 } 65 } 66 67 /** 68 * Creates a WatchService and registers the given directory 69 */ 70 private void setupWatch(final String initialText) throws IOException { 71 this.watcher = FileSystems.getDefault().newWatchService(); 72 this.dir = Files.createTempDirectory("REPL"); 73 this.tmpfile = Files.createTempFile(dir, null, ".js"); 74 Files.write(tmpfile, initialText.getBytes(Charset.forName("UTF-8"))); 75 dir.register(watcher, 76 ENTRY_CREATE, 77 ENTRY_DELETE, 78 ENTRY_MODIFY); 79 watchedThread = new Thread(() -> { 80 for (;;) { 81 WatchKey key; 82 try { 83 key = watcher.take(); 84 } catch (final ClosedWatchServiceException ex) { 85 break; 86 } catch (final InterruptedException ex) { 87 continue; // tolerate an intrupt 88 } 89 90 if (!key.pollEvents().isEmpty()) { 91 if (!input.terminalEditorRunning()) { 92 saveFile(); 93 } 94 } 95 96 boolean valid = key.reset(); 97 if (!valid) { 98 errorHandler.accept("Invalid key"); 99 break; 100 } 101 } 102 }); 103 watchedThread.start(); 104 } 105 106 private void launch(final String cmd) throws IOException { 107 ProcessBuilder pb = new ProcessBuilder(cmd, tmpfile.toString()); 108 pb = pb.inheritIO(); 109 110 try { 111 input.suspend(); 112 Process process = pb.start(); 113 process.waitFor(); 114 } catch (final IOException ex) { 115 errorHandler.accept("process IO failure: " + ex.getMessage()); 116 } catch (final InterruptedException ex) { 117 errorHandler.accept("process interrupt: " + ex.getMessage()); 118 } finally { 119 try { 120 watcher.close(); 121 watchedThread.join(); //so that saveFile() is finished. 122 saveFile(); 123 } catch (InterruptedException ex) { 124 errorHandler.accept("process interrupt: " + ex.getMessage()); 125 } finally { 126 input.resume(); 127 } 128 } 129 } 130 131 private void saveFile() { 132 List<String> lines; 133 try { 134 lines = Files.readAllLines(tmpfile); 135 } catch (final IOException ex) { 136 errorHandler.accept("Failure read edit file: " + ex.getMessage()); 137 return ; 138 } 139 StringBuilder sb = new StringBuilder(); 140 for (String ln : lines) { 141 sb.append(ln); 142 sb.append('\n'); 143 } 144 saveHandler.accept(sb.toString()); 145 } 146 147 static void edit(final String cmd, final Consumer<String> errorHandler, final String initialText, 148 final Consumer<String> saveHandler, final Console input) { 149 ExternalEditor ed = new ExternalEditor(errorHandler, saveHandler, input); 150 ed.edit(cmd, initialText); 151 } 152 }