Java tutorial
/* * Copyright 2014 TWO SIGMA OPEN SOURCE, LLC * * 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 com.twosigma.beaker.clojure.util; import com.google.common.base.Charsets; import com.google.common.io.Resources; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringReader; import java.io.StringWriter; import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.nio.file.FileSystems; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Semaphore; import clojure.lang.RT; import clojure.lang.Var; import com.twosigma.beaker.jvm.classloader.DynamicClassLoaderSimple; import com.twosigma.beaker.jvm.object.SimpleEvaluationObject; import com.twosigma.beaker.jvm.serialization.BeakerObjectConverter; import com.twosigma.beaker.jvm.threads.BeakerCellExecutor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ClojureEvaluator { private final static Logger logger = LoggerFactory.getLogger(ClojureEvaluator.class.getName()); protected final String shellId; protected final String sessionId; protected List<String> classPath; protected List<String> imports; protected List<String> requirements; protected boolean exit; protected boolean updateLoader; protected BeakerCellExecutor executor; protected workerThread myWorker; protected String currentClassPath = ""; protected String currentImports = ""; protected String outDir = ""; protected String currenClojureNS; protected String currentRequirements = ""; protected DynamicClassLoaderSimple loader; protected class jobDescriptor { String codeToBeExecuted; SimpleEvaluationObject outputObject; jobDescriptor(String c, SimpleEvaluationObject o) { codeToBeExecuted = c; outputObject = o; } } protected static final String beaker_clojure_ns = "beaker_clojure_shell"; protected Var clojureLoadString = null; protected final Semaphore syncObject = new Semaphore(0, true); protected final ConcurrentLinkedQueue<jobDescriptor> jobQueue = new ConcurrentLinkedQueue<jobDescriptor>(); protected String initScriptSource() throws IOException { URL url = this.getClass().getClassLoader().getResource("init_clojure_script.txt"); return Resources.toString(url, Charsets.UTF_8); } public ClojureEvaluator(String id, String sId) { shellId = id; sessionId = sId; classPath = new ArrayList<String>(); imports = new ArrayList<String>(); requirements = new ArrayList<>(); } public void init() { loader = new DynamicClassLoaderSimple(ClassLoader.getSystemClassLoader()); loader.addJars(classPath); loader.addDynamicDir(outDir); String loadFunctionPrefix = "run_str"; currenClojureNS = String.format("%1$s_%2$s", beaker_clojure_ns, shellId); try { String clojureInitScript = String.format(initScriptSource(), beaker_clojure_ns, shellId, loadFunctionPrefix, NSClientProxy.class.getName(), sessionId); clojureLoadString = RT.var(String.format("%1$s_%2$s", beaker_clojure_ns, shellId), String.format("%1$s_%2$s", loadFunctionPrefix, shellId)); clojure.lang.Compiler.load(new StringReader(clojureInitScript)); } catch (IOException e) { logger.error(e.getMessage()); } exit = false; updateLoader = false; executor = new BeakerCellExecutor("clojure"); startWorker(); } protected void startWorker() { myWorker = new workerThread(); myWorker.start(); } public String getShellId() { return shellId; } public void killAllThreads() { executor.killAllThreads(); } public void cancelExecution() { executor.cancelExecution(); } public void resetEnvironment() { executor.killAllThreads(); loader = new DynamicClassLoaderSimple(ClassLoader.getSystemClassLoader()); loader.addJars(classPath); loader.addDynamicDir(outDir); ClassLoader oldLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(loader); for (String s : imports) { if (s != null & !s.isEmpty()) try { loader.loadClass(s); clojureLoadString.invoke(String.format("(import '%s)", s)); } catch (Exception e) { logger.error(e.getMessage()); } } for (String s : requirements) { if (s != null && !s.isEmpty()) try { clojureLoadString.invoke(String.format("(require '%s)", s)); } catch (Exception e) { logger.error(e.getMessage()); } } Thread.currentThread().setContextClassLoader(oldLoader); syncObject.release(); } public void exit() { exit = true; cancelExecution(); syncObject.release(); } public void evaluate(SimpleEvaluationObject seo, String code) { // send job to thread jobQueue.add(new jobDescriptor(code, seo)); syncObject.release(); } protected class workerThread extends Thread { public workerThread() { super("clojure worker"); } /* * This thread performs all the evaluation */ public void run() { jobDescriptor j = null; while (!exit) { try { // wait for work syncObject.acquire(); // get next job descriptor j = jobQueue.poll(); if (j == null) continue; j.outputObject.started(); if (!executor.executeTask(new MyRunnable(j.codeToBeExecuted, j.outputObject))) { j.outputObject.error("... cancelled!"); } } catch (Throwable e) { logger.error(e.getMessage()); } } } protected class MyRunnable implements Runnable { protected final String theCode; protected final SimpleEvaluationObject theOutput; public MyRunnable(String code, SimpleEvaluationObject out) { theCode = code; theOutput = out; } @Override public void run() { ClassLoader oldLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(loader); theOutput.setOutputHandler(); Object result; try { Object o = clojureLoadString.invoke(theCode); try { //workaround, checking of corrupted clojure objects if (null != o) { o.hashCode(); } theOutput.finished(o); } catch (Exception e) { theOutput.error("Object: " + o.getClass() + ", value cannot be displayed due to following error: " + e.getMessage()); } } catch (Throwable e) { if (e instanceof InterruptedException || e instanceof InvocationTargetException || e instanceof ThreadDeath) { theOutput.error("... cancelled!"); } else { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); if (null != e.getCause()) { e.getCause().printStackTrace(pw); } else { e.printStackTrace(pw); } theOutput.error(sw.toString()); } } theOutput.setOutputHandler(); Thread.currentThread().setContextClassLoader(oldLoader); } } } public void setShellOptions(String cp, String in, String od, String req) throws IOException { if (od == null || od.isEmpty()) { od = FileSystems.getDefault().getPath(System.getenv("beaker_tmp_dir"), "dynclasses", sessionId) .toString(); } else { od = od.replace("$BEAKERDIR", System.getenv("beaker_tmp_dir")); } // check if we are not changing anything if (currentClassPath.equals(cp) && currentImports.equals(in) && outDir.equals(od) && currentRequirements.equals(req)) return; outDir = od; if (!currentClassPath.equals(cp)) { currentClassPath = cp; if (cp.isEmpty()) classPath = new ArrayList<String>(); else classPath = Arrays.asList(cp.split("[\\s" + File.pathSeparatorChar + "]+")); } if (!currentImports.equals(in)) { currentImports = in; if (in.isEmpty()) imports = new ArrayList<String>(); else imports = Arrays.asList(in.split("\\s+")); } if (!currentRequirements.equals(req)) { currentRequirements = req; if (req.isEmpty()) requirements = new ArrayList<String>(); else requirements = Arrays.asList(req.split("\\R")); } resetEnvironment(); } public List<String> autocomplete(String code, int caretPosition) { int i = caretPosition; while (i > 0) { char c = code.charAt(i - 1); if (!Character.isUnicodeIdentifierStart(c) || "[]{}()/\\".indexOf(c) >= 0) { break; } else { i--; } } String _code = code.substring(i, caretPosition); String apropos = "(repl_%1$s/apropos \"%2$s\")"; Object o = clojureLoadString.invoke(String.format(apropos, shellId, _code)); List<String> result = new ArrayList<String>(); for (Object s : ((Collection) o)) { String whole = s.toString(); int d = whole.indexOf('/'); if (d > 0) { String woNS = whole.substring(d + 1); String ns = whole.substring(0, d); result.add(woNS); if (!currenClojureNS.equals(ns) && !"clojure.core".equals(ns)) result.add(whole); } else { result.add(whole); } } return result; } }