I noticed that the tests didn't run with "make test." This updates the patch so that they can. I didn't add java to the list of default languages because the java tests are slow.
On Mon, Oct 5, 2020 at 9:23 AM ian martins <ia...@jhu.edu> wrote: > I wrote those examples in an org file so I could test as I wrote them, and > then exported it to make it more readable, but the export resulted in > source block headers being lost. Here is the same without export: > ---- > * Changes > > - support for functional mode (~:results value~) > - accept variables > - don't require package, class, and main definitions > - write source and result tempfiles to ~org-babel-temporary-directory~, > but respects the ~:dir~ header > - work with tramp > > * Examples > ** Example 1 > This outputs "hello." If class and main definitions aren't given the > code block will be wrapped in generic ones. > > #+begin_src java :results output silent > System.out.print("hello"); > #+end_src > > This is exactly equivalent: > > #+begin_src java :results output silent > public class Main { > public static void main(String[] args) { > System.out.print("hello"); > } > } > #+end_src > > ** Example 2 > This also outputs "hello." > > #+begin_src java :results value silent > return "hello"; > #+end_src > > ** Example 3 > This generates the class "Example" in the package "org.orgmode" in the > current directory. > > #+begin_src java :results output silent :classname org.orgmode.Example > :dir . > System.out.print("hello, org-mode"); > #+end_src > > ** Example 4 > The "Hey" class defines a static method but no main. C-c C-c on the > "Hey" source block will write "./org/orgmode/Hey.java" and compile it. > > The "Main" class calls the "Hey" class. C-c C-c on the "Main" source > block will write "./org/orgmode/Main.java" and compile and run it. > > #+begin_src java :results output silent :dir . > package org.orgmode; > > public class Hey { > public static String say() { > return "hey"; > } > } > #+end_src > > #+begin_src java :results output silent :dir . > package org.orgmode; > > public class Main { > public static void main(String[] args) { > System.out.print(Hey.say()); > } > } > #+end_src > > Instead of C-c C-c, we could have added tangle headers and written the > source files out by tangling. > > ** Example 5 > This prints the variable from the header > > #+begin_src java :var msg="hello, org-mode" :results output silent > System.out.print(msg); > #+end_src > > ** Example 6 > This prints "hello, org-mode." The table is provided to the method as a > list of lists. > > #+name: table > | message | hello, org-mode | > > #+begin_src java :var tbl=table :results output silent > System.out.print(tbl.get(0).get(1)); > #+end_src > > ** Example 7 > This example returns a list. > > Note that you're allowed to specify imports without defining the class > or main methods. > > #+begin_src java :results value :exports both > import java.util.Arrays; > > return Arrays.asList("message", "hello, org-mode"); > #+end_src > > #+RESULTS: > | message | hello, org-mode | > > On Mon, Oct 5, 2020 at 8:35 AM ian martins <ia...@jhu.edu> wrote: > >> 1 Changes >> ========= >> >> - support for functional mode (`:results value') >> - accept variables >> - don't require package, class, and main definitions >> - write source and result tempfiles to >> `org-babel-temporary-directory', but respects the `:dir' header >> - work with tramp >> >> >> 2 Examples >> ========== >> Some examples follow. See the tests for more examples. I'll write >> proper docs after review. >> >> 2.1 Example 1 >> ~~~~~~~~~~~~~ >> >> This outputs "hello." If class and main definitions aren't given the >> code block will be wrapped in generic ones. >> >> ,---- >> | System.out.print("hello"); >> `---- >> >> This is exactly equivalent: >> >> ,---- >> | public class Main { >> | public static void main(String[] args) { >> | System.out.print("hello"); >> | } >> | } >> `---- >> >> >> 2.2 Example 2 >> ~~~~~~~~~~~~~ >> >> This also outputs "hello." >> >> ,---- >> | return "hello"; >> `---- >> >> >> 2.3 Example 3 >> ~~~~~~~~~~~~~ >> >> This generates the class "Example" in the package "org.orgmode" in the >> current directory. >> >> ,---- >> | System.out.print("hello, org-mode"); >> `---- >> >> >> 2.4 Example 4 >> ~~~~~~~~~~~~~ >> >> The "Hey" class defines a static method but no main. C-c C-c on the >> "Hey" source block will write "./org/orgmode/Hey.java" and compile it. >> >> The "Main" class calls the "Hey" class. C-c C-c on the "Main" source >> block will write "./org/orgmode/Main.java" and compile and run it. >> >> ,---- >> | package org.orgmode; >> | >> | public class Hey { >> | public static String say() { >> | return "hey"; >> | } >> | } >> `---- >> >> ,---- >> | package org.orgmode; >> | >> | public class Main { >> | public static void main(String[] args) { >> | System.out.print(Hey.say()); >> | } >> | } >> `---- >> >> Instead of C-c C-c, we could have added tangle headers and written the >> source files out by tangling. >> >> >> 2.5 Example 5 >> ~~~~~~~~~~~~~ >> >> This prints the variable from the header >> >> ,---- >> | System.out.print(msg); >> `---- >> >> >> 2.6 Example 6 >> ~~~~~~~~~~~~~ >> >> This prints "hello, org-mode." The table is provided to the method as >> a list of lists. >> >> message hello, org-mode >> >> ,---- >> | System.out.print(tbl.get(0).get(1)); >> `---- >> >> >> 2.7 Example 7 >> ~~~~~~~~~~~~~ >> >> This example returns a list. >> >> Note that you're allowed to specify imports without defining the class >> or main methods. >> >> ,---- >> | import java.util.Arrays; >> | >> | return Arrays.asList("message", "hello, org-mode"); >> `---- >> >> message hello, org-mode >> >
From 6307c528d1d8aebc0200555dea6855f401132aa5 Mon Sep 17 00:00:00 2001 From: Ian Martins <ia...@jhu.edu> 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 <ia...@jhu.edu> +;; Author: Ian Martins <ia...@jhu.edu> ;; 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<vals.length; ii++) { + sb.append(__toString(vals[ii])); + if (ii<vals.length-1) + sb.append(\",\"); + } + sb.append(\"]\"); + return sb.toString(); + } else if (val instanceof List) { + StringBuffer sb = new StringBuffer(); + List vals = (List)val; + sb.append(\"[\"); + for (int ii=0; ii<vals.size(); ii++) { + sb.append(__toString(vals.get(ii))); + if (ii<vals.size()-1) + sb.append(\",\"); + } + sb.append(\"]\"); + return sb.toString(); + } else { + return String.valueOf(val); + } + } + + public static void main(String[] args) throws IOException { + BufferedWriter output = new BufferedWriter(new FileWriter(\"%s\")); + output.write(__toString(_main(args))); + output.close(); + }" + "Code to inject into a class so that we can capture the value it returns. +This implementation was inspired by ob-python, although not as +elegant. This modified the source block to write out the value +it wants to return to a temporary file so that ob-java can read +it back. The name of the temporary file to write must be +replaced in this string.") + +(defun org-babel-java--expand-for-evaluation (body suppress-package-p result-type result-file) + "Expand source block for evaluation. +In order to return a value we have to add a __toString method. +In order to prevent classes without main methods from erroring we +add a dummy main method if one is not provided. These +manipulations are done outside of `org-babel--expand-body' so +that they are hidden from tangles. + +BODY is the file content before instrumentation. + +SUPPRESS-PACKAGE-P if true, suppress the package statement. + +RESULT-TYPE is taken from params. + +RESULT-FILE is the temp file to write the result." + (with-temp-buffer + (insert body) + + ;; suppress package statement + (goto-char (point-min)) + (when (and suppress-package-p + (re-search-forward org-babel-java--package-re nil t)) + (replace-match "")) + + ;; add a dummy main method if needed + (goto-char (point-min)) + (when (not (re-search-forward org-babel-java--main-re nil t)) + (org-babel-java--move-past org-babel-java--class-re) + (insert "\n public static void main(String[] args) { + System.out.print(\"success\"); +}\n\n")) + + ;; special handling to return value + (when (eq result-type 'value) + (goto-char (point-min)) + (org-babel-java--move-past org-babel-java--class-re) + (insert (format org-babel-java--result-wrapper + (org-babel-process-file-name result-file 'noquote))) + (search-forward "public static void main(") ; rename existing main + (replace-match "public static Object _main(")) + + ;; add imports + (org-babel-java--import-maybe "java.util" "List") + (org-babel-java--import-maybe "java.util" "Arrays") + (org-babel-java--import-maybe "java.io" "BufferedWriter") + (org-babel-java--import-maybe "java.io" "FileWriter") + (org-babel-java--import-maybe "java.io" "IOException") + + (buffer-string))) + +(defun org-babel-java--move-past (re) + "Move point past the first occurrence of the given regexp RE." + (while (re-search-forward re nil t) + (goto-char (1+ (match-end 0))))) + +(defun org-babel-java--import-maybe (package class) + "Import from PACKAGE the given CLASS if it is used and not already imported." + (let (class-found import-found) + (goto-char (point-min)) + (setq class-found (re-search-forward class nil t)) + (goto-char (point-min)) + (setq import-found (re-search-forward (concat "^import .*" package ".*" class ";") nil t)) + (when (and class-found (not import-found)) + (org-babel-java--move-past org-babel-java--package-re) + (insert (concat "import " package "." class ";\n"))))) + +(defun org-babel-expand-body:java (body params) + "Expand BODY with PARAMS. +BODY could be a few statements, or could include a full class +definition specifying package, imports, and class. Because we +allow this flexibility in what the source block can contain, it +is simplest to expand the code block from the inside out." + (let* ((fullclassname (or (cdr (assq :classname params)) ; class and package + (org-babel-java-find-classname body))) + (classname (car (last (split-string fullclassname "\\.")))) ; just class name + (packagename (if (seq-contains fullclassname ?.) ; just package name + (file-name-base fullclassname))) + (var-lines (org-babel-variable-assignments:java params)) + (imports-val (assq :imports params)) + (imports (if imports-val + (split-string (org-babel-read (cdr imports-val) nil) " ") + nil))) + (with-temp-buffer + (insert body) + + ;; wrap main. If there are methods defined, but no main method + ;; and no class, wrap everything in a generic main method. + (goto-char (point-min)) + (when (and (not (re-search-forward org-babel-java--class-re nil t)) + (not (re-search-forward org-babel-java--any-method-re nil t))) + (org-babel-java--move-past org-babel-java--package-re) ; if package is defined, move past it + (org-babel-java--move-past org-babel-java--imports-re) ; if imports are defined, move past them + (insert "public static void main(String[] args) {\n") + (indent-code-rigidly (point) (point-max) 4) + (goto-char (point-max)) + (insert "\n}")) + + ;; wrap class. If there's no class, wrap everything in a + ;; generic class. + (goto-char (point-min)) + (when (not (re-search-forward org-babel-java--class-re nil t)) + (org-babel-java--move-past org-babel-java--package-re) ; if package is defined, move past it + (org-babel-java--move-past org-babel-java--imports-re) ; if imports are defined, move past them + (insert (concat "\npublic class " (file-name-base classname) " {\n")) + (indent-code-rigidly (point) (point-max) 4) + (goto-char (point-max)) + (insert "\n}")) + (goto-char (point-min)) + + ;; insert variables from source block headers + (when var-lines + (goto-char (point-min)) + (org-babel-java--move-past org-babel-java--class-re) ; move inside class + (insert (mapconcat 'identity var-lines "\n")) + (insert "\n")) + + ;; add imports from source block headers + (when imports + (goto-char (point-min)) + (org-babel-java--move-past org-babel-java--package-re) ; if package is defined, move past it + (insert (mapconcat (lambda (package) (concat "import " package ";")) imports "\n") "\n")) + + ;; add package at the top + (goto-char (point-min)) + (when (and packagename (not (re-search-forward org-babel-java--package-re nil t))) + (insert (concat "package " packagename ";\n"))) + + ;; return expanded body + (buffer-string)))) + +(defun org-babel-variable-assignments:java (params) + "Return a list of java statements assigning the block's variables. +variables are contained in PARAMS." + (mapcar + (lambda (pair) + (let* ((type-data (org-babel-java-val-to-type (cdr pair))) + (basetype (car type-data)) + (var-to-java (lambda (var) (funcall #'org-babel-java-var-to-java var basetype)))) + (format " static %s %s = %s;" + (cdr type-data) ; type + (car pair) ; name + (funcall var-to-java (cdr pair))))) ; value + (org-babel--get-vars params))) + +(defun org-babel-java-var-to-java (var basetype) + "Convert an elisp value to a java variable. +Convert an elisp value, VAR, of type BASETYPE into a string of +java source code specifying a variable of the same value." + (cond ((and (sequencep var) (not (stringp var))) + (let ((var-to-java (lambda (var) (funcall #'org-babel-java-var-to-java var basetype)))) + (concat "Arrays.asList(" (mapconcat var-to-java var ", ") ")"))) + ((eq var 'hline) org-babel-java-hline-to) + ((eq basetype 'integerp) (format "%d" var)) + ((eq basetype 'floatp) (format "%f" var)) + ((eq basetype 'stringp) (if (and (stringp var) (string-match-p ".\n+." var)) + (error "Java does not support multiline string literals") + (format "\"%s\"" var))))) + +(defun org-babel-java-val-to-type (val) + "Determine the type of VAL. +Return (BASETYPE . LISTTYPE), where BASETYPE is a symbol +representing the type of the individual items in VAL, and +LISTTYPE is a string name of the type parameter for a container +for BASETYPE items." + (let* ((basetype (org-babel-java-val-to-base-type val)) + (basetype-str (pcase basetype + (`integerp "Integer") + (`floatp "Double") + (`stringp "String") + (_ (error "Unknown type %S" basetype))))) + (cond + ((and (listp val) (listp (car val))) ; a table + (cons basetype (format "List<List<%s>>" basetype-str))) + ((or (listp val) (vectorp val)) ; a list declared in the source block header + (cons basetype (format "List<%s>" basetype-str))) + (t ; return base type + (cons basetype basetype-str))))) + +(defun org-babel-java-val-to-base-type (val) + "Determine the base type of VAL. +VAL may be +`integerp' if all base values are integers +`floatp' if all base values are either floating points or integers +`stringp' otherwise." + (cond + ((integerp val) 'integerp) + ((floatp val) 'floatp) + ((or (listp val) (vectorp val)) + (let ((type nil)) + (mapc (lambda (v) + (pcase (org-babel-java-val-to-base-type v) + (`stringp (setq type 'stringp)) + (`floatp + (when (or (not type) (eq type 'integerp)) + (setq type 'floatp))) + (`integerp + (unless type (setq type 'integerp))))) + val) + type)) + (t 'stringp))) + +(defun org-babel-java-table-or-string (results) + "Convert RESULTS into an appropriate elisp value. +If the results look like a list or vector, then convert them into an +Emacs-lisp table, otherwise return the results as a string." + (let ((res (org-babel-script-escape results))) + (if (listp res) + (mapcar (lambda (el) (if (eq 'null el) + org-babel-java-null-to + el)) + res) + res))) + +(defun org-babel-java-evaluate (cmd result-type result-params result-file) + "Evaluate using an external java process. +CMD the command to execute. + +If RESULT-TYPE equals 'output then return standard output as a +string. If RESULT-TYPE equals 'value then return the value +returned by the source block, as elisp. + +RESULT-PARAMS input params used to format the reponse. + +RESULT-FILE filename of the tempfile to store the returned value in +for 'value RESULT-TYPE. Not used for 'output RESULT-TYPE." + (let ((raw (pcase result-type + ('output (org-babel-eval cmd "")) + ('value (org-babel-eval cmd "") + (org-babel-eval-read-file result-file))))) + (org-babel-result-cond result-params raw + (org-babel-java-table-or-string raw)))) (provide 'ob-java) diff --git a/testing/lisp/test-ob-java.el b/testing/lisp/test-ob-java.el new file mode 100644 index 000000000..090c40084 --- /dev/null +++ b/testing/lisp/test-ob-java.el @@ -0,0 +1,583 @@ +;;; test-ob-java.el --- tests for ob-java.el + +;; Copyright (c) 2020 Ian Martins +;; Authors: Ian Martins + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Code: +(require 'org-test) + +(require 'ob-core) +(defvar org-babel-temporary-directory ; from ob-core + (if (boundp 'org-babel-temporary-directory) + org-babel-temporary-directory + (temporary-file-directory))) + +(org-test-for-executable "java") +(org-test-for-executable "javac") +(unless (featurep 'ob-java) + (signal 'missing-test-dependency "Support for java code blocks")) + +; simple tests + +(ert-deftest ob-java/simple () + "Hello world program that writes output." + (org-test-with-temp-text + "#+begin_src java :results output silent +System.out.print(42); +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-java/simple-with-bracket () + "Hello world program that outputs an open square bracket." + (org-test-with-temp-text + "#+begin_src java :results output silent +System.out.print(\"[42\"); +#+end_src" + (should (string= "[42" (org-babel-execute-src-block))))) + +(ert-deftest ob-java/simple-with-quote () + "Hello world program that writes quotes." + (org-test-with-temp-text + "#+begin_src java :results output silent +System.out.print(\"\\\"42\\\"\"); +#+end_src" + (should (string= "\"42\"" (org-babel-execute-src-block))))) + +(ert-deftest ob-java/simple-return-int () + "Hello world program that returns an int value." + (org-test-with-temp-text + "#+begin_src java :results silent +return 42; +#+end_src" + (should (eq 42 (org-babel-execute-src-block))))) + +(ert-deftest ob-java/simple-return-float () + "Hello world program that returns a float value." + (org-test-with-temp-text + "#+begin_src java :results silent +return 42.0; +#+end_src" + (should (equal 42.0 (org-babel-execute-src-block))))) + +(ert-deftest ob-java/simple-return-string () + "Hello world program that returns a string value." + (org-test-with-temp-text + "#+begin_src java :results silent +return \"forty two\"; +#+end_src" + (should (string= "forty two" (org-babel-execute-src-block))))) + +(ert-deftest ob-java/simple-with-main () + "Hello world program that defines a main function." + (org-test-with-temp-text + "#+begin_src java :results output silent +public static void main(String[] args) { + System.out.print(42); +} +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-java/simple-with-two-methods () + "Hello world program with two methods and no class." + (org-test-with-temp-text + "#+begin_src java :results output silent +public static void main(String[] args) { + System.out.print(foo()); +} +public static int foo() { + return 42; +} +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-java/simple-with-no-main () + "Hello world program with no main method. Babel adds a dummy one so it can run without error." + (org-test-with-temp-text + "#+begin_src java :results output silent +public static int foo() { + return 42; +} +#+end_src" + (should (string= "success" (org-babel-execute-src-block))))) + +(ert-deftest ob-java/simple-with-main-args-array () + "Hello world program that defines a main function with the square brackets after `args'." + (org-test-with-temp-text + "#+begin_src java :results output silent +public static void main(String args[]) { + System.out.print(42); +} +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-java/simple-with-class () + "Hello world program that defines a class." + (org-test-with-temp-text + "#+begin_src java :results output silent +public class Simple { + public static void main(String[] args) { + System.out.print(42); + } +} +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-java/simple-with-class-and-package () + "Hello world program that defines a class and package." + (org-test-with-temp-text + "#+begin_src java :results output silent +package pkg; +public class Simple { + public static void main(String[] args) { + System.out.print(42); + } +} +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-java/simple-with-class-attr () + "Hello world program with class header attribute." + (org-test-with-temp-text + "#+begin_src java :results output silent :classname Simple +public static void main(String[] args) { + System.out.print(42); +} +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-java/simple-with-class-attr-with-package () + "Hello world program with class attr with package." + (org-test-with-temp-text + "#+begin_src java :results output silent :classname pkg.Simple +public static void main(String[] args) { + System.out.print(42); +} +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + + +(ert-deftest ob-java/one-arg () + "Command line arg." + (org-test-with-temp-text + "#+begin_src java :results output silent :cmdargs \"fortytwo\" +System.out.print(args[0]); +#+end_src" + (should (string= "fortytwo" (org-babel-execute-src-block))))) + +(ert-deftest ob-java/args-quoted-string () + "Two command line args, first contains a space." + (org-test-with-temp-text + "#+begin_src java :results output silent :cmdargs \"\\\"forty two\\\" 42\" +System.out.println(args[0]); +System.out.println(args[1]); +#+end_src" + (should (string= "forty two\n42\n" (org-babel-execute-src-block))))) + +;; var tests + +(ert-deftest ob-java/integer-var () + "Read and write an integer variable." + (org-test-with-temp-text + "#+begin_src java :var a=42 :results output silent +System.out.print(a); +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-java/var-with-main () + "Read and write an integer variable, with main function provided." + (org-test-with-temp-text + "#+begin_src java :var a=42 :results output silent +public static void main(String[] args) { + System.out.print(a); +} +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-java/var-with-class () + "Read and write an integer variable, with class provided." + (org-test-with-temp-text + "#+begin_src java :var a=42 :results output silent +public class Main { + public static void main(String[] args) { + System.out.print(a); + } +} +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-java/var-with-class-and-package () + "Read and write an integer variable, with class and package provided." + (org-test-with-temp-text + "#+begin_src java :var a=42 :results output silent +package pkg; +public class Main { + public static void main(String[] args) { + System.out.print(a); + } +} +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-java/var-with-class-and-hanging-curlies () + "Read and write an integer variable, with class with hanging curlies." + (org-test-with-temp-text + "#+begin_src java :var a=42 :results output silent +public class Main +{ + public static void main(String[] args) + { + System.out.print(a); + } +} +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-java/two-vars () + "Read two integer variables, combine and write them." + (org-test-with-temp-text + "#+begin_src java :var a=21 b=2 :results output silent +System.out.print(a*b); +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-java/string-var () + "Read and write a string variable." + (org-test-with-temp-text + "#+begin_src java :var a=\"forty two\" :results output silent +System.out.print(String.format(\"%s, len=%d\", a, a.length())); +#+end_src" + (should (string= "forty two, len=9" (org-babel-execute-src-block))))) + +(ert-deftest ob-java/multiline-string-var () + "Java doesn't support multiline string literals, so this errors." + (org-test-with-temp-text + "#+begin_src java :var a=\"forty\ntwo\" :results output silent +System.out.print(String.format(\"%s, len=%d\", a, a.length())); +#+end_src" + (should-error (org-babel-execute-src-block))) + :type 'error) + +;; return list + +(ert-deftest ob-java/return-vector-using-list () + "Return a vector using a list." + (org-test-with-temp-text + "#+begin_src java :results vector silent +import java.util.List; +import java.util.Arrays; +List<List<Integer>> a = Arrays.asList(Arrays.asList(4), + Arrays.asList(2)); +return a; +#+end_src" + (should (equal '((4) (2)) + (org-babel-execute-src-block))))) + +(ert-deftest ob-java/return-vector-using-array () + "Return a vector using an array." + (org-test-with-temp-text + "#+begin_src java :results vector silent +Integer[][] a = {{4}, {2}}; +return a; +#+end_src" + (should (equal '((4) (2)) + (org-babel-execute-src-block))))) + +(ert-deftest ob-java/read-return-list () + "Read and return a list." + (org-test-with-temp-text + "#+begin_src java :var a=java_list :results silent +import java.util.List; +import java.util.Arrays; +List<String> b = Arrays.asList(a.get(0).get(0), + a.get(1).get(0)); +return b; +#+end_src + +#+name: java_list +- forty +- two" + (should (equal '("forty" "two") + (org-babel-execute-src-block))))) + +(ert-deftest ob-java/read-list-return-array () + "Read a list and return an array." + (org-test-with-temp-text + "#+begin_src java :var a=java_list :results silent +String[] b = {a.get(0).get(0), a.get(1).get(0)}; +return b; +#+end_src + +#+name: java_list +- forty +- two" + (should (equal '("forty" "two") + (org-babel-execute-src-block))))) + +(ert-deftest ob-java/read-return-list-with-package () + "Return a vector." + (org-test-with-temp-text + "#+begin_src java :var a=java_list :results silent +package pkg; +import java.util.List; +import java.util.Arrays; +List<String> b = Arrays.asList(a.get(0).get(0), + a.get(1).get(0)); +return b; +#+end_src + +#+name: java_list +- forty +- two" + (should (equal '("forty" "two") + (org-babel-execute-src-block))))) + +(ert-deftest ob-java/output-list-with-spaces () + "Return a vector." + (org-test-with-temp-text + "#+begin_src java :results output list raw silent +System.out.println(\"forty two\"); +System.out.println(\"forty two\"); +#+end_src" + (should (equal "forty two\nforty two\n" + (org-babel-execute-src-block))))) + +;; list vars + +(ert-deftest ob-java/list-var () + "Read and write a list variable." + (org-test-with-temp-text + "#+begin_src java :var a='(\"forty\" \"two\") :results silent +import java.util.List; +List<String> b = a; +return b; +#+end_src" + (should (equal '("forty" "two") + (org-babel-execute-src-block))))) + +(ert-deftest ob-java/vector-var () + "Read and write a vector variable." + (org-test-with-temp-text + "#+begin_src java :var a='[\"forty\" \"two\"] :results silent +import java.util.List; +List<String> b = a; +return b; +#+end_src" + (should (equal '("forty" "two") + (org-babel-execute-src-block))))) + +(ert-deftest ob-java/matrix-var () + "Read and write matrix variable." + (org-test-with-temp-text + "#+begin_src java :var a=java_matrix :results silent +import java.util.List; +import java.util.Arrays; +List<List<Integer>> b = Arrays.asList(Arrays.asList(a.get(0).get(0), a.get(1).get(0)), + Arrays.asList(a.get(0).get(1), a.get(1).get(1))); +return b; // transpose +#+end_src + +#+name: java_matrix +| 2 | 1 | +| 4 | 2 |" + (should (equal '((2 4) (1 2)) + (org-babel-execute-src-block))))) + +(ert-deftest ob-java/matrix-var-with-header () + "Read matrix variable and write it with header." + (org-test-with-temp-text + "#+begin_src java :var a=java_matrix :results value table silent +import java.util.List; +import java.util.Arrays; +List<List> b = Arrays.asList(Arrays.asList(\"col1\", \"col2\"), + null, + Arrays.asList(a.get(0).get(0), a.get(1).get(0)), + Arrays.asList(a.get(0).get(1), a.get(1).get(1))); +return b; // transpose +#+end_src + +#+name: java_matrix +| 2 | 1 | +| 4 | 2 |" + (should (equal '(("col1" "col2") hline (2 4) (1 2)) + (org-babel-execute-src-block))))) + +;; output table + +(ert-deftest ob-java/output-table-with-header () + "Write a table that includes a header." + (org-test-with-temp-text + "#+begin_src java :var a=java_matrix :results output raw table silent +System.out.println(\"|col1|col2|\"); +System.out.println(\"|-\"); +for (int ii=0; ii<a.size(); ii++) { + for (int jj=0; jj<a.get(0).size(); jj++) { + System.out.print(\"|\" + a.get(ii).get(jj)); + } + System.out.println(\"\"); + } +#+end_src + +#+name: java_matrix +| 2 | 1 | +| 4 | 2 |" + (should (equal "|col1|col2|\n|-\n|2|1\n|4|2\n" + (org-babel-execute-src-block))))) + +(ert-deftest ob-java/inhomogeneous_table () + "Read and write an inhomogeneous table." + (org-test-with-temp-text + "#+begin_src java :var a=java_table :results silent +import java.util.List; +import java.util.Arrays; +List<List> b = Arrays.asList(Arrays.asList(a.get(0).get(0), + Integer.parseInt(a.get(0).get(1))*2), + Arrays.asList(a.get(1).get(0), + Integer.parseInt(a.get(1).get(1))*2)); +return b; +#+end_src + +#+name: java_table + | string | number | + |--------+--------| + | forty | 2 | + | two | 1 |" + (should (equal + '(("forty" 4) ("two" 2)) + (org-babel-execute-src-block))))) + +;; imports + +(ert-deftest ob-java/import_library () + "Import a standard java library." + (org-test-with-temp-text + "#+begin_src java :results output silent :imports java.util.Base64 +byte[] encoded = Base64.getEncoder().encode(\"42\".getBytes()); +String decoded = new String(Base64.getDecoder().decode(encoded)); +System.out.print(String.format(\"encoded=%s, decoded=%s\", new String(encoded), decoded)); +#+end_src" + (should (string= + "encoded=NDI=, decoded=42" + (org-babel-execute-src-block))))) + +(ert-deftest ob-java/import_library_inline () + "Import a standard java library." + (org-test-with-temp-text + "#+begin_src java :results output silent +import java.util.Base64; +byte[] encoded = Base64.getEncoder().encode(\"42\".getBytes()); +String decoded = new String(Base64.getDecoder().decode(encoded)); +System.out.print(String.format(\"encoded=%s, decoded=%s\", new String(encoded), decoded)); +#+end_src" + (should (string= + "encoded=NDI=, decoded=42" + (org-babel-execute-src-block))))) + +;; tangle + +(ert-deftest ob-java/tangle () + "Tangle a source block." + (org-test-with-temp-text-in-file + "#+begin_src java :tangle \"Tangle.java\" :results value :classname Tangle +return \"tangled\"; +#+end_src" + (should + (string= + "public class Tangle { + public static void main(String[] args) { + return \"tangled\"; + } +} +" + (unwind-protect + (progn (org-babel-tangle) + (with-temp-buffer + (insert-file-contents "Tangle.java") + (untabify (point-min) (point-max)) + (buffer-string))) + (delete-file "Tangle.java")))))) + +(ert-deftest ob-java/tangle-with-package () + "Tangle a source block." + (org-test-with-temp-text-in-file + "#+begin_src java :tangle \"tangle/Tangle.java\" :results value :classname tangle.Tangle +return \"tangled\"; +#+end_src" + (should + (string= + "package tangle; + +public class Tangle { + public static void main(String[] args) { + return \"tangled\"; + } +} +" + (unwind-protect + (progn + (make-directory "tangle") + (org-babel-tangle) + (with-temp-buffer + (insert-file-contents "tangle/Tangle.java") + (untabify (point-min) (point-max)) + (buffer-string))) + (delete-file "tangle/Tangle.java") + (delete-directory "tangle")))))) + + +;; specify output dir + +(ert-deftest ob-java/simple-dir () + "Hello world program that writes output." + (org-test-with-temp-text + (format "#+begin_src java :results output silent :dir %s +System.out.print(42); +#+end_src" org-babel-temporary-directory) + (should (string= + "42" + (unwind-protect + (org-babel-execute-src-block) + (delete-file (concat (file-name-as-directory org-babel-temporary-directory) + "Main.java")) + (delete-file (concat (file-name-as-directory org-babel-temporary-directory) + "Main.class"))))))) + +(ert-deftest ob-java/simple-dir-with-package () + "Hello world program that writes output." + (org-test-with-temp-text + (format "#+begin_src java :results output silent :dir %s +package pkg; + +public class Main { + public static void main(String[] args) { + System.out.print(42); + } +} +#+end_src" org-babel-temporary-directory) + (should (string= + "42" + (unwind-protect + (org-babel-execute-src-block) + (delete-file (concat (file-name-as-directory org-babel-temporary-directory) + "pkg/Main.java")) + (delete-file (concat (file-name-as-directory org-babel-temporary-directory) + "pkg/Main.class")) + (delete-directory (concat (file-name-as-directory org-babel-temporary-directory) + "pkg"))))))) + + +;;; test-ob-java.el ends here -- 2.25.1