| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488 | ;;; ob-java.el --- org-babel functions for java evaluation -*- lexical-binding: t -*-;; Copyright (C) 2011-2021 Free Software Foundation, Inc.;; Authors: Eric Schulte;;          Dan Davison;; Maintainer: Ian Martins <ianxm@jhu.edu>;; Keywords: literate programming, reproducible research;; Homepage: https://orgmode.org;; This file is part of GNU Emacs.;; GNU Emacs 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.;; GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.;;; Commentary:;; Org-Babel support for evaluating java source code.;;; Code:(require 'ob)(defvar org-babel-tangle-lang-exts)(add-to-list 'org-babel-tangle-lang-exts '("java" . "java"))(defvar org-babel-temporary-directory) ; from ob-core(defvar org-babel-default-header-args:java '((:results . "output")					     (:dir . "."))  "Default header args for java source blocks.The docs say functional mode should be the default [1], butob-java didn't originally support functional mode, so we keepscripting mode as the default for now to maintain previousbehavior.Most languages write tempfiles to babel's temporary directory,but ob-java originally had to write them to the currentdirectory, so we keep that as the default behavior.[1] https://orgmode.org/manual/Results-of-Evaluation.html")(defconst org-babel-header-args:java '((imports . :any))  "Java-specific header arguments.")(defcustom org-babel-java-command "java"  "Name of the java command.May be either a command in the path, like java or an absolutepath name, like /usr/local/bin/java.  Parameters may be used,like java -verbose."  :group 'org-babel  :package-version '(Org . "9.5")  :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 absolutepath name, like /usr/local/bin/javac.  Parameters may be used,like javac -verbose."  :group 'org-babel  :package-version '(Org . "9.5")  :type 'string)(defcustom org-babel-java-hline-to "null"  "Replace hlines in incoming tables with this when translating to java."  :group 'org-babel  :package-version '(Org . "9.5")  :type 'string)(defcustom org-babel-java-null-to 'hline  "Replace `null' in java tables with this before returning."  :group 'org-babel  :package-version '(Org . "9.5")  :type 'symbol)(defconst org-babel-java--package-re (rx line-start (0+ space) "package"					 (1+ space) (group (1+ (in alnum ?_ ?.))) ; capture the package name					 (0+ space) ?\; line-end)  "Regexp for the package statement.")(defconst org-babel-java--imports-re (rx line-start (0+ space) "import"                                         (opt (1+ space) "static")					 (1+ space) (group (1+ (in alnum ?_ ?. ?*))) ; capture the fully qualified class name					 (0+ space) ?\; line-end)  "Regexp for import statements.")(defconst org-babel-java--class-re (rx line-start (0+ space) (opt (seq "public" (1+ space)))				       "class" (1+ space)				       (group (1+ (in alnum ?_))) ; capture the class name				       (0+ space) ?{)  "Regexp for the class declaration.")(defconst org-babel-java--main-re (rx line-start (0+ space) "public"				      (1+ space) "static"				      (1+ space) "void"				      (1+ space) "main"				      (0+ space) ?\(				      (0+ space) "String"				      (0+ space) (1+ (in alnum ?_ ?\[ ?\] space)) ; "[] args" or "args[]"				      (0+ space) ?\)				      (0+ space) (opt "throws" (1+ (in alnum ?_ ?, ?. space)))				      ?{)  "Regexp for the main method declaration.")(defconst org-babel-java--any-method-re (rx line-start					    (0+ space) (opt (seq (1+ alnum) (1+ space)))   ; visibility					    (opt (seq "static" (1+ space)))                ; binding					    (1+ (in alnum ?_ ?\[ ?\]))                     ; return type                                            (1+ space) (1+ (in alnum ?_))                  ; method name					    (0+ space) ?\(					    (0+ space) (0+ (in alnum ?_ ?\[ ?\] ?, space)) ; params					    (0+ space) ?\)					    (0+ space) (opt "throws" (1+ (in alnum ?_ ?, ?. 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 aselegant.  This modified the source block to write out the valueit wants to return to a temporary file so that ob-java can readit back.  The name of the temporary file to write must bereplaced in this string.")(defun org-babel-execute:java (body params)  "Execute a java source block with BODY code and PARAMS params."  (let* (;; allow header overrides         (org-babel-java-compiler          (or (cdr (assq :javac params))              org-babel-java-compiler))         (org-babel-java-command          (or (cdr (assq :java params))              org-babel-java-command))         ;; if true, run from babel temp directory         (run-from-temp (not (cdr (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 (string-match-p "\\." fullclassname)                          (file-name-base fullclassname)))         ;; the base dir that contains the top level package dir         (basedir (file-name-as-directory (if run-from-temp                                              (if (file-remote-p default-directory)                                                  (concat                                                   (file-remote-p default-directory)                                                   org-babel-remote-temporary-directory)                                                org-babel-temporary-directory)                                            default-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)) ""))         ;; runtime flags         (cmdline (or (cdr (assq :cmdline params)) ""))         ;; command line args         (cmdargs (or (cdr (assq :cmdargs params)) ""))         ;; the command to compile and run         (cmd (concat org-babel-java-compiler " " cmpflag " "                      (org-babel-process-file-name src-file 'noquote)                      " && " org-babel-java-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 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))    ;; 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 themtogether into a fully qualified class name and return.  Else justreturn class name.  If that isn't found either, default to Main."  (let ((package (if (string-match org-babel-java--package-re body)                     (match-string 1 body)))        (class (if (string-match org-babel-java--class-re body)                   (match-string 1 body))))    (or (and package class (concat package "." class))        (and class class)        (and package (concat package ".Main"))        "Main")))(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 weadd a dummy main method if one is not provided.  Thesemanipulations are done outside of `org-babel--expand-body' sothat 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 classdefinition specifying package, imports, and class.  Because weallow this flexibility in what the source block can contain, itis 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 (string-match-p "\\." 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--main-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 ofjava 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 symbolrepresenting the type of the individual items in VAL, andLISTTYPE is a string name of the type parameter for a containerfor 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 anEmacs-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 astring.  If RESULT-TYPE equals `value' then return the valuereturned 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 infor `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);;; ob-java.el ends here
 |