4

In Emacs use a Lisp function to run the Java program the current file correspond to.

(defun java-run-current-file ()
  "Runs the java program the current file correspond to"
  (interactive)
  (shell-command
   (concat "java "
       (file-name-sans-extension
        (file-name-nondirectory (buffer-file-name))))))

It works by stripping the current file name of its path and extension and using it as an argument to java which is run from the path where the file is at. The problem with this approach is that if the current file is part of a package then

  1. the argument to java has to be prefixed with the package name and a dot, and
  2. java has to be run from the directory containing the package.

So for example if the file is file.java and the package name is pkg java is called as java pkg.file from the directory containing the directory pkg (the parent directory of the directory where file.java is placed).

How can I modify the function to be aware of packages and construct the argument to java accordingly? I guess one might solve this by searching the current file for a package declaration, such as

package pkg;

and if it finds one it uses that package name to call java appropriately.

N.N.
  • 8,336
  • 12
  • 54
  • 94

2 Answers2

2

Try the following code to search the current file for a package declaration:

(save-excursion
  (goto-char (point-min))
  (when (re-search-forward "^\\s *package\\s +\\(.*\\);" (point-max) t)
    (match-string 1)))

This will return the value between a package declaration and the semi-colon. It will return nil if no such declaration is found.

(Beware that this might fail if there is a commented-out package declaration somewhere before the actual package declaration, in a C-style, multi-line comment (/* ... */).)

In order to change the directory from which a shell command is run, use the cd function. Since in Java the package structure should reflect the directory structure, you can use the package information determined by the above code to figure out the base directory of your source code:

(let ((directory (file-name-directory (buffer-file-name)))
      (sub-dirs (reverse (split-string package "\\."))))
  (while sub-dirs
    (if (string-match (concat "^\\(.*/\\)" (regexp-quote (car sub-dirs)) "/$") directory)
        (setq directory (match-string 1 directory)
              sub-dirs (cdr sub-dirs))
      (error "Package does not match directory structure")))
  (cd directory))

You may use this code to extend your function like so:

(defun java-run-current-file ()
  "Runs the java program the current file corresponds to"
  (interactive)
  (let* ((package (save-excursion
                    (goto-char (point-min))
                    (when (re-search-forward "^\\s *package\\s +\\(.*\\);" (point-max) t)
                      (match-string 1))))
         (directory (file-name-directory (buffer-file-name)))
         sub-dirs)

    (if directory
        (setq directory (file-truename directory))
      (error "Current buffer is not visiting a file"))

    (when package
      (setq sub-dirs (reverse (split-string package "\\.")))

      (while sub-dirs
        (if (string-match (concat "^\\(.*/\\)" (regexp-quote (car sub-dirs)) "/$") directory)
            (setq directory (match-string 1 directory)
                  sub-dirs (cdr sub-dirs))
          (error "Package does not match directory structure"))))

    (cd directory)
    (shell-command
     (concat "java "
             (if package (concat package ".") "")
             (file-name-sans-extension
              (file-name-nondirectory (buffer-file-name)))))))
Thomas
  • 17,016
  • 4
  • 46
  • 70
  • Thanks. That seems to satisfy the first point in my question. Now only second point remains of running `java` from the parent directory of the current directory (rather than the current) if `package` is defined. I read the documentation for `shell-command` but I cannot seem to find how to influence which directory the shell starts in. – N.N. Sep 23 '12 at 09:46
  • Ah, okay - I've edited my answer to address the second part as well. – Thomas Sep 24 '12 at 05:21
  • Actually, the code has a major bug when you try to run it on a class that has no `package` declaration. I've pointed out myself at the top of my answer that the package detection code returns `nil` when no declaration is found, but then I don't handle that `nil` value properly. The computation of `sub-dirs` should only be done if `package` is non-nil, and otherwise there should be an `(error...)`. Can I leave that as an exercise to the reader? – Thomas Sep 25 '12 at 00:46
  • In trying to write the best answer you might want to edit the answer to fix the bug. – N.N. Sep 25 '12 at 04:43
  • Is is possible to modularize the part above `(cd directory)` to one or two functions so that one may use them for a function for the compiler too? Also, is it possible to `cd` back to the original directory after the function is done so that it does not cause unexpected behavior with e.g. `C-x C-f`? – N.N. Oct 03 '12 at 08:56
  • Sure it is. You could write a function, say, `java-get-package-root-directory` that contains the `(let...` expression until right before `(cd directory)`. Then `java-run-current-file` would call that function. As for cd'ing back to the original directory, that is actually a very good idea. You should probably wrap the execution of the shell command in an `(unwind-protect ...)` to make sure the cd'ing back to the original directory is executed even when an error occurs while running the shell command. – Thomas Oct 04 '12 at 00:00
  • Sounds good. I am not sure how to write a function that would return two strings though as not only `directory` but also `package` seems to be needed for the latter part (after `(cd...`). – N.N. Oct 04 '12 at 16:43
  • You could either modularize it even more and subdivide the code into two functions, one that returns the package and one that returns the directory. The latter one would call the former one, and your `java-run-current-file` would also call the one that returns the package. Or, if you want to avoid calling the same function twice (which however, would probably be premature optimization) you can always return a list of two values: `(directory package)`. – Thomas Oct 05 '12 at 06:10
  • I tried making a function that returns a list with two values as you suggest but when I try to use the function I get "wrong-type-argument stringp (directory)". For the definition see https://gist.github.com/3884514 . If you happen to spot the problem you are welcome to add the modification to your answer to make your answer even better. – N.N. Oct 13 '12 at 12:40
  • Have you actually looked at the returned value of your `java-get-package-root-directory` function? It's a list containing two symbols, `'package` and `'directory`. What you really want, though, is the contents of the variables with the same name. Replace `'(package directory)` with `(list package directory)` and you should be good to go. – Thomas Oct 14 '12 at 06:51
  • I got it working via `cons` after realizing that `list` did not work either (dunno why though) and I also incorporated the `unwind-protect` mechanism you suggested https://gist.github.com/3889376 . Would you like to add it to your answer for others to see or should I add it as another answer (if you don't mind of course)? – N.N. Oct 14 '12 at 18:20
  • Try again with `(list...)`, it definitely works (that's what it's for really) ;-). As for extending my answer: I've been hesistant to do further edits because I think that the focus of your question has slightly shifted. Thus I believe my answer as it is now addresses your original question quite well. Maybe the way to go would be to post a second question regarding the modularization? That should make it easier for others who might google for similar problems. I'd be happy to write an answer that summarizes our discussion here, and maybe someone else can even come up with something better? – Thomas Oct 14 '12 at 23:19
  • You're right. I've [posted another question](http://stackoverflow.com/q/12892104/789593). – N.N. Oct 15 '12 at 08:49
0

Either use a proper Java IDE (you will eventually if you do this on a regular basis) or read the file to parse the package line.

Also note that you will most likely also need to specify the compilation root folder (or cd to it) to be able to set the class path properly.

For deriving the actual class name, reading the file and searching for "package (.*);" (matching newlines too) would most likely be good enough.

To be frank, when I need to work like this, I write a small script (bat-file under WIndows) which does what I need. The reason is that I usually need a lot of jarfiles on the classpath too, which eventually boils down to me needing to specify the whole command line anyway.

Thorbjørn Ravn Andersen
  • 73,784
  • 33
  • 194
  • 347
  • 4
    If he or she wanted to stick with emacs, for other reasons that you may not appreciate, how exactly would they parse the file and get the package line in emacs lisp? – mwolfetech Sep 22 '12 at 18:54
  • @mwolfetech I sympathize completely with wanting to use Emacs - only thing is that for Java development much, much better tooling exist. I am not familiar with Emacs-Lisp but reading the file and search for "package (.*);" (matching newlines too) would probably be a good approach. – Thorbjørn Ravn Andersen Sep 22 '12 at 20:25