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