From 6307c528d1d8aebc0200555dea6855f401132aa5 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. --- 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