aboutsummaryrefslogtreecommitdiffstats
path: root/lisp/org/ob-java.el
diff options
context:
space:
mode:
authorKyle Meyer2021-09-29 18:48:59 -0400
committerKyle Meyer2021-09-29 23:21:21 -0400
commitbf9ec3d91a79414deac039f7bf83352a9b0a9a85 (patch)
tree5e636992801ca408a26f7b7532c666d24c80020e /lisp/org/ob-java.el
parentdc94ca7b2b878c9a88be72fea118bf6557259ffd (diff)
downloademacs-bf9ec3d91a79414deac039f7bf83352a9b0a9a85.tar.gz
emacs-bf9ec3d91a79414deac039f7bf83352a9b0a9a85.zip
Update to Org 9.5
Diffstat (limited to 'lisp/org/ob-java.el')
-rw-r--r--lisp/org/ob-java.el480
1 files changed, 442 insertions, 38 deletions
diff --git a/lisp/org/ob-java.el b/lisp/org/ob-java.el
index b1d517e94aa..60ef33bc6e4 100644
--- a/lisp/org/ob-java.el
+++ b/lisp/org/ob-java.el
@@ -1,8 +1,10 @@
1;;; ob-java.el --- Babel Functions for Java -*- lexical-binding: t; -*- 1;;; ob-java.el --- org-babel functions for java evaluation -*- lexical-binding: t -*-
2 2
3;; Copyright (C) 2011-2021 Free Software Foundation, Inc. 3;; Copyright (C) 2011-2021 Free Software Foundation, Inc.
4 4
5;; Author: Eric Schulte 5;; Authors: Eric Schulte
6;; Dan Davison
7;; Maintainer: Ian Martins <ianxm@jhu.edu>
6;; Keywords: literate programming, reproducible research 8;; Keywords: literate programming, reproducible research
7;; Homepage: https://orgmode.org 9;; Homepage: https://orgmode.org
8 10
@@ -23,8 +25,7 @@
23 25
24;;; Commentary: 26;;; Commentary:
25 27
26;; Currently this only supports the external compilation and execution 28;; Org-Babel support for evaluating java source code.
27;; of java code blocks (i.e., no session support).
28 29
29;;; Code: 30;;; Code:
30(require 'ob) 31(require 'ob)
@@ -32,52 +33,455 @@
32(defvar org-babel-tangle-lang-exts) 33(defvar org-babel-tangle-lang-exts)
33(add-to-list 'org-babel-tangle-lang-exts '("java" . "java")) 34(add-to-list 'org-babel-tangle-lang-exts '("java" . "java"))
34 35
36(defvar org-babel-temporary-directory) ; from ob-core
37
38(defvar org-babel-default-header-args:java '((:results . "output")
39 (:dir . "."))
40 "Default header args for java source blocks.
41The docs say functional mode should be the default [1], but
42ob-java didn't originally support functional mode, so we keep
43scripting mode as the default for now to maintain previous
44behavior.
45
46Most languages write tempfiles to babel's temporary directory,
47but ob-java originally had to write them to the current
48directory, so we keep that as the default behavior.
49
50[1] https://orgmode.org/manual/Results-of-Evaluation.html")
51
52(defconst org-babel-header-args:java '((imports . :any))
53 "Java-specific header arguments.")
54
35(defcustom org-babel-java-command "java" 55(defcustom org-babel-java-command "java"
36 "Name of the java command. 56 "Name of the java command.
37May be either a command in the path, like java 57May be either a command in the path, like java or an absolute
38or an absolute path name, like /usr/local/bin/java 58path name, like /usr/local/bin/java. Parameters may be used,
39parameters may be used, like java -verbose" 59like java -verbose."
40 :group 'org-babel 60 :group 'org-babel
41 :version "24.3" 61 :package-version '(Org . "9.5")
42 :type 'string) 62 :type 'string)
43 63
44(defcustom org-babel-java-compiler "javac" 64(defcustom org-babel-java-compiler "javac"
45 "Name of the java compiler. 65 "Name of the java compiler.
46May be either a command in the path, like javac 66May be either a command in the path, like javac or an absolute
47or an absolute path name, like /usr/local/bin/javac 67path name, like /usr/local/bin/javac. Parameters may be used,
48parameters may be used, like javac -verbose" 68like javac -verbose."
69 :group 'org-babel
70 :package-version '(Org . "9.5")
71 :type 'string)
72
73(defcustom org-babel-java-hline-to "null"
74 "Replace hlines in incoming tables with this when translating to java."
49 :group 'org-babel 75 :group 'org-babel
50 :version "24.3" 76 :package-version '(Org . "9.5")
51 :type 'string) 77 :type 'string)
52 78
79(defcustom org-babel-java-null-to 'hline
80 "Replace `null' in java tables with this before returning."
81 :group 'org-babel
82 :package-version '(Org . "9.5")
83 :type 'symbol)
84
85(defconst org-babel-java--package-re (rx line-start (0+ space) "package"
86 (1+ space) (group (1+ (in alnum ?_ ?.))) ; capture the package name
87 (0+ space) ?\; line-end)
88 "Regexp for the package statement.")
89(defconst org-babel-java--imports-re (rx line-start (0+ space) "import"
90 (opt (1+ space) "static")
91 (1+ space) (group (1+ (in alnum ?_ ?. ?*))) ; capture the fully qualified class name
92 (0+ space) ?\; line-end)
93 "Regexp for import statements.")
94(defconst org-babel-java--class-re (rx line-start (0+ space) (opt (seq "public" (1+ space)))
95 "class" (1+ space)
96 (group (1+ (in alnum ?_))) ; capture the class name
97 (0+ space) ?{)
98 "Regexp for the class declaration.")
99(defconst org-babel-java--main-re (rx line-start (0+ space) "public"
100 (1+ space) "static"
101 (1+ space) "void"
102 (1+ space) "main"
103 (0+ space) ?\(
104 (0+ space) "String"
105 (0+ space) (1+ (in alnum ?_ ?\[ ?\] space)) ; "[] args" or "args[]"
106 (0+ space) ?\)
107 (0+ space) (opt "throws" (1+ (in alnum ?_ ?, ?. space)))
108 ?{)
109 "Regexp for the main method declaration.")
110(defconst org-babel-java--any-method-re (rx line-start
111 (0+ space) (opt (seq (1+ alnum) (1+ space))) ; visibility
112 (opt (seq "static" (1+ space))) ; binding
113 (1+ (in alnum ?_ ?\[ ?\])) ; return type
114 (1+ space) (1+ (in alnum ?_)) ; method name
115 (0+ space) ?\(
116 (0+ space) (0+ (in alnum ?_ ?\[ ?\] ?, space)) ; params
117 (0+ space) ?\)
118 (0+ space) (opt "throws" (1+ (in alnum ?_ ?, ?. space)))
119 ?{)
120 "Regexp for any method.")
121(defconst org-babel-java--result-wrapper "\n public static String __toString(Object val) {
122 if (val instanceof String) {
123 return \"\\\"\" + val + \"\\\"\";
124 } else if (val == null) {
125 return \"null\";
126 } else if (val.getClass().isArray()) {
127 StringBuffer sb = new StringBuffer();
128 Object[] vals = (Object[])val;
129 sb.append(\"[\");
130 for (int ii=0; ii<vals.length; ii++) {
131 sb.append(__toString(vals[ii]));
132 if (ii<vals.length-1)
133 sb.append(\",\");
134 }
135 sb.append(\"]\");
136 return sb.toString();
137 } else if (val instanceof List) {
138 StringBuffer sb = new StringBuffer();
139 List vals = (List)val;
140 sb.append(\"[\");
141 for (int ii=0; ii<vals.size(); ii++) {
142 sb.append(__toString(vals.get(ii)));
143 if (ii<vals.size()-1)
144 sb.append(\",\");
145 }
146 sb.append(\"]\");
147 return sb.toString();
148 } else {
149 return String.valueOf(val);
150 }
151 }
152
153 public static void main(String[] args) throws IOException {
154 BufferedWriter output = new BufferedWriter(new FileWriter(\"%s\"));
155 output.write(__toString(_main(args)));
156 output.close();
157 }"
158 "Code to inject into a class so that we can capture the value it returns.
159This implementation was inspired by ob-python, although not as
160elegant. This modified the source block to write out the value
161it wants to return to a temporary file so that ob-java can read
162it back. The name of the temporary file to write must be
163replaced in this string.")
164
53(defun org-babel-execute:java (body params) 165(defun org-babel-execute:java (body params)
54 (let* ((classname (or (cdr (assq :classname params)) 166 "Execute a java source block with BODY code and PARAMS params."
55 (error 167 (let* (;; allow header overrides
56 "Can't compile a java block without a classname"))) 168 (org-babel-java-compiler
57 (packagename (file-name-directory classname)) 169 (or (cdr (assq :javac params))
58 (src-file (concat classname ".java")) 170 org-babel-java-compiler))
59 (cmpflag (or (cdr (assq :cmpflag params)) "")) 171 (org-babel-java-command
60 (cmdline (or (cdr (assq :cmdline params)) "")) 172 (or (cdr (assq :java params))
61 (cmdargs (or (cdr (assq :cmdargs params)) "")) 173 org-babel-java-command))
62 (full-body (org-babel-expand-body:generic body params))) 174 ;; if true, run from babel temp directory
63 (with-temp-file src-file (insert full-body)) 175 (run-from-temp (not (cdr (assq :dir params))))
64 (org-babel-eval 176 ;; class and package
65 (concat org-babel-java-compiler " " cmpflag " " src-file) "") 177 (fullclassname (or (cdr (assq :classname params))
178 (org-babel-java-find-classname body)))
179 ;; just the class name
180 (classname (car (last (split-string fullclassname "\\."))))
181 ;; just the package name
182 (packagename (if (string-match-p "\\." fullclassname)
183 (file-name-base fullclassname)))
184 ;; the base dir that contains the top level package dir
185 (basedir (file-name-as-directory (if run-from-temp
186 (if (file-remote-p default-directory)
187 (concat
188 (file-remote-p default-directory)
189 org-babel-remote-temporary-directory)
190 org-babel-temporary-directory)
191 default-directory)))
192 ;; the dir to write the source file
193 (packagedir (if (and (not run-from-temp) packagename)
194 (file-name-as-directory
195 (concat basedir (replace-regexp-in-string "\\\." "/" packagename)))
196 basedir))
197 ;; the filename of the source file
198 (src-file (concat packagedir classname ".java"))
199 ;; compiler flags
200 (cmpflag (or (cdr (assq :cmpflag params)) ""))
201 ;; runtime flags
202 (cmdline (or (cdr (assq :cmdline params)) ""))
203 ;; command line args
204 (cmdargs (or (cdr (assq :cmdargs params)) ""))
205 ;; the command to compile and run
206 (cmd (concat org-babel-java-compiler " " cmpflag " "
207 (org-babel-process-file-name src-file 'noquote)
208 " && " org-babel-java-command
209 " -cp " (org-babel-process-file-name basedir 'noquote)
210 " " cmdline " " (if run-from-temp classname fullclassname)
211 " " cmdargs))
212 ;; header args for result processing
213 (result-type (cdr (assq :result-type params)))
214 (result-params (cdr (assq :result-params params)))
215 (result-file (and (eq result-type 'value)
216 (org-babel-temp-file "java-")))
217 ;; the expanded body of the source block
218 (full-body (org-babel-expand-body:java body params)))
219
66 ;; created package-name directories if missing 220 ;; created package-name directories if missing
67 (unless (or (not packagename) (file-exists-p packagename)) 221 (unless (or (not packagedir) (file-exists-p packagedir))
68 (make-directory packagename 'parents)) 222 (make-directory packagedir 'parents))
69 (let ((results (org-babel-eval (concat org-babel-java-command 223
70 " " cmdline " " classname " " cmdargs) ""))) 224 ;; write the source file
71 (org-babel-reassemble-table 225 (setq full-body (org-babel-java--expand-for-evaluation
72 (org-babel-result-cond (cdr (assq :result-params params)) 226 full-body run-from-temp result-type result-file))
73 (org-babel-read results t) 227 (with-temp-file src-file (insert full-body))
74 (let ((tmp-file (org-babel-temp-file "c-"))) 228
75 (with-temp-file tmp-file (insert results)) 229 ;; compile, run, process result
76 (org-babel-import-elisp-from-file tmp-file))) 230 (org-babel-reassemble-table
77 (org-babel-pick-name 231 (org-babel-java-evaluate cmd result-type result-params result-file)
78 (cdr (assq :colname-names params)) (cdr (assq :colnames params))) 232 (org-babel-pick-name
79 (org-babel-pick-name 233 (cdr (assoc :colname-names params)) (cdr (assoc :colnames params)))
80 (cdr (assq :rowname-names params)) (cdr (assq :rownames params))))))) 234 (org-babel-pick-name
235 (cdr (assoc :rowname-names params)) (cdr (assoc :rownames params))))))
236
237;; helper functions
238
239(defun org-babel-java-find-classname (body)
240 "Try to find fully qualified class name in BODY.
241Look through BODY for the package and class. If found, put them
242together into a fully qualified class name and return. Else just
243return class name. If that isn't found either, default to Main."
244 (let ((package (if (string-match org-babel-java--package-re body)
245 (match-string 1 body)))
246 (class (if (string-match org-babel-java--class-re body)
247 (match-string 1 body))))
248 (or (and package class (concat package "." class))
249 (and class class)
250 (and package (concat package ".Main"))
251 "Main")))
252
253(defun org-babel-java--expand-for-evaluation (body suppress-package-p result-type result-file)
254 "Expand source block for evaluation.
255In order to return a value we have to add a __toString method.
256In order to prevent classes without main methods from erroring we
257add a dummy main method if one is not provided. These
258manipulations are done outside of `org-babel--expand-body' so
259that they are hidden from tangles.
260
261BODY is the file content before instrumentation.
262
263SUPPRESS-PACKAGE-P if true, suppress the package statement.
264
265RESULT-TYPE is taken from params.
266
267RESULT-FILE is the temp file to write the result."
268 (with-temp-buffer
269 (insert body)
270
271 ;; suppress package statement
272 (goto-char (point-min))
273 (when (and suppress-package-p
274 (re-search-forward org-babel-java--package-re nil t))
275 (replace-match ""))
276
277 ;; add a dummy main method if needed
278 (goto-char (point-min))
279 (when (not (re-search-forward org-babel-java--main-re nil t))
280 (org-babel-java--move-past org-babel-java--class-re)
281 (insert "\n public static void main(String[] args) {
282 System.out.print(\"success\");
283 }\n\n"))
284
285 ;; special handling to return value
286 (when (eq result-type 'value)
287 (goto-char (point-min))
288 (org-babel-java--move-past org-babel-java--class-re)
289 (insert (format org-babel-java--result-wrapper
290 (org-babel-process-file-name result-file 'noquote)))
291 (search-forward "public static void main(") ; rename existing main
292 (replace-match "public static Object _main("))
293
294 ;; add imports
295 (org-babel-java--import-maybe "java.util" "List")
296 (org-babel-java--import-maybe "java.util" "Arrays")
297 (org-babel-java--import-maybe "java.io" "BufferedWriter")
298 (org-babel-java--import-maybe "java.io" "FileWriter")
299 (org-babel-java--import-maybe "java.io" "IOException")
300
301 (buffer-string)))
302
303(defun org-babel-java--move-past (re)
304 "Move point past the first occurrence of the given regexp RE."
305 (while (re-search-forward re nil t)
306 (goto-char (1+ (match-end 0)))))
307
308(defun org-babel-java--import-maybe (package class)
309 "Import from PACKAGE the given CLASS if it is used and not already imported."
310 (let (class-found import-found)
311 (goto-char (point-min))
312 (setq class-found (re-search-forward class nil t))
313 (goto-char (point-min))
314 (setq import-found
315 (re-search-forward (concat "^import .*" package ".*\\(?:\\*\\|" class "\\);") nil t))
316 (when (and class-found (not import-found))
317 (org-babel-java--move-past org-babel-java--package-re)
318 (insert (concat "import " package "." class ";\n")))))
319
320(defun org-babel-expand-body:java (body params)
321 "Expand BODY with PARAMS.
322BODY could be a few statements, or could include a full class
323definition specifying package, imports, and class. Because we
324allow this flexibility in what the source block can contain, it
325is simplest to expand the code block from the inside out."
326 (let* ((fullclassname (or (cdr (assq :classname params)) ; class and package
327 (org-babel-java-find-classname body)))
328 (classname (car (last (split-string fullclassname "\\.")))) ; just class name
329 (packagename (if (string-match-p "\\." fullclassname) ; just package name
330 (file-name-base fullclassname)))
331 (var-lines (org-babel-variable-assignments:java params))
332 (imports-val (assq :imports params))
333 (imports (if imports-val
334 (split-string (org-babel-read (cdr imports-val) nil) " ")
335 nil)))
336 (with-temp-buffer
337 (insert body)
338
339 ;; wrap main. If there are methods defined, but no main method
340 ;; and no class, wrap everything in a generic main method.
341 (goto-char (point-min))
342 (when (and (not (re-search-forward org-babel-java--main-re nil t))
343 (not (re-search-forward org-babel-java--any-method-re nil t)))
344 (org-babel-java--move-past org-babel-java--package-re) ; if package is defined, move past it
345 (org-babel-java--move-past org-babel-java--imports-re) ; if imports are defined, move past them
346 (insert "public static void main(String[] args) {\n")
347 (indent-code-rigidly (point) (point-max) 4)
348 (goto-char (point-max))
349 (insert "\n}"))
350
351 ;; wrap class. If there's no class, wrap everything in a
352 ;; generic class.
353 (goto-char (point-min))
354 (when (not (re-search-forward org-babel-java--class-re nil t))
355 (org-babel-java--move-past org-babel-java--package-re) ; if package is defined, move past it
356 (org-babel-java--move-past org-babel-java--imports-re) ; if imports are defined, move past them
357 (insert (concat "\npublic class " (file-name-base classname) " {\n"))
358 (indent-code-rigidly (point) (point-max) 4)
359 (goto-char (point-max))
360 (insert "\n}"))
361 (goto-char (point-min))
362
363 ;; insert variables from source block headers
364 (when var-lines
365 (goto-char (point-min))
366 (org-babel-java--move-past org-babel-java--class-re) ; move inside class
367 (insert (mapconcat 'identity var-lines "\n"))
368 (insert "\n"))
369
370 ;; add imports from source block headers
371 (when imports
372 (goto-char (point-min))
373 (org-babel-java--move-past org-babel-java--package-re) ; if package is defined, move past it
374 (insert (mapconcat (lambda (package) (concat "import " package ";")) imports "\n") "\n"))
375
376 ;; add package at the top
377 (goto-char (point-min))
378 (when (and packagename (not (re-search-forward org-babel-java--package-re nil t)))
379 (insert (concat "package " packagename ";\n")))
380
381 ;; return expanded body
382 (buffer-string))))
383
384(defun org-babel-variable-assignments:java (params)
385 "Return a list of java statements assigning the block's variables.
386variables are contained in PARAMS."
387 (mapcar
388 (lambda (pair)
389 (let* ((type-data (org-babel-java-val-to-type (cdr pair)))
390 (basetype (car type-data))
391 (var-to-java (lambda (var) (funcall #'org-babel-java-var-to-java var basetype))))
392 (format " static %s %s = %s;"
393 (cdr type-data) ; type
394 (car pair) ; name
395 (funcall var-to-java (cdr pair))))) ; value
396 (org-babel--get-vars params)))
397
398(defun org-babel-java-var-to-java (var basetype)
399 "Convert an elisp value to a java variable.
400Convert an elisp value, VAR, of type BASETYPE into a string of
401java source code specifying a variable of the same value."
402 (cond ((and (sequencep var) (not (stringp var)))
403 (let ((var-to-java (lambda (var) (funcall #'org-babel-java-var-to-java var basetype))))
404 (concat "Arrays.asList(" (mapconcat var-to-java var ", ") ")")))
405 ((eq var 'hline) org-babel-java-hline-to)
406 ((eq basetype 'integerp) (format "%d" var))
407 ((eq basetype 'floatp) (format "%f" var))
408 ((eq basetype 'stringp) (if (and (stringp var) (string-match-p ".\n+." var))
409 (error "Java does not support multiline string literals")
410 (format "\"%s\"" var)))))
411
412(defun org-babel-java-val-to-type (val)
413 "Determine the type of VAL.
414Return (BASETYPE . LISTTYPE), where BASETYPE is a symbol
415representing the type of the individual items in VAL, and
416LISTTYPE is a string name of the type parameter for a container
417for BASETYPE items."
418 (let* ((basetype (org-babel-java-val-to-base-type val))
419 (basetype-str (pcase basetype
420 (`integerp "Integer")
421 (`floatp "Double")
422 (`stringp "String")
423 (_ (error "Unknown type %S" basetype)))))
424 (cond
425 ((and (listp val) (listp (car val))) ; a table
426 (cons basetype (format "List<List<%s>>" basetype-str)))
427 ((or (listp val) (vectorp val)) ; a list declared in the source block header
428 (cons basetype (format "List<%s>" basetype-str)))
429 (t ; return base type
430 (cons basetype basetype-str)))))
431
432(defun org-babel-java-val-to-base-type (val)
433 "Determine the base type of VAL.
434VAL may be
435`integerp' if all base values are integers
436`floatp' if all base values are either floating points or integers
437`stringp' otherwise."
438 (cond
439 ((integerp val) 'integerp)
440 ((floatp val) 'floatp)
441 ((or (listp val) (vectorp val))
442 (let ((type nil))
443 (mapc (lambda (v)
444 (pcase (org-babel-java-val-to-base-type v)
445 (`stringp (setq type 'stringp))
446 (`floatp
447 (when (or (not type) (eq type 'integerp))
448 (setq type 'floatp)))
449 (`integerp
450 (unless type (setq type 'integerp)))))
451 val)
452 type))
453 (t 'stringp)))
454
455(defun org-babel-java-table-or-string (results)
456 "Convert RESULTS into an appropriate elisp value.
457If the results look like a list or vector, then convert them into an
458Emacs-lisp table, otherwise return the results as a string."
459 (let ((res (org-babel-script-escape results)))
460 (if (listp res)
461 (mapcar (lambda (el) (if (eq 'null el)
462 org-babel-java-null-to
463 el))
464 res)
465 res)))
466
467(defun org-babel-java-evaluate (cmd result-type result-params result-file)
468 "Evaluate using an external java process.
469CMD the command to execute.
470
471If RESULT-TYPE equals `output' then return standard output as a
472string. If RESULT-TYPE equals `value' then return the value
473returned by the source block, as elisp.
474
475RESULT-PARAMS input params used to format the response.
476
477RESULT-FILE filename of the tempfile to store the returned value in
478for `value' RESULT-TYPE. Not used for `output' RESULT-TYPE."
479 (let ((raw (pcase result-type
480 (`output (org-babel-eval cmd ""))
481 (`value (org-babel-eval cmd "")
482 (org-babel-eval-read-file result-file)))))
483 (org-babel-result-cond result-params raw
484 (org-babel-java-table-or-string raw))))
81 485
82(provide 'ob-java) 486(provide 'ob-java)
83 487