;; @(#) jpack.el -- Java package statement generator
;; @(#) $Id: jde-package.el,v 1.1 2001/02/25 05:36:05 paulk Exp $

;; This file is not part of Emacs

;; Copyright (C) 1998, 2000, 2001 by David Ponce
;; Author:       David Ponce <david@dponce.com>
;; Maintainer:   David Ponce <david@dponce.com>
;;               Paul Kinnucan <paulk@mediaone.net>
;; Created:      September 28 1998

;; COPYRIGHT NOTICE
;;
;; 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., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.

;;; Description:
;;
;;  This package automatically generates a Java package statement.  
;;  The package name is deducted from the current classpath setting of JDE. When
;;  a directory found in classpath is a root of the current buffer default
;;  directory, the relative path of the default directory from the classpath one
;;  becomes the package name by substituting directory separators by '.'.
;;
;;  For example:
;;      My JDE classpath setting is ("~/java" "/jdk1.1.6/lib/classes.zip")
;;      I edit the file ~/java/FR/test/MyClass.java
;;      The package name generated will be `FR.test'.
;;
;;      My JDE classpath setting is ("~/java" "~/java/test" "/jdk1.1.6/lib/classes.zip")
;;      I edit the file ~/java/test/MyClass.java
;;      "// Default package used." will be generated because the default package can be used.
;;
;;      My JDE classpath setting is ("~/java" "/jdk1.1.6/lib/classes.zip")
;;      I edit the file /usr/java/MyClass.java
;;      No package name will be generated because the directory /usr/java is not
;;      accessible from classpath.

;;; Usage:
;;
;;  M-x `jde-package-update' to update the Java package statement or insert a new
;;                             at top of buffer.
;;  The function `jde-package-generate-package-statement' could be used in template to
;;  automatically generate the package statement.

;;; Customization:
;;
;;  M-x `jde-package-customize' to customize all the jde-package options.
;;
;;  The following variables could be set:
;;
;;  o `jde-package-load-hook'
;;         hook run when package has been loaded.
;;
;;  o `jde-package-package-comment'
;;         Java line comment appended to the generated package statement.
;;         Default to " // Generated package name"
;;
;;  o `jde-package-default-package-comment'
;;         Java line comment generated when the default package is used.
;;         Default to "// Default package used"
;;
;;  o `jde-package-search-classpath-variables'
;;         The list of variables where to search the current classpath list.
;;         Default to '(jde-compile-option-classpath jde-global-classpath)

;;; Support:
;;
;; Latest version of jde-package can be downloaded from: <http://www.dponce.com/>
;;
;; Any comments, suggestions, bug reports or upgrade requests are welcome.
;; Please send them to David Ponce at <david@dponce.com>
;;

;;; Code:


(defconst jde-package-unknown-package-name
  "*unknown*"
  "The string returned when a package name can't be generated.")

(defconst jde-package-package-regexp
  "package .*;.*$"
  "The regexp used to find the Java package statement.")

(defgroup jde-package nil
  "jde-package package customization"
  :group 'tools
  :prefix "jde-package-")

(defcustom jde-package-load-hook nil
  "*Hook run when package has been loaded."
  :group 'jde-package
  :type 'hook
  )

(defcustom jde-package-package-comment " // Generated package name"
  "*Java line comment appended to the generated package statement.
An empty string suppress the generation of this comment."
  :group 'jde-package
  :type 'string
  )

(defcustom jde-package-default-package-comment "// Default package used"
  "*Java line comment generated when the default package is used.
An empty string suppress the generation of this comment."
  :group 'jde-package
  :type 'string
  )

(defcustom jde-package-search-classpath-variables
  '(jde-compile-option-classpath jde-global-classpath)
  "*Specify the variables where to search the current classpath list.
The first one which has a non nil value will be used by jde-package."
  :group 'jde-package
  :type '(repeat variable)
  )

;;;###autoload
(defun jde-package-customize ()
  "Customization of the group jde-package."
  (interactive)
  (customize-group "jde-package"))


(defun jde-package-fullpath (path)
  "Returns the full path of the given path.
~ (HOME) and environment variables references are expanded."
  (expand-file-name (substitute-in-file-name path)))

(defun jde-package-get-classpath ()
  "Returns the current classpath list. That is to say the first non-nil value
found in the variables given by `jde-package-search-classpath-variables'."
  (let ((search-in jde-package-search-classpath-variables)
        (classpath))
    (catch 'jde-package-classpath
      (while search-in
        (setq classpath (symbol-value (car search-in)))
        (if classpath
            (throw 'jde-package-classpath classpath))
        (setq search-in (cdr search-in))))))

(defun jde-package-get-directories-in-classpath ()
  "Returns the list of directories found in classpath."
  (mapcan '(lambda (path)
             (unless (string= path ".") ; "." is ignored in classpath
               (let ((path (jde-package-fullpath path)))
                 (when (file-directory-p path)
                   (list (file-name-as-directory path))))))
          (jde-package-get-classpath)))

(defun jde-package-get-current-directory ()
  "Returns the full path of the current directory."
  (jde-package-fullpath default-directory))

(defun jde-package-seach-package-directories ()
  "Returns a list of package directory candidates or nil if none found."
  (let ((dir (jde-package-get-current-directory))
        (case-fold-search (eq system-type 'windows-nt))) ; case-insensitive for Windows
    (mapcan '(lambda (root)
               (let ((root (regexp-quote root)))
                 (message "Seaching %S in %S..." root dir)
                 (and (string-match root dir)
                      (list (substring dir (match-end 0))))))
            (jde-package-get-directories-in-classpath))))

(defun jde-package-get-package-directory ()
  "Returns the package directory found or `jde-package-unknown-package-name'
if none found."
  (or (jde-package-best-package-candidate (jde-package-seach-package-directories))
      jde-package-unknown-package-name))

(defun jde-package-best-package-candidate (candidates)
  "Returns the best package directory candidate from the given list of
package directories. The best is the shortest one that could be found."
  (car (sort candidates '(lambda (dir1 dir2) (string-match (regexp-quote dir1) dir2)))))

(defun jde-package-convert-directory-to-package (dir)
  "Converts the given directory path to a valid Java package name
by replacing `directory-sep-char' by '.' and removing extra
`directory-sep-char' at end."
  (if (string= dir "")
      ""
    (subst-char-in-string directory-sep-char ?.
                          (substring (file-name-as-directory dir) 0 -1)
                          t)))

;;;###autoload
(defun jde-package-generate-package-statement ()
  "Generates a Java package statement. If the package name cant be generated
this function returns an empty string."
  (let ((package-name (jde-package-get-package-directory)))
    (cond ((string= package-name jde-package-unknown-package-name)
           (message "The current directory is not accessible from classpath.")
           "")
          ((string= package-name "")
           (message "Default package used.")
           jde-package-default-package-comment)
          (t (message "package %s;%s"
                      (jde-package-convert-directory-to-package package-name)
                      jde-package-package-comment)))))

;;;###autoload
(defun jde-package-update ()
  "Updates or replaces the Java package statement in the current buffer.
If the package statement does not exist a new one is inserted at the
top of the buffer. If the package name cant be generated nothing is done.
The major mode of current buffer must be 'jde-mode'."
  (interactive)
  (if (eq major-mode 'jde-mode)
      (let ((package (jde-package-generate-package-statement)))
        (unless (string= package "")
          (goto-char (point-min))
          (if (re-search-forward jde-package-package-regexp nil t)
              (replace-match package)
            (progn
              (insert package)
              (newline)))))
    (message "Invalid major mode found. Must be 'jde-mode'.")))

(provide 'jde-package)
(run-hooks 'jde-package-load-hook)

;;; Change History:

;; $Log: jde-package.el,v $
;; Revision 1.1  2001/02/25 05:36:05  paulk
;; Initial XEmacs version.
;;
;; Revision 1.2  2001/02/22 03:36:30  paulk
;; Removed require for jde to fix infinite recursion loading bug.
;;
;; Revision 1.1  2001/02/21 05:52:02  paulk
;; Initial revision.
;;

;;
;; $Log: jde-package.el,v $
;; Revision 1.1  2001/02/25 05:36:05  paulk
;; Initial XEmacs version.
;;
;; Revision 1.2  2001/02/22 03:36:30  paulk
;; Removed require for jde to fix infinite recursion loading bug.
;;
;; Revision 1.1  2001/02/21 05:52:02  paulk
;; Initial revision.
;;

;;; jde-package.el ends here.
