@Override public OutputStream pipe(String command, PrintStream out, PrintStream err) { CommandInvocation invocation = javaArgs.parse(command, JAVA_OPTIONS.separatorCode(' ')); Matcher identifierMatcher = lambdaIdentifierRegex.matcher(invocation.getUnprocessedInput()); if (identifierMatcher.matches()) { String lambdaArgIdentifier = identifierMatcher.group("id"); String lambdaBody = identifierMatcher.group("body").trim(); if (lambdaBody.endsWith(";")) { lambdaBody = lambdaBody.substring(0, lambdaBody.length() - 1); } String lineConsumerCode = "return (java.util.function.Function<String, ?>)" + "((" + lambdaArgIdentifier.trim() + ") -> " + lambdaBody + ")"; JavaCode functionCode = new JavaCode(code); functionCode.addLine(lineConsumerCode); Optional<Callable<?>> callable = javacService.compileJavaSnippet(functionCode, classLoaderContext, err); if (callable.isPresent()) { try { Function<String, ?> callback = (Function<String, ?>) callable.get().call(); return new LineOutputStream( line -> { @Nullable Object output = callback.apply(line); if (output != null) { out.println(output); } }, out); } catch (Exception e) { err.println(e); } } } throw new RuntimeException( "When used in a pipeline, the Java snippet must be in the form of a " + "lambda of type Function<String, ?> that takes one text line of the input at a time.\n" + "Example: ... | java line -> line.contains(\"text\") ? line : null"); }
@Override public void execute(String line, PrintStream out, PrintStream err) { Objects.requireNonNull(classLoaderContext, "Did not set ClassLoaderCapabilities"); CommandInvocation invocation = javaArgs.parse(line, JAVA_OPTIONS.separatorCode(' ')); if (invocation.hasOption(RESET_CODE_ARG)) { code.resetCode(); } if (invocation.hasOption(RESET_ALL_ARG)) { code.resetAll(); } String codeToRun = invocation.getUnprocessedInput(); @Nullable String className; if (invocation.hasOption(CLASS_ARG) && (className = extractClassName(codeToRun, err)) != null) { Optional<Class<Object>> javaClass = javacService.compileJavaClass(classLoaderContext, className, codeToRun, err); javaClass.ifPresent(out::println); } else { breakupJavaLines(codeToRun); boolean show = invocation.hasOption(SHOW_ARG); if (show) { out.println(javacService.getJavaSnippetClass(code)); if (!codeToRun.isEmpty()) { // add new lines between the code and its result out.println(); } } // run the current code if some input was given, or in any case when no show option if (!codeToRun.isEmpty() || !show) { runJava(out, err); } } }
public class JavaCommand implements Command, StreamingCommand { private static final String JAVA_ID_REGEX = "([a-zA-Z_$][a-zA-Z\\d_$]*\\.)*[a-zA-Z_$][a-zA-Z\\d_$]*"; static final String RESET_CODE_ARG = "-r"; static final String RESET_CODE_LONG_ARG = "--reset"; static final String RESET_ALL_ARG = "-t"; static final String RESET_ALL_LONG_ARG = "--reset-all"; static final String SHOW_ARG = "-s"; static final String SHOW_LONG_ARG = "--show"; static final String CLASS_ARG = "-c"; static final String CLASS_LONG_ARG = "--class-define"; private static final CommandHelper.CommandBreakupOptions JAVA_OPTIONS = CommandHelper.CommandBreakupOptions.create().includeQuotes(true); private Bundle bundle; private final JavacService javacService = JavacService.createDefault(); private final JavaCode code; private final Pattern lambdaIdentifierRegex = Pattern.compile( "\\s*(\\()?\\s*(?<id>" + JAVA_ID_REGEX + ")\\s*(\\))?\\s*->(?<body>.+)", Pattern.DOTALL); private ClassLoaderCapabilities classLoaderContext; private final ArgsSpec javaArgs = ArgsSpec.builder() .accepts(RESET_CODE_ARG, RESET_CODE_LONG_ARG) .end() .accepts(RESET_ALL_ARG, RESET_ALL_LONG_ARG) .end() .accepts(SHOW_ARG, SHOW_LONG_ARG) .end() .accepts(CLASS_ARG, CLASS_LONG_ARG) .end() .build(); @SuppressWarnings("ConstantConditions") private static final Callable<?> ERROR = () -> null; public JavaCommand() { this.code = new JavaCode(() -> Optional.ofNullable(getClassLoaderContext())); } public ClassLoaderContext getClassLoaderContext() { return javacService.getAugmentedClassLoaderContext(classLoaderContext); } public void setClassLoaderContext(ClassLoaderCapabilities classLoaderContext) { this.classLoaderContext = classLoaderContext; } public void activate(BundleContext context) { this.bundle = context.getBundle(); } public void deactivate(BundleContext context) { this.bundle = null; } @Override public String getName() { return "java"; } @Override public String getUsage() { return "java [-r | -s | -t | <java snippet>] | " + "[-c <class definition>]"; } @Override public String getShortDescription() { return "Run Java code statements.\n" + "\nAll statements entered previously, except return statements, are executed each time " + "a new statement is entered.\n" + "Previous statements can be forgotten with the -r (reset) option.\n" + "Permanent variables can be created by adding them to the 'binding' Map, whose contents\n" + "get expanded into local variables on execution." + "\n\n" + "The java command accepts the following flags:\n" + " \n" + " * " + RESET_CODE_ARG + ", " + RESET_CODE_LONG_ARG + ":\n" + " reset the current code statement buffer.\n" + " * " + RESET_ALL_ARG + ", " + RESET_ALL_LONG_ARG + ":\n" + " reset the current code statement buffer and imports.\n" + " * " + SHOW_ARG + ", " + SHOW_LONG_ARG + ":\n" + " show the current statement buffer.\n" + " * " + CLASS_ARG + ", " + CLASS_LONG_ARG + ":\n" + " define a Java class.\n" + "\n" + "Simple example:\n\n" + ">> java return 2 + 2\n" + "< 4\n\n" + "Binding example:\n\n" + ">> java binding.put(\"var\", 10);\n" + ">> java -r return var + 10; // var is still present even after using the -r option\n" + "< 20\n\n" + "Multi-line example to define a separate class:\n\n" + ">> :{\n" + "java -c class Person {\n" + " String name;\n" + " int age;\n" + " Person(String name, int age) {\n" + " this.name = name;\n" + " this.age = age;\n" + " }\n" + " public String toString() { return \"Person(\" + name + \",\" + age + \")\"; }" + "}\n" + ":}\n" + "< class Person\n" + ">> java return new Person(\"Mary\", 24);\n" + "< Person(Mary, 24)\n\n" + "When run through pipes, the Java snippet should be a Function<String, ?> that takes \n" + "each input line as an argument, returning something to be printed (or null).\n" + "Example:\n" + ">> some_command | java line -> line.contains(\"text\") ? line : null"; } JavaCode getAutocompleContext() { return code; } @Override public OutputStream pipe(String command, PrintStream out, PrintStream err) { CommandInvocation invocation = javaArgs.parse(command, JAVA_OPTIONS.separatorCode(' ')); Matcher identifierMatcher = lambdaIdentifierRegex.matcher(invocation.getUnprocessedInput()); if (identifierMatcher.matches()) { String lambdaArgIdentifier = identifierMatcher.group("id"); String lambdaBody = identifierMatcher.group("body").trim(); if (lambdaBody.endsWith(";")) { lambdaBody = lambdaBody.substring(0, lambdaBody.length() - 1); } String lineConsumerCode = "return (java.util.function.Function<String, ?>)" + "((" + lambdaArgIdentifier.trim() + ") -> " + lambdaBody + ")"; JavaCode functionCode = new JavaCode(code); functionCode.addLine(lineConsumerCode); Optional<Callable<?>> callable = javacService.compileJavaSnippet(functionCode, classLoaderContext, err); if (callable.isPresent()) { try { Function<String, ?> callback = (Function<String, ?>) callable.get().call(); return new LineOutputStream( line -> { @Nullable Object output = callback.apply(line); if (output != null) { out.println(output); } }, out); } catch (Exception e) { err.println(e); } } } throw new RuntimeException( "When used in a pipeline, the Java snippet must be in the form of a " + "lambda of type Function<String, ?> that takes one text line of the input at a time.\n" + "Example: ... | java line -> line.contains(\"text\") ? line : null"); } @Override public void execute(String line, PrintStream out, PrintStream err) { Objects.requireNonNull(classLoaderContext, "Did not set ClassLoaderCapabilities"); CommandInvocation invocation = javaArgs.parse(line, JAVA_OPTIONS.separatorCode(' ')); if (invocation.hasOption(RESET_CODE_ARG)) { code.resetCode(); } if (invocation.hasOption(RESET_ALL_ARG)) { code.resetAll(); } String codeToRun = invocation.getUnprocessedInput(); @Nullable String className; if (invocation.hasOption(CLASS_ARG) && (className = extractClassName(codeToRun, err)) != null) { Optional<Class<Object>> javaClass = javacService.compileJavaClass(classLoaderContext, className, codeToRun, err); javaClass.ifPresent(out::println); } else { breakupJavaLines(codeToRun); boolean show = invocation.hasOption(SHOW_ARG); if (show) { out.println(javacService.getJavaSnippetClass(code)); if (!codeToRun.isEmpty()) { // add new lines between the code and its result out.println(); } } // run the current code if some input was given, or in any case when no show option if (!codeToRun.isEmpty() || !show) { runJava(out, err); } } } @Nullable private static String extractClassName(String code, PrintStream err) { InputStream inputStream = new ByteArrayInputStream(code.getBytes(StandardCharsets.UTF_8)); try { CompilationUnit compilationUnit = JavaParser.parse(inputStream); List<TypeDeclaration> types = compilationUnit.getTypes(); if (types.size() == 1) { String simpleType = types.get(0).getName(); return Optional.ofNullable(compilationUnit.getPackage()) .map(PackageDeclaration::getPackageName) .map(it -> it + "." + simpleType) .orElse(simpleType); } else if (types.size() == 0) { err.println("No class definition found"); } else { err.println("Too many class definitions found. Only one class can be defined at a time."); } } catch (ParseException e) { // ignore error, let the compiler provide an error message return "Err"; } return null; } private void runJava(PrintStream out, PrintStream err) { Binding.out = out; Binding.err = err; Binding.ctx = bundle.getBundleContext(); try { Callable<?> callable = javacService.compileJavaSnippet(code, classLoaderContext, err).orElse(ERROR); if (callable != ERROR) { Object result = callable.call(); if (result != null) { out.println(result); } code.commit(); } else { code.abort(); } } catch (Throwable e) { code.abort(); e.printStackTrace(err); } } private void breakupJavaLines(String input) { CommandHelper.breakupArguments( input, (javaLine) -> { javaLine = javaLine.trim(); if (!javaLine.isEmpty() && !javaLine.startsWith("//")) { code.addLine(javaLine); } return true; }, JAVA_OPTIONS.separatorCode(';')); } }