From 6edfc2b57c4b560ce50d9d607c1767e799444059 Mon Sep 17 00:00:00 2001 From: Ian Martins Date: Mon, 5 Oct 2020 08:07:25 -0400 Subject: [PATCH] ob-java.el: Add support for variables, return values, tramp * lisp/ob-java.el: Add support for variables and return values. Write tempfiles to the `org-babel-temporary-directory'. Make package, class, and main method definitions optional. * testing/lisp/test-ob-java.el: Add tests. ob-java was missing features common to many other org-babel implementations, such as the ability to accept variables and return a value. This brings those features to ob-java, making the experience more consistent across org-babel languages, and expanding the ways java source blocks interact with org mode buffers. A simple java program requires boilerplate code to declare a class and main method. This boilerplate code makes java source blocks more tedious to write and more effort to read. This is more evident in an org buffer where there may be many small source blocks. This change makes that code optional. If it is not provided, the source block will be wrapped in the boilerplate code just before compilation. Most org babel implementation write temporary files to the `org-babel-temporary-directory'. That avoids polluting the current directory and provides built-in support for tramp. ob-java would write source files and binaries to the directory containing the org file by default. This may be unexpected and unwanted, and didn't work with tramp. This change writes all tempfiles to the `org-babel-temporary-directory'. Executing an ob-java source block would always try to compile and run the code in the source block, so a source block that wasn't runnable (didn't contain a main method) would error during run. This change adds a generic main method just before compilation if one isn't included in the source block so that the run will succeed. There were no tests for ob-java. This adds tests. --- lisp/ob-java.el | 422 ++++++++++++++++++++++--- testing/lisp/test-ob-java.el | 583 +++++++++++++++++++++++++++++++++++ 2 files changed, 964 insertions(+), 41 deletions(-) create mode 100644 testing/lisp/test-ob-java.el diff --git a/lisp/ob-java.el b/lisp/ob-java.el index fee695bb9..e704c5552 100644 --- a/lisp/ob-java.el +++ b/lisp/ob-java.el @@ -1,9 +1,8 @@ -;;; ob-java.el --- Babel Functions for Java -*- lexical-binding: t; -*- +;;; ob-java.el --- org-babel functions for java evaluation -*- lexical-binding: t -*- ;; Copyright (C) 2011-2020 Free Software Foundation, Inc. -;; Author: Eric Schulte -;; Maintainer: Ian Martins +;; Author: Ian Martins ;; Keywords: literate programming, reproducible research ;; Homepage: https://orgmode.org @@ -24,8 +23,7 @@ ;;; Commentary: -;; Currently this only supports the external compilation and execution -;; of java code blocks (i.e., no session support). +;; Org-Babel support for evaluating java source code. ;;; Code: (require 'ob) @@ -33,52 +31,394 @@ (defvar org-babel-tangle-lang-exts) (add-to-list 'org-babel-tangle-lang-exts '("java" . "java")) -(defcustom org-babel-java-command "java" - "Name of the java command. -May be either a command in the path, like java -or an absolute path name, like /usr/local/bin/java -parameters may be used, like java -verbose" +(defvar org-babel-default-header-args:java '() + "Default header args for java source blocks.") + +(defconst org-babel-header-args:java '((imports . :any)) + "Java-specific header arguments.") + +(defvar org-babel-java-compiler-command "javac" + "Name of the command to execute the java compiler.") + +(defvar org-babel-java-runtime-command "java" + "Name of the command to run the java runtime.") + +(defcustom org-babel-java-hline-to "null" + "Replace hlines in incoming tables with this when translating to java." :group 'org-babel - :version "24.3" + :version "25.2" + :package-version '(Org . "9.3") :type 'string) -(defcustom org-babel-java-compiler "javac" - "Name of the java compiler. -May be either a command in the path, like javac -or an absolute path name, like /usr/local/bin/javac -parameters may be used, like javac -verbose" +(defcustom org-babel-java-null-to 'hline + "Replace `null' in java tables with this before returning." :group 'org-babel - :version "24.3" - :type 'string) + :version "25.2" + :package-version '(Org . "9.3") + :type 'symbol) (defun org-babel-execute:java (body params) - (let* ((classname (or (cdr (assq :classname params)) - (error - "Can't compile a java block without a classname"))) - (packagename (file-name-directory classname)) - (src-file (concat classname ".java")) + "Execute a java source block with BODY code and PARAMS params." + (let* (;; if true, run from babel temp directory + (run-from-temp (not (assq :dir params))) + ;; class and package + (fullclassname (or (cdr (assq :classname params)) + (org-babel-java-find-classname body))) + ;; just the class name + (classname (car (last (split-string fullclassname "\\.")))) + ;; just the package name + (packagename (if (seq-contains fullclassname ?.) + (file-name-base fullclassname))) + ;; the base dir that contains the top level package dir + (basedir (file-name-as-directory (if run-from-temp + org-babel-temporary-directory + "."))) + ;; the dir to write the source file + (packagedir (if (and (not run-from-temp) packagename) + (file-name-as-directory + (concat basedir (replace-regexp-in-string "\\\." "/" packagename))) + basedir)) + ;; the filename of the source file + (src-file (concat packagedir classname ".java")) + ;; compiler flags (cmpflag (or (cdr (assq :cmpflag params)) "")) - (cmdline (or (cdr (assq :cmdline params)) "")) + ;; runtime flags + (cmdline (or (cdr (assq :cmdline params)) "")) + ;; command line args (cmdargs (or (cdr (assq :cmdargs params)) "")) - (full-body (org-babel-expand-body:generic body params))) + ;; the command to compile and run + (cmd (concat org-babel-java-compiler-command " " cmpflag " " + (org-babel-process-file-name src-file 'noquote) + " && " org-babel-java-runtime-command + " -cp " (org-babel-process-file-name basedir 'noquote) + " " cmdline " " (if run-from-temp classname fullclassname) + " " cmdargs)) + ;; header args for result processing + (result-type (cdr (assq :result-type params))) + (result-params (cdr (assq :result-params params))) + (result-file (and (eq result-type 'value) + (org-babel-temp-file "java-"))) + ;; the expanded body of the source block + (full-body (org-babel-expand-body:java body params))) + ;; created package-name directories if missing - (unless (or (not packagename) (file-exists-p packagename)) - (make-directory packagename 'parents)) + (unless (or (not packagedir) (file-exists-p packagedir)) + (make-directory packagedir 'parents)) + + ;; write the source file + (setq full-body (org-babel-java--expand-for-evaluation + full-body run-from-temp result-type result-file)) (with-temp-file src-file (insert full-body)) - (org-babel-eval - (concat org-babel-java-compiler " " cmpflag " " src-file) "") - (let ((results (org-babel-eval (concat org-babel-java-command - " " cmdline " " classname " " cmdargs) ""))) - (org-babel-reassemble-table - (org-babel-result-cond (cdr (assq :result-params params)) - (org-babel-read results t) - (let ((tmp-file (org-babel-temp-file "c-"))) - (with-temp-file tmp-file (insert results)) - (org-babel-import-elisp-from-file tmp-file))) - (org-babel-pick-name - (cdr (assq :colname-names params)) (cdr (assq :colnames params))) - (org-babel-pick-name - (cdr (assq :rowname-names params)) (cdr (assq :rownames params))))))) + + ;; compile, run, process result + (org-babel-reassemble-table + (org-babel-java-evaluate cmd result-type result-params result-file) + (org-babel-pick-name + (cdr (assoc :colname-names params)) (cdr (assoc :colnames params))) + (org-babel-pick-name + (cdr (assoc :rowname-names params)) (cdr (assoc :rownames params)))))) + +;; helper functions + +(defun org-babel-java-find-classname (body) + "Try to find fully qualified class name in BODY. +Look through BODY for the package and class. If found, put them +together into a fully qualified class name and return. Else just +return class name. If that isn't found either, default to Main." + (let ((package (if (string-match "package \\\([^ ]*\\\);" body) + (match-string 1 body))) + (class (if (string-match "public class \\\([^ \n]*\\\)" body) + (match-string 1 body)))) + (or (and package class (concat package "." class)) + (and class class) + (and package (concat package ".Main")) + "Main"))) + +(defconst org-babel-java--package-re "^[[:space:]]*package .*;$" + "Regexp for the package statement.") +(defconst org-babel-java--imports-re "^[[:space:]]*import .*;$" + "Regexp for import statements.") +(defconst org-babel-java--class-re "^public class [[:alnum:]_]+[[:space:]]*\n?[[:space:]]*{" + "Regexp for the class declaration.") +(defconst org-babel-java--main-re "public static void main(String\\(?:\\[]\\)? args\\(?:\\[]\\)?).*\n?[[:space:]]*{" + "Regexp for the main method declaration.") +(defconst org-babel-java--any-method-re "public .*(.*).*\n?[[:space:]]*{" + "Regexp for any method.") +(defconst org-babel-java--result-wrapper "\n public static String __toString(Object val) { + if (val instanceof String) { + return \"\\\"\" + val + \"\\\"\"; + } else if (val == null) { + return \"null\"; + } else if (val.getClass().isArray()) { + StringBuffer sb = new StringBuffer(); + Object[] vals = (Object[])val; + sb.append(\"[\"); + for (int ii=0; ii