ExternalEditor.java revision 1384:5beae9dfcdb9
142629Sobrien/* 238494Sobrien * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 338494Sobrien * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 438494Sobrien * 542629Sobrien * This code is free software; you can redistribute it and/or modify it 638494Sobrien * under the terms of the GNU General Public License version 2 only, as 742629Sobrien * published by the Free Software Foundation. Oracle designates this 838494Sobrien * particular file as subject to the "Classpath" exception as provided 938494Sobrien * by Oracle in the LICENSE file that accompanied this code. 1038494Sobrien * 1138494Sobrien * This code is distributed in the hope that it will be useful, but WITHOUT 1238494Sobrien * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1338494Sobrien * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 1438494Sobrien * version 2 for more details (a copy is included in the LICENSE file that 1538494Sobrien * accompanied this code). 1638494Sobrien * 1738494Sobrien * You should have received a copy of the GNU General Public License version 1838494Sobrien * 2 along with this work; if not, write to the Free Software Foundation, 1938494Sobrien * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 2038494Sobrien * 2138494Sobrien * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 2238494Sobrien * or visit www.oracle.com if you need additional information or have any 2338494Sobrien * questions. 2438494Sobrien */ 2538494Sobrien 2638494Sobrienpackage jdk.nashorn.tools.jjs; 2738494Sobrien 2838494Sobrienimport java.io.IOException; 2938494Sobrienimport java.nio.charset.Charset; 3038494Sobrienimport java.nio.file.ClosedWatchServiceException; 3138494Sobrienimport java.nio.file.FileSystems; 3238494Sobrienimport java.nio.file.Files; 3338494Sobrienimport java.nio.file.Path; 3438494Sobrienimport java.nio.file.WatchKey; 3538494Sobrienimport java.nio.file.WatchService; 3638494Sobrienimport java.util.List; 3738494Sobrienimport java.util.function.Consumer; 3838494Sobrienimport static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; 3938494Sobrienimport static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; 4038494Sobrienimport static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; 4138494Sobrien 4238494Sobrienfinal class ExternalEditor { 4338494Sobrien private final Consumer<String> errorHandler; 4438494Sobrien private final Consumer<String> saveHandler; 4538494Sobrien private final Console input; 4638494Sobrien 4738494Sobrien private WatchService watcher; 4838494Sobrien private Thread watchedThread; 4938494Sobrien private Path dir; 5038494Sobrien private Path tmpfile; 5138494Sobrien 5238494Sobrien ExternalEditor(Consumer<String> errorHandler, Consumer<String> saveHandler, Console input) { 5338494Sobrien this.errorHandler = errorHandler; 5438494Sobrien this.saveHandler = saveHandler; 5538494Sobrien this.input = input; 5638494Sobrien } 5738494Sobrien 5838494Sobrien private void edit(String cmd, String initialText) { 5938494Sobrien try { 6038494Sobrien setupWatch(initialText); 6138494Sobrien launch(cmd); 62119679Smbr } catch (IOException ex) { 6338494Sobrien errorHandler.accept(ex.getMessage()); 6438494Sobrien } 6538494Sobrien } 6638494Sobrien 6738494Sobrien /** 6838494Sobrien * Creates a WatchService and registers the given directory 6938494Sobrien */ 7038494Sobrien private void setupWatch(String initialText) throws IOException { 7138494Sobrien this.watcher = FileSystems.getDefault().newWatchService(); 7238494Sobrien this.dir = Files.createTempDirectory("REPL"); 7338494Sobrien this.tmpfile = Files.createTempFile(dir, null, ".js"); 7438494Sobrien Files.write(tmpfile, initialText.getBytes(Charset.forName("UTF-8"))); 75119679Smbr dir.register(watcher, 7682794Sobrien ENTRY_CREATE, 7782794Sobrien ENTRY_DELETE, 7838494Sobrien ENTRY_MODIFY); 7982794Sobrien watchedThread = new Thread(() -> { 8038494Sobrien for (;;) { 8138494Sobrien WatchKey key; 8238494Sobrien try { 8382794Sobrien key = watcher.take(); 84310490Scy } catch (ClosedWatchServiceException ex) { 8582794Sobrien break; 86119679Smbr } catch (InterruptedException ex) { 87119679Smbr continue; // tolerate an intrupt 8838494Sobrien } 8938494Sobrien 9038494Sobrien if (!key.pollEvents().isEmpty()) { 9138494Sobrien if (!input.terminalEditorRunning()) { 9238494Sobrien saveFile(); 9338494Sobrien } 9438494Sobrien } 9538494Sobrien 9638494Sobrien boolean valid = key.reset(); 97310490Scy if (!valid) { 98310490Scy errorHandler.accept("Invalid key"); 99310490Scy break; 10038494Sobrien } 10138494Sobrien } 10238494Sobrien }); 10338494Sobrien watchedThread.start(); 10438494Sobrien } 105 106 private void launch(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 (IOException ex) { 115 errorHandler.accept("process IO failure: " + ex.getMessage()); 116 } catch (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 (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(String cmd, Consumer<String> errorHandler, String initialText, 148 Consumer<String> saveHandler, Console input) { 149 ExternalEditor ed = new ExternalEditor(errorHandler, saveHandler, input); 150 ed.edit(cmd, initialText); 151 } 152} 153