Package org.jetbrains.plugins.clojure.repl

Source Code of org.jetbrains.plugins.clojure.repl.ClojureReplProcessHandler$ProcessWaitFor

/*
* Copyright 2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.clojure.repl;

import clojure.lang.AFn;
import com.intellij.execution.CantRunException;
import com.intellij.execution.configurations.CommandLineBuilder;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.configurations.JavaParameters;
import com.intellij.execution.process.ProcessAdapter;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.process.ProcessOutputTypes;
import com.intellij.ide.DataManager;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.projectRoots.JavaSdkType;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.SdkType;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ModuleSourceOrderEntry;
import com.intellij.openapi.roots.OrderEntry;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.encoding.EncodingManager;
import com.intellij.util.Alarm;
import com.intellij.util.PathUtil;
import org.jetbrains.plugins.clojure.config.ClojureConfigUtil;
import org.jetbrains.plugins.clojure.utils.ClojureUtils;

import java.io.*;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.*;

/**
* @author Kurt Christensen, ilyas
* @author <a href="mailto:ianp@ianp.org">Ian Phillips</a>
*/
public class ClojureReplProcessHandler extends ProcessHandler {

  private static final Logger LOG = Logger.getInstance(ClojureReplProcessHandler.class.getName());
  private static ExecutorService ourThreadExecutorsService = null;

  private final Process myProcess;
  private final ProcessWaitFor myWaitFor;
  private final String myExecPath;
  private final Module myModule;

  private static final String CLOJURE_SDK = PathUtil.getJarPathForClass(AFn.class);

  public static Future<?> executeOnPooledThread(Runnable task) {
    final Application application = ApplicationManager.getApplication();

    if (application != null) {
      return application.executeOnPooledThread(task);
    } else {
      if (ourThreadExecutorsService == null) {
        ourThreadExecutorsService = new ThreadPoolExecutor(
            10,
            Integer.MAX_VALUE,
            60L,
            TimeUnit.SECONDS,
            new SynchronousQueue<Runnable>(),
            new ThreadFactory() {
              @SuppressWarnings({"HardCodedStringLiteral"})
              public Thread newThread(Runnable r) {
                return new Thread(r, "OSProcessHandler pooled thread");
              }
            }
        );
      }

      return ourThreadExecutorsService.submit(task);
    }
  }

  public ClojureReplProcessHandler(String path, Module module)
      throws IOException, ConfigurationException, CantRunException {

    myExecPath = path;
    myModule = module;

    if (notConfigured()) {
      throw new ConfigurationException("Can't create Clojure REPL process");
    } else {
      // Only a single command line per-project is supported. We may need more flexibility
      //  in the future (e.g., different Clojure paths with different args)

      final JavaParameters params = new JavaParameters();
      params.configureByModule(myModule, JavaParameters.JDK_AND_CLASSES_AND_TESTS);
      // To avoid NCDFE while starting REPL

      final boolean sdkConfigured = ClojureConfigUtil.isClojureConfigured(myModule);
      if (!sdkConfigured) {
        final String jarPath = ClojureConfigUtil.CLOJURE_SDK;
        assert jarPath != null;
        params.getClassPath().add(jarPath);
      }

      Set<VirtualFile> cpVFiles = new HashSet<VirtualFile>();
      ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(myModule);
      OrderEntry[] entries = moduleRootManager.getOrderEntries();
      for (OrderEntry orderEntry : entries) {
        // Add module sources to classpath
        if (orderEntry instanceof ModuleSourceOrderEntry) {
          cpVFiles.addAll(Arrays.asList(orderEntry.getFiles(OrderRootType.SOURCES)));
        }
      }

      for (VirtualFile file : cpVFiles) {
        params.getClassPath().add(file.getPath());
      }


      params.setMainClass(ClojureUtils.CLOJURE_MAIN);
      params.setWorkingDirectory(path);

      final GeneralCommandLine line = CommandLineBuilder.createFromJavaParameters(params, PlatformDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext()), true);

      final Sdk sdk = params.getJdk();
      assert sdk != null;
      final SdkType type = (SdkType) sdk.getSdkType();
      final String executablePath = ((JavaSdkType) type).getVMExecutablePath(sdk);

      final ArrayList<String> env = new ArrayList<String>();
      final ArrayList<String> cmd = new ArrayList<String>();
      cmd.add(executablePath);
      cmd.addAll(line.getParametersList().getList());

      if (!sdkConfigured) {
        ClojureConfigUtil.warningDefaultClojureJar(myModule);
      }

      myProcess = Runtime.getRuntime().exec(cmd.toArray(new String[cmd.size()]),
          env.toArray(new String[env.size()]), new File(myExecPath));
      myWaitFor = new ProcessWaitFor(myProcess);
    }
  }

  private boolean notConfigured() {
    return false;
  }

  private static class ProcessWaitFor {
    private final com.intellij.util.concurrency.Semaphore myWaitSemaphore = new com.intellij.util.concurrency.Semaphore();

    private final Future<?> myWaitForThreadFuture;
    private int myExitCode;

    public void detach() {
      myWaitForThreadFuture.cancel(true);
      myWaitSemaphore.up();
    }

    public ProcessWaitFor(final Process process) {
      myWaitSemaphore.down();
      final Runnable action = new Runnable() {
        public void run() {
          try {
            myExitCode = process.waitFor();
          }
          catch (InterruptedException e) {
            // Do nothing, by design
          }
          myWaitSemaphore.up();
        }
      };

      myWaitForThreadFuture = executeOnPooledThread(action);
    }

    public int waitFor() {
      myWaitSemaphore.waitFor();
      return myExitCode;
    }
  }

  public void startNotify() {
    final ReadProcessThread stdoutThread = new ReadProcessThread(createProcessOutReader()) {
      protected void textAvailable(String s) {
        notifyTextAvailable(s, ProcessOutputTypes.STDOUT);
      }
    };

    final ReadProcessThread stderrThread = new ReadProcessThread(createProcessErrReader()) {
      protected void textAvailable(String s) {
        notifyTextAvailable(s, ProcessOutputTypes.STDERR);
      }
    };

    //notifyTextAvailable(myCommandLine + '\n', ProcessOutputTypes.SYSTEM);

    addProcessListener(new ProcessAdapter() {
      public void startNotified(final ProcessEvent event) {
        try {
          final Future<?> stdOutReadingFuture = executeOnPooledThread(stdoutThread);
          final Future<?> stdErrReadingFuture = executeOnPooledThread(stderrThread);

          final Runnable action = new Runnable() {
            public void run() {
              int exitCode = 0;

              try {
                exitCode = myWaitFor.waitFor();

                // tell threads that no more attempts to read process' output should be made
                stderrThread.setProcessTerminated(true);
                stdoutThread.setProcessTerminated(true);

                stdErrReadingFuture.get();
                stdOutReadingFuture.get();
              }
              catch (InterruptedException e) {
                // Do nothing
              }
              catch (ExecutionException e) {
                // Do nothing
              }

              onOSProcessTerminated(exitCode);
            }
          };

          executeOnPooledThread(action);
        }
        finally {
          removeProcessListener(this);
        }
      }
    });

    super.startNotify();
  }

  protected void onOSProcessTerminated(final int exitCode) {
    notifyProcessTerminated(exitCode);
  }

  protected Reader createProcessOutReader() {
    return new BufferedReader(new InputStreamReader(myProcess.getInputStream(), getCharset()));
  }

  protected Reader createProcessErrReader() {
    return new BufferedReader(new InputStreamReader(myProcess.getErrorStream(), getCharset()));
  }

  protected void destroyProcessImpl() {
    try {
      closeStreams();
    }
    finally {
      myProcess.destroy();
    }
  }

  protected void detachProcessImpl() {
    final Runnable runnable = new Runnable() {
      public void run() {
        closeStreams();

        myWaitFor.detach();
        notifyProcessDetached();
      }
    };

    executeOnPooledThread(runnable);
  }

  private void closeStreams() {
    try {
      myProcess.getOutputStream().close();
    }
    catch (IOException e) {
      LOG.error(e);
    }
  }

  public boolean detachIsDefault() {
    return false;
  }

  public OutputStream getProcessInput() {
    return myProcess.getOutputStream();
  }

  public Charset getCharset() {
    return EncodingManager.getInstance().getDefaultCharset();
  }

  private static abstract class ReadProcessThread implements Runnable {
    private static final int NOTIFY_TEXT_DELAY = 300;

    private final Reader myReader;

    private final StringBuffer myBuffer = new StringBuffer();
    private final Alarm myAlarm;

    private boolean myIsClosed = false;
    private boolean myIsProcessTerminated = false;

    public ReadProcessThread(final Reader reader) {
      myReader = reader;
      myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
    }

    public synchronized boolean isProcessTerminated() {
      return myIsProcessTerminated;
    }

    public synchronized void setProcessTerminated(boolean isProcessTerminated) {
      myIsProcessTerminated = isProcessTerminated;
    }

    public void run() {
      Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
      try {
        myAlarm.addRequest(new Runnable() {
          public void run() {
            if (!isClosed()) {
              myAlarm.addRequest(this, NOTIFY_TEXT_DELAY);
              checkTextAvailable();
            }
          }
        }, NOTIFY_TEXT_DELAY);

        try {
          while (!isClosed()) {
            final int c = readNextByte();
            if (c == -1) {
              break;
            }
            synchronized (myBuffer) {
              myBuffer.append((char) c);
            }
            if (c == '\n') { // not by '\r' because of possible '\n'
              checkTextAvailable();
            }
          }
        }
        catch (Exception e) {
          LOG.error(e);
          e.printStackTrace();
        }

        close();
      }
      finally {
        Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
      }
    }

    private int readNextByte() {
      try {
        while (!myReader.ready()) {
          if (isProcessTerminated()) {
            return -1;
          }
          try {
            Thread.sleep(1L);
          }
          catch (InterruptedException ignore) {
          }
        }
        return myReader.read();
      }
      catch (IOException e) {
        return -1; // When process terminated Process.getInputStream()'s underlaying stream becomes closed on Linux.
      }
    }

    private void checkTextAvailable() {
      synchronized (myBuffer) {
        if (myBuffer.length() == 0) return;
        // warning! Since myBuffer is reused, do not use myBuffer.toString() to fetch the string
        // because the created string will get StringBuffer's internal char array as a buffer which is possibly too large.
        final String s = myBuffer.substring(0, myBuffer.length());
        myBuffer.setLength(0);
        textAvailable(s);
      }
    }

    private void close() {
      synchronized (this) {
        if (isClosed()) {
          return;
        }
        myIsClosed = true;
      }
      try {
        myReader.close();
      }
      catch (IOException e1) {
        // supressed
      }
      checkTextAvailable();
    }

    protected abstract void textAvailable(final String s);

    private synchronized boolean isClosed() {
      return myIsClosed;
    }

  }
}
TOP

Related Classes of org.jetbrains.plugins.clojure.repl.ClojureReplProcessHandler$ProcessWaitFor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.