langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ExternalEditor.java
changeset 41939 4e7ef9667ea6
parent 41930 394bc7b2237f
parent 41938 8e66bf10fcec
child 41940 048d559e9da7
equal deleted inserted replaced
41930:394bc7b2237f 41939:4e7ef9667ea6
     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.internal.jshell.tool;
       
    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.Arrays;
       
    37 import java.util.Scanner;
       
    38 import java.util.function.Consumer;
       
    39 import java.util.stream.Collectors;
       
    40 import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
       
    41 import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
       
    42 import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
       
    43 
       
    44 /**
       
    45  * Wrapper for controlling an external editor.
       
    46  */
       
    47 public class ExternalEditor {
       
    48     private final Consumer<String> errorHandler;
       
    49     private final Consumer<String> saveHandler;
       
    50     private final Consumer<String> printHandler;
       
    51     private final IOContext input;
       
    52     private final boolean wait;
       
    53 
       
    54     private WatchService watcher;
       
    55     private Thread watchedThread;
       
    56     private Path dir;
       
    57     private Path tmpfile;
       
    58 
       
    59     ExternalEditor(Consumer<String> errorHandler, Consumer<String> saveHandler,
       
    60             IOContext input, boolean wait, Consumer<String> printHandler) {
       
    61         this.errorHandler = errorHandler;
       
    62         this.saveHandler = saveHandler;
       
    63         this.printHandler = printHandler;
       
    64         this.input = input;
       
    65         this.wait = wait;
       
    66     }
       
    67 
       
    68     private void edit(String[] cmd, String initialText) {
       
    69         try {
       
    70             setupWatch(initialText);
       
    71             launch(cmd);
       
    72         } catch (IOException ex) {
       
    73             errorHandler.accept(ex.getMessage());
       
    74         }
       
    75     }
       
    76 
       
    77     /**
       
    78      * Creates a WatchService and registers the given directory
       
    79      */
       
    80     private void setupWatch(String initialText) throws IOException {
       
    81         this.watcher = FileSystems.getDefault().newWatchService();
       
    82         this.dir = Files.createTempDirectory("jshelltemp");
       
    83         this.tmpfile = Files.createTempFile(dir, null, ".java");
       
    84         Files.write(tmpfile, initialText.getBytes(Charset.forName("UTF-8")));
       
    85         dir.register(watcher,
       
    86                 ENTRY_CREATE,
       
    87                 ENTRY_DELETE,
       
    88                 ENTRY_MODIFY);
       
    89         watchedThread = new Thread(() -> {
       
    90             for (;;) {
       
    91                 WatchKey key;
       
    92                 try {
       
    93                     key = watcher.take();
       
    94                 } catch (ClosedWatchServiceException ex) {
       
    95                     // The watch service has been closed, we are done
       
    96                     break;
       
    97                 } catch (InterruptedException ex) {
       
    98                     // tolerate an interrupt
       
    99                     continue;
       
   100                 }
       
   101 
       
   102                 if (!key.pollEvents().isEmpty()) {
       
   103                     // Changes have occurred in temp edit directory,
       
   104                     // transfer the new sources to JShell (unless the editor is
       
   105                     // running directly in JShell's window -- don't make a mess)
       
   106                     if (!input.terminalEditorRunning()) {
       
   107                         saveFile();
       
   108                     }
       
   109                 }
       
   110 
       
   111                 boolean valid = key.reset();
       
   112                 if (!valid) {
       
   113                     // The watch service has been closed, we are done
       
   114                     break;
       
   115                 }
       
   116             }
       
   117         });
       
   118         watchedThread.start();
       
   119     }
       
   120 
       
   121     private void launch(String[] cmd) throws IOException {
       
   122         String[] params = Arrays.copyOf(cmd, cmd.length + 1);
       
   123         params[cmd.length] = tmpfile.toString();
       
   124         ProcessBuilder pb = new ProcessBuilder(params);
       
   125         pb = pb.inheritIO();
       
   126 
       
   127         try {
       
   128             input.suspend();
       
   129             Process process = pb.start();
       
   130             // wait to exit edit mode in one of these ways...
       
   131             if (wait) {
       
   132                 // -wait option -- ignore process exit, wait for carriage-return
       
   133                 Scanner scanner = new Scanner(System.in);
       
   134                 printHandler.accept("jshell.msg.press.return.to.leave.edit.mode");
       
   135                 scanner.nextLine();
       
   136             } else {
       
   137                 // wait for process to exit
       
   138                 process.waitFor();
       
   139             }
       
   140         } catch (IOException ex) {
       
   141             errorHandler.accept("process IO failure: " + ex.getMessage());
       
   142         } catch (InterruptedException ex) {
       
   143             errorHandler.accept("process interrupt: " + ex.getMessage());
       
   144         } finally {
       
   145             try {
       
   146                 watcher.close();
       
   147                 watchedThread.join(); //so that saveFile() is finished.
       
   148                 saveFile();
       
   149             } catch (InterruptedException ex) {
       
   150                 errorHandler.accept("process interrupt: " + ex.getMessage());
       
   151             } finally {
       
   152                 input.resume();
       
   153             }
       
   154         }
       
   155     }
       
   156 
       
   157     private void saveFile() {
       
   158         try {
       
   159             saveHandler.accept(Files.lines(tmpfile).collect(Collectors.joining("\n", "", "\n")));
       
   160         } catch (IOException ex) {
       
   161             errorHandler.accept("Failure in read edit file: " + ex.getMessage());
       
   162         }
       
   163     }
       
   164 
       
   165     static void edit(String[] cmd, Consumer<String> errorHandler, String initialText,
       
   166             Consumer<String> saveHandler, IOContext input, boolean wait, Consumer<String> printHandler) {
       
   167         ExternalEditor ed = new ExternalEditor(errorHandler, saveHandler, input, wait, printHandler);
       
   168         ed.edit(cmd, initialText);
       
   169     }
       
   170 }