;;; pmd.el --- Major mode for pluging PMD into Emacs.

;; Author: John Russell <drjimmy42 at yahoo.com>
;; Maintainer: Nascif A. Abousalh-Neto <nascif at acm.org>
;; Created: 06/16/2004 18:21
;; Version: $Revision$
;; Keywords: PMD major-mode

;; Copyright (C) 2002 John Russell
;; Copyright (C) 2003 Nascif A. Abousalh-Neto

;; 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 GNU Emacs; see the file COPYING.  If not, write to
;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.

;;; Commentary:
;; Installation:
;;  1) Install the core PMD application (not part of this download, get it 
;;     from http://pmd.sourceforge.net/);
;;  2) Make sure the directory of pmd.el is part of your Emacs load-path;
;; 	3) Add this line to your .emacs file:
;; 	    (autoload 'pmd-current-buffer "pmd" "PMD Mode" t)
;; 	    (autoload 'pmd-current-dir "pmd" "PMD Mode" t)
;;  4) Load pmd.el
;;  5) Configure the location of the Java executable and the PMD installation 
;;     directory using the command pmd-customize.
;;
;; 	Once this is done, you can call the pmd-current-buffer and pmd-current-dir 
;;  functions using M-x <function name> or by bind them to key sequences.

;; Configuration:
;; 	Besides the variables described above, you can also compile the rulesets used by PMD.
;;  Type:
;; 	    M-x pmd-customize
;; 		--or--
;; 	    M-x customize-group <RET>
;; 	    pmd <RET>
	
;; Description:
;; 	This mode uses a compilation buffer to display the output of PMD
;; (http://pmd.sourceforge.net/).  
;; It provides two commands, pmd-current-buffer and pmd-current-dir, which as you
;; guessed run PMD on the contents of the current buffer (must be a .java file)
;; or on all .java files in the directory associated with the current buffer. 
;; You can defining the PMD rulesets by customizing the variable pmd-ruleset-list.

;; Change History 

;; 10/21/2005 0.6: Nascif A. Abousalh-Neto
;; - Updated to work with PMD 3.3

;; 06/16/2004 0.5: Nascif A. Abousalh-Neto
;; - Tested with PMD 1.08
;; - fixed dependency on missing defun
;;
;; 03/24/03 - 0.4: Nascif A. Abousalh-Neto
;; Update to align with PMD 1.04.
;; - Added new method, pmd-current-dir
;; - Removed variable pmd-version
;; - Changed customization: string variable pmd-rulesets replaced by list-based variable pmd-ruleset-list

;; 02/04/03 - 0.3: Nascif A. Abousalh-Neto
;; Updated the primary plugin with 
;; contributions.  Note that these changes include an updated pmd-1.02.jar file
;; to support them.  These changes will be part of pmd-1.03 when it comes out;
;; this is just an updated release of pmd-1.02 to support this Emacs plugin.

;; 01/16/03 - 0.2 - John Russell
;; Completely rewritten

;; 06/06/02 - 0.1
;; First version of PMD for Emacs
;; Defined one function "pmd-current-buffer" which will
;; run PMD on the current buffer, and write the output
;; to *PMD*. Output format is in XML.  I will work in trying to 
;; get a better, easier to read format for everyone.


(defgroup pmd nil "PMD"
  :group 'emacs)

(defcustom pmd-java-home "/usr/local/bin/java"
  "Java binary to run PMD with."
  :type 'file
  :group 'pmd )

(defcustom pmd-home "~/pmd"
  "Directory where PMD is installed."
  :type 'directory
  :group 'pmd)

(defcustom pmd-ruleset-list (list "basic" "braces" "clone" "codesize" "controversial" "coupling" 
                                  "design" "finalizers" "imports" "javabeans" "junit" "logging-java" 
                                  "naming" "optimizations" "strictexception" "strings" "sunsecure" 
                                  "unusedcode" "logging-jakarta-commons")

  "A list of Rulesets to apply. Rulesets are specified in XML files inside the \"rulesets\" subdirectory of the main PMD jar file."
  :type '(repeat (file :tag "Ruleset"))
  :group 'pmd)

;;-------------------------
;;Inner workings
;;-------------------------

(defconst pmd-xemacsp (string-match "XEmacs" (emacs-version)))

(defun pmd-help ()
  "Help for `pmd-mode'."
  (interactive)
  (describe-function 'pmd-mode))

;;-------------------------
;; Main functions
;;-------------------------

;;;###autoload 
(defun pmd-customize ()
  "Customization of group `pmd' for Pmd-mode."
  (interactive)
  (customize-group "pmd"))

;;;###autoload 
(defun pmd-current-buffer ()
  "Run PMD on the contents of the current buffer."
  (interactive)
  (if (string-equal (file-name-extension
                        (buffer-file-name)) "java")
         (pmd-file-or-dir (buffer-file-name))
    (message "Current buffer does not contain a Java file.  Aborting.")))

(defun pmd-current-dir ()
  "Run PMD on the contents of the current directory (recursively)."
  (interactive)
  (pmd-file-or-dir (file-name-directory (buffer-file-name))))

(defun pmd-classpath ()
  (let* ((path-separator (if (eq system-type 'windows-nt) ";" ":"))
         (path-slash     (if (eq system-type 'windows-nt) "\\" "/"))
         (pmd-etc     (concat pmd-home "etc"))
         (pmd-lib     (concat pmd-home path-slash "lib" path-slash)))
    (concat "\'" 
            pmd-etc path-separator
            (mapconcat
             (lambda (path)
               path) 
             (directory-files pmd-lib t "\\.jar$")
             path-separator)
            "\'")))

(defun pmd-jar ()
  (let* ((path-separator (if (eq system-type 'windows-nt) ";" ":"))
         (path-slash     (if (eq system-type 'windows-nt) "\\" "/"))
         (pmd-etc     (concat pmd-home "etc"))
         (pmd-lib     (concat pmd-home path-slash "lib" path-slash)))
    (concat "\'" 
            (car (directory-files pmd-lib t ".*pmd-.*\\.jar$"))
            "\'")))

;; (defun pmd-file-or-dir (target)
;;   "Run PMD on the given target (file or dir)"
;;   (if (eq (count-windows) 1)
;;       (split-window-vertically))
;;   (other-window 1)
;;   (switch-to-buffer (get-buffer-create "*PMD*"))
;;   (if pmd-append-output
;;       (goto-char (point-max))
;;     (erase-buffer))
;;   (pmd-mode)
;;   (insert-string (concat " PMD output for " target "\n\n"))

;;   (insert-string (concat pmd-java-home " -cp \"" (pmd-classpath) "\" net.sourceforge.pmd.PMD "
;;                            target " emacs " (mapconcat (lambda (path) path) pmd-ruleset-list ",")))

;;   (insert-string "\n\n")

;;   (insert (concat (shell-command-to-string 
;;                    (concat pmd-java-home " -cp \"" (pmd-classpath) "\" net.sourceforge.pmd.PMD "
;;                            target " emacs " (mapconcat (lambda (path) path) pmd-ruleset-list ","))) "\n"))
;;   (insert-string "Done.\n")
;;   (goto-char (point-min)))

(defun pmd-file-or-dir (target)
  "Run PMD on the given target (file or dir)"

  (let ((pmd-command
         (concat pmd-java-home " -jar " (pmd-jar) " "
                           target " emacs " (mapconcat (lambda (path) path) pmd-ruleset-list ","))))
    
    ;; Force save-some-buffers to use the minibuffer
    ;; to query user about whether to save modified buffers.
    (if (and (eq system-type 'windows-nt)
             (not pmd-xemacsp)) 
        (let ((temp last-nonmenu-event))
          ;; The next line makes emacs think that jde-jalopy
          ;; was invoked from the minibuffer, even when it
          ;; is actually invoked from the menu-bar.
          (setq last-nonmenu-event t)
          (save-some-buffers (not compilation-ask-about-save) nil)
          (setq last-nonmenu-event temp))
      (save-some-buffers (not compilation-ask-about-save) nil))
    (compile-internal pmd-command "No more errors" "PMD")))

(provide 'pmd)



