;;; jsee.el --- Java source documentation viewer

;; Copyright (C) 1998, 2000, 2003, 2005 by David Ponce

;; Author: David Ponce <david@dponce.com>
;; Maintainer: David Ponce <david@dponce.com>
;; Created: 3 Dec 1998
;; Keywords: jde
;; Revision: $Id: jsee.el,v 1.11 2005/07/05 07:00:37 ponced Exp $

(defconst jsee-version "2.1")

;; This file is not part of 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 2, 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; see the file COPYING.  If not, write to
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth
;; Floor, Boston, MA 02110-1301, USA.

;;; Commentary:
;;
;; This package is a JDE add-on which automatically generates and view
;; documentation generated from the source of the java file in the
;; current buffer.  The default implementation uses javadoc as
;; generator and view the documentation with the default browser.
;;
;; M-x `jsee-browse-api-doc' generate and view the source
;; documentation of the java file in the current buffer.
;;
;; To install and use, put this file on your Emacs-Lisp load path and
;; add the following into your ~/.emacs startup file:
;;
;; (require 'jsee)
;;

;;; History:
;;

;;; Code:
(require 'jde)

;;; Options
;;

;; ALL CUSTOMIZABLE VARIABLES ARE PREFIXED WITH `jde-' TO ALLOW THEIR
;; SAVING IN A JDE PROJECT FILE.

(defgroup jde-jsee nil
  "Global jsee customization."
  :group 'jde
  :prefix "jde-jsee-")

(defcustom jde-jsee-doc-generator "javadoc"
  "*The Java API Documentation Generator.
Specifies the path to the tool to be used to generate API
documentation for the program in the current buffer.  The default is
the JDK tool (javadoc)."
  :group 'jde-jsee
  :type 'string)

(defcustom jde-jsee-get-doc-generator-options-function 'jsee-javadoc-get-options
  "*Function used to get the doc generator command line options.
The default function provided builds command line options for the JDK
javadoc tool."
  :group 'jde-jsee
  :type 'function)

(defcustom jde-jsee-pre-generate-function 'jsee-javadoc-delete-previous-output
  "*Function called before running the doc generator.
If nil does nothing. The default function provided
`jsee-javadoc-delete-previous-output' deletes any HTML file previously
generated by javadoc for the current Java file."
  :group 'jde-jsee
  :type 'function)

(defcustom jde-jsee-post-generate-function 'jsee-javadoc-browse-output
  "*Function called after the doc generator completed.
If nil does nothing. The default function provided
`jsee-javadoc-browse-output' browses the HTML file generated by
javadoc for the current Java file."
  :group 'jde-jsee
  :type 'function)

(defcustom jde-jsee-load-hook '(jsee-default-load-hook)
  "Hook run when package has been loaded.
The default hook provided `jsee-default-load-hook' maps the `jde-mode'
key `C-f1' to the `jsee-browse-api-doc' command."
  :group 'jde-jsee
  :type 'hook)

;;; Common functions
;;
(defun jsee-fullpath (path)
  "Return the full path of the given PATH.
~ (HOME) and environment variables references are expanded."
  (expand-file-name (substitute-in-file-name path)))

(defun jsee-call (fvar &optional with-error &rest with-args)
  "Wrapper to the function `funcall'.
FVAR is a symbol bound to the function to be called.
If optional argument WITH-ERROR is non-nil, signal an error if FVAR is
not a valid function.
WITH-ARGS are the parameters passed to the function."
  (if (and (symbolp fvar) (fboundp fvar))
      (apply fvar with-args)
    (if with-error
        (error "Invalid function %s" fvar))))

(defun jsee-delete-file (file)
  "Helper function used to delete the file FILE.
Return non-nil if the file is deleted or nil if not."
  (if (stringp file)
      (condition-case err
          (progn
            (delete-file file)
            (message "File %s deleted." file)
            t)
        (error
         (message "%s" (error-message-string err))
         nil))))

(defun jsee-get-package-name ()
  "Return the package name in the current file.
This function uses semantic to find the package statement."
  (let ((packages (semantic-find-nonterminal-by-token
                   'package (current-buffer))))
    (if packages
        (semantic-token-name (car packages)))))

(defun jsee-get-file-class ()
  "Return the qualified class name of the current file."
  (let ((package (jsee-get-package-name)))
    (concat (if package (concat package ".") "")
            (file-name-sans-extension
             (file-name-nondirectory buffer-file-name)))))

(defvar jsee-file-class nil
  "Hold the qualified class name of the current file.
This variable is local to the compilation buffer and can be used by
the `jde-jsee-post-generate-function'.")

;;; Options specific to the javadoc generator
;;
(defgroup jde-jsee-javadoc nil
  "Options specific to the jsee javadoc generator."
  :group 'jde-jsee
  :prefix "jde-jsee-javadoc-")

(defcustom jde-jsee-javadoc-output-dir "$TEMP/jsee"
  "*The working directory where javadoc will generate HTML files.
This directory must exists. For better results with JDK 1 javadoc
style you can copy into it the 'images' sub-directory of the JDK 1 API
documentation."
  :group 'jde-jsee-javadoc
  :type 'string)

(defcustom jde-jsee-javadoc-others-options ""
  "*javadoc options as a string of command-line arguments.
The value of this variable should be a string of switches understood
by javadoc, for example, \"-author -version\". This variable is
intended to be used to set javadoc options not otherwise defined by
jsee."
  :group 'jde-jsee-javadoc
  :type 'string)

(defcustom jde-jsee-javadoc-version-option t
  "*If non-nil javadoc will include @version paragraphs."
  :group 'jde-jsee-javadoc
  :type 'boolean)

(defcustom jde-jsee-javadoc-nodeprecated-option nil
  "*If non-nil javadoc will exclude @deprecated paragraphs."
  :group 'jde-jsee-javadoc
  :type 'boolean)

(defcustom jde-jsee-javadoc-author-option t
  "*If non-nil javadoc will include @author paragraphs."
  :group 'jde-jsee-javadoc
  :type 'boolean)

(defcustom jde-jsee-javadoc-noindex-option t
  "*If non-nil javadoc will not generate method and field index."
  :group 'jde-jsee-javadoc
  :type 'boolean)

(defcustom jde-jsee-javadoc-notree-option t
  "*If non-nil javadoc will not generate the class/interface hierarchy."
  :group 'jde-jsee-javadoc
  :type 'boolean)

(defcustom jde-jsee-javadoc-public-option nil
  "*If non-nil javadoc will show only public classes and members."
  :group 'jde-jsee-javadoc
  :type 'boolean)

(defcustom jde-jsee-javadoc-protected-option t
  "*If non-nil javadoc will show protected/public classes and members."
  :group 'jde-jsee-javadoc
  :type 'boolean)

(defcustom jde-jsee-javadoc-package-option nil
  "*If non-nil javadoc will show package/protected/public classes and members."
  :group 'jde-jsee-javadoc
  :type 'boolean)

(defcustom jde-jsee-javadoc-private-option nil
  "*If non-nil javadoc will show all classes and members."
  :group 'jde-jsee-javadoc
  :type 'boolean)

;;; Javadoc specific
;;
(defun jsee-javadoc-get-output-dir ()
  "Return the directory where javadoc will generate HTML files.
That is, `jde-jsee-javadoc-output-dir' if non-nil, else
`default-directory'."
  (if (stringp jde-jsee-javadoc-output-dir)
      (file-name-as-directory
       (jsee-fullpath jde-jsee-javadoc-output-dir))
    default-directory))

(defun jsee-javadoc-get-classpath-option ()
  "Return the javadoc -classpath value.
From respectively `jde-compile-option-classpath' or
`jde-global-classpath'."
  (if jde-compile-option-classpath
      (jde-build-classpath-arg jde-compile-option-classpath
                               jde-quote-classpath)
    (if jde-global-classpath
        (jde-build-classpath-arg jde-global-classpath
                                 jde-quote-classpath)
      "")))

(defun jsee-javadoc-get-options ()
  "Return the javadoc command line options."
  (let ((options (jsee-javadoc-get-classpath-option)))
    (setq options (concat options " -d " (jsee-javadoc-get-output-dir)))
    (if jde-jsee-javadoc-version-option
        (setq options (concat options " -version")))
    (if jde-jsee-javadoc-nodeprecated-option
        (setq options (concat options " -nodeprecated")))
    (if jde-jsee-javadoc-author-option
        (setq options (concat options " -author")))
    (if jde-jsee-javadoc-noindex-option
        (setq options (concat options " -noindex")))
    (if jde-jsee-javadoc-notree-option
        (setq options (concat options " -notree")))
    (if jde-jsee-javadoc-public-option
        (setq options (concat options " -public")))
    (if jde-jsee-javadoc-protected-option
        (setq options (concat options " -protected")))
    (if jde-jsee-javadoc-package-option
        (setq options (concat options " -package")))
    (if jde-jsee-javadoc-private-option
        (setq options (concat options " -private")))
    (if (not (string-equal jde-jsee-javadoc-others-options ""))
        (setq options (concat options " " jde-jsee-javadoc-others-options)))
    options))

(defun jsee-lookup-java1-javadoc (class docset-dir) 
  (let ((doc-path
	 (concat 
	  (expand-file-name class docset-dir)
	  ".html")))
    (if (file-exists-p doc-path) 
	doc-path)))

(defun jsee-lookup-java2-javadoc (class docset-dir) 
  (let ((doc-path
	 (concat 
	  (expand-file-name 
	   (substitute ?/ ?. class) 
	   docset-dir)
	  ".html")))
    (if (file-exists-p doc-path) 
	doc-path)))

(defun jsee-javadoc-get-output-filename ()
  "Return the name of the javadoc HTML file, from this Java file.
If not found return nil."
  (let ((docset (jsee-javadoc-get-output-dir))
        (class  jsee-file-class))
    (or (jsee-lookup-java1-javadoc class docset)
        (jsee-lookup-java2-javadoc class docset))))

(defun jsee-javadoc-delete-previous-output ()
  "Remove any previous javadoc HTML file."
  (let ((docset (jsee-javadoc-get-output-dir))
        (class  jsee-file-class))
    (jsee-delete-file (jsee-lookup-java1-javadoc class docset))
    (jsee-delete-file (jsee-lookup-java2-javadoc class docset))))
  
(defun jsee-javadoc-browse-output ()
  "Browse the generated javadoc file."
  (let ((output (jsee-javadoc-get-output-filename)))
    (cond (output
           (message "browse-url-of-file %s" output)
           (browse-url-of-file output))
          (t
           (message "Doc file not found for %s in %s"
                    jsee-file-class
                    (jsee-javadoc-get-output-dir))))))

;;; Doc generator process handling
;;
(defun jsee-pre-generate-handler ()
  "Handle operations run before the doc generator."
  (message "jsee-pre-generate-handler in buffer %s" (buffer-name))
  (make-local-variable 'jsee-file-class)
  (jsee-call jde-jsee-pre-generate-function)
  (set (make-local-variable 'compilation-finish-function)
       'jsee-post-generate-handler))

(defun jsee-post-generate-handler (buffer msg)
  "Handle operations run after the doc generator."
  (message "jsee-post-generate-handler in buffer %s" (buffer-name))
  (if (string-equal msg "finished\n")
      (jsee-call jde-jsee-post-generate-function)))

(defun jsee-make-doc-generator-command ()
  "Return the command to generate the Java documentation."
  (concat jde-jsee-doc-generator " " 
          (jsee-call jde-jsee-get-doc-generator-options-function 'with-error)
          " "
          (file-name-nondirectory buffer-file-name)))

(defun jsee-run-doc-generator ()
  "Run the Java API Documentation Generator."
  (let ((compile-command (jsee-make-doc-generator-command))
        (compilation-process-setup-function 'jsee-pre-generate-handler))
    (save-some-buffers (not compilation-ask-about-save) nil)
    (setq jsee-file-class (jsee-get-file-class))
    (compile-internal compile-command "No more errors")))

;;; Command & hook
;;

;;;###autoload
(defun jsee-browse-api-doc ()
  "Browse the Java API Documentation of the current Java file."
  (interactive)
  (if (and (eq major-mode 'jde-mode) (semantic-active-p))
      (jsee-run-doc-generator)
    (error "Not in `jde-mode', or buffer not set up for parsing.")))

(defun jsee-default-load-hook ()
  "Default hook run when package has been loaded.
Define the keyboard shortcut [C-f1] for `jsee-browse-api-doc'."
  (define-key java-mode-map [(control f1)] 'jsee-browse-api-doc))

(provide 'jsee)

(run-hooks 'jde-jsee-load-hook)

;;; jsee.el ends here
