(defun java-get-import-region (import-regexp)
"Find the region containing the import statements and return a
cons of its start and end position."
(let ((import-regexp (if (equal (aref import-regexp 0) ?^)
import-regexp
(concat "^" import-regexp))))
(goto-char (point-max))
(if (not (re-search-backward import-regexp nil t))
()
(end-of-line)
(let ((end-of-imports (+ (point) 1)))
(goto-char (point-min))
(re-search-forward import-regexp)
(cons (match-beginning 0) end-of-imports)))))
(defun java-sort-imports (&optional no-message)
"Sort the import statements at the top of a java program. They are
grouped into three sets in the following order, those starting with
java, those starting with org, and those starting with com. Within
each set they are sorted alphabetically and blank lines are inserted
between packages."
(interactive)
(let* ( (import-regexp "\\s-*\\(//\\)?\\s-*import\\s-+")
(hoist-forward
(lambda (hoist-import hoist-point)
(goto-char (point-min))
(if (re-search-forward
(concat "^" import-regexp hoist-import)
nil
t)
(progn
(forward-line 0)
(let ((start-of-region (point)))
(next-line 1)
(while (looking-at
(concat import-regexp hoist-import))
(next-line 1))
(let ((copied-text (buffer-substring start-of-region
(point))))
(delete-region start-of-region
(point))
(goto-char hoist-point)
(insert copied-text)))))))
(sort-imports
(lambda (start-of-sort-region end-of-sort-region)
(save-restriction
(narrow-to-region start-of-sort-region
end-of-sort-region)
(goto-char (point-min))
(sort-subr
nil
(function (lambda ()
(next-line 1)
(forward-line 0)))
(function (lambda ()
(end-of-line)))
(function (lambda ()
(let ((here (point)))
(end-of-line)
(let ((limit (point)))
(goto-char here)
(if (re-search-forward (concat "^" import-regexp) limit t)
(buffer-substring (match-end 0) limit)
"")))))))))
(reformat-region
(lambda (start-of-sort-region end-of-sort-region)
(funcall sort-imports start-of-sort-region end-of-sort-region)
(goto-char start-of-sort-region)
(re-search-forward (concat "^" import-regexp))
(forward-line 0)
(delete-region start-of-sort-region (point))
(funcall hoist-forward "org"
start-of-sort-region)
(funcall hoist-forward "java"
start-of-sort-region)
(goto-char start-of-sort-region)
(let ((last-package "")
(this-package "")
(start-of-package 0))
(while (looking-at import-regexp)
(setq start-of-package (+ (match-end 0) 1))
(end-of-line)
(search-backward "." start-of-package)
(setq this-package
(buffer-substring
start-of-package (point)))
(if (not (equal last-package this-package))
(progn
(if (not (equal last-package ""))
(progn
(forward-line 0)
(newline)))
(setq last-package this-package)))
(next-line 1)
(forward-line 0))))))
(save-excursion
(let ((import-region (java-get-import-region import-regexp)))
(if (null import-region)
()
(let ( (start-of-sort-region (car import-region))
(end-of-sort-region (cdr import-region))
(old-buf (current-buffer))
(temp-buf (generate-new-buffer " sort-imports"))
(modified nil))
(set-buffer temp-buf)
(insert-buffer-substring old-buf start-of-sort-region
end-of-sort-region)
(funcall reformat-region (point-min) (point-max))
(if (equal
(compare-buffer-substrings
(current-buffer) (point-min) (point-max)
old-buf start-of-sort-region end-of-sort-region)
0)
(or no-message
(message "Already sorted. No changes made to buffer."))
(set-buffer old-buf)
(delete-region start-of-sort-region end-of-sort-region)
(goto-char start-of-sort-region)
(insert-buffer temp-buf)
(or no-message
(message "Sorting completed. Buffer has been modified"))
(setq modified t))
(kill-buffer temp-buf)
modified))))))
(defun java-check-imports (&optional no-message)
"Check the import statements in a program and comment out any where the class name
can not be found in the program. Can not deal with imports that end in * so they are
ignored. Returns t if the buffer was modified, nil otherwise."
(interactive)
(save-excursion
(let* ( (import-regexp "^\\s-*import\\s-+\\([^ ]+\\.\\([^;]+\\)\\);")
(import-region (java-get-import-region import-regexp)))
(if (null import-region)
nil
(let* ( (modified nil)
(last-package-name "")
(case-fold-search nil)
(start-of-region (car import-region))
(end-of-region (cdr import-region))
(comment-line (lambda (n)
(let ((pos (point)))
(forward-line n)
(if (not (looking-at "^\\s-*//"))
(progn
(insert "//")
(setq modified t)))
(goto-char pos))))
(find-non-commented
(lambda (class-name)
(if (re-search-forward
(concat "\\Sw\\(" class-name "\\)\\Sw")
(point-max) t)
(if (equal (get-text-property (match-beginning 1) 'face)
'font-lock-comment-face)
(funcall find-non-commented class-name)
t)
nil))))
(goto-char start-of-region)
(while (re-search-forward import-regexp end-of-region t)
(let ((package-name (match-string-no-properties 1))
(class-name (match-string-no-properties 2)))
(if (equal package-name last-package-name)
(funcall comment-line -1))
(if (not (equal class-name "*"))
(let ((pos (point)))
(goto-char end-of-region)
(let ((found (funcall find-non-commented class-name)))
(goto-char pos)
(if (not found)
(funcall comment-line 0)))))
(setq last-package-name package-name))
(forward-line 1))
(or no-message
(if modified
(message "Check completed. Buffer has been modified")
(message "Check completed. No changes made to buffer")))
modified)))))
(defun java-delete-commented-imports (&optional no-message)
"Delete any import statements that have been commented out by
java-check-imports."
(interactive)
(let ( (import-regexp "\\s-*//\\s-*import\\s-+[^ ]+\\.\\([^;]+\\);")
(modified nil)
(limit (point-max)))
(save-excursion
(goto-char (point-min))
(while (re-search-forward import-regexp limit t)
(forward-line -1)
(let ((blank-before (looking-at "^\\s-*$")))
(forward-line 1)
(let ((pos (point)))
(forward-line 1)
(if (and blank-before
(looking-at "^\\s-*$"))
(forward-line 1))
(delete-region (point) pos))
(setq modified t))))
(or no-message
(if modified
(message "Delete completed. Buffer has been modified")
(message "Delete completed. No changes made to buffer")))
modified))
(defun java-sort-and-check-imports ()
"Sort the import statements in the program and then check them to
comment out any that are not used."
(interactive)
(let ((modified (java-sort-imports t)))
(if (or (java-check-imports t)
modified)
(message "Buffer was modified")
(message "Buffer not modified"))))
(defvar java-log4j-statement
"logCat"
"*Name of the java variable holding the instance of
org.apache.log4j.Category that should be used for logging. Can also be
a method call that returns a Category. Must support isDebugEnabled and
debug methods.")
(defun java-insert-debug ()
"Insert statements to write a debugging message using log4j."
(interactive)
(end-of-line)
(newline-and-indent)
(insert "if (" java-log4j-statement ".isDebugEnabled())")
(newline-and-indent)
(insert java-log4j-statement ".debug(")
(java-insert-more-debug (read-string "Enter msg: " "") t)
(insert ");"))
(defun java-insert-more-debug (msg &optional firstTime)
"Insert an additional debugging message with a plus between it and
any previous messages."
(if (or (null msg)
(eq (length msg) 0))
()
(if (not firstTime)
(progn
(insert " + ")
(newline-and-indent)))
(insert msg)
(java-insert-more-debug (read-string "More msg or enter to end: " ""))))
(defvar java-extras-menu
(make-sparse-keymap "Extras")
"The menu that presents some extra java mode functionality")
(define-key java-extras-menu [delete-commented-imports]
'(menu-item "Delete Commented Imports" java-delete-commented-imports
:key-sequence nil
:help "Delete import statements that are commented out"))
(define-key java-extras-menu [sort-and-check-imports]
'(menu-item "Sort and Check" java-sort-and-check-imports
:key-sequence nil
:help "Sort the import statements and check for unused ones"))
(define-key java-extras-menu [check-imports]
'(menu-item "Check Imports" java-check-imports
:key-sequence nil
:help "Check for unused import statements"))
(define-key java-extras-menu [sort-imports]
'(menu-item "Sort Imports" java-sort-imports
:key-sequence nil
:help "Sort the import statements"))
(define-key java-extras-menu [debug-statement]
'(menu-item "Insert log4j" java-insert-debug
:key-sequence nil
:help "Insert a log4j debugging statement."))
(defun java-add-extras-menu ()
"Add the extras menu if it is not already present. Should normally
be invoked from java-mode-hook."
(let ((java-menu (lookup-key java-mode-map [menu-bar Java])))
(or (lookup-key java-menu [extras])
(define-key-after java-menu
[extras] (cons "Extras" java-extras-menu) t))))