>>>>> "Petter" == Petter Måhlén <[EMAIL PROTECTED]> writes:

  >> >>>>> "Petter" == Petter Måhlén <[EMAIL PROTECTED]> writes:
  >>
  Petter> Found another little nit to pick. I have a class named
  Petter> "Task", and when I run jde-import-all for that class, I am
  Petter> prompted to import some class named sun.something.Task. I am
  Petter> not enough of an elisp person to see how, but maybe
  Petter> jde-import-all-expand-strip-exclude should exclude the
  Petter> current class as well?
  >>
  >>
  >> The functions that actually do the import come from JDEE core
  >> itself rather than me. In this case the
  >> `jde-import-excluded-packages' variable should do what you want
  >> (exclude sun.* classes).
  >>
  >> The current class, and classes in the current package should
  >> already be excluded. If this isn't happening then its a bug but
  >> probably not mine.

  Petter> Aha, I saw it slightly differently: if I am presently
  Petter> working on a class called Task, and the jde-import-all finds
  Petter> an unqualified name of Task, it shouldn't bother trying to
  Petter> import it. That's why I suggested that the current class
  Petter> should be removed before even looking for classes to import.
  Petter> Anyway, I looked into it (and realised that maybe one of
  Petter> these days I am going to have to drop my claim of not
  Petter> knowing any elisp... terrible thought), and came up with the
  Petter> below possible solution, which seems to work as far as I can
  Petter> tell. Would that make sense to you?

It makes sense yes. I didn't bother because I think it gets caught
anyway. But I will add this change. 


  Petter> By the way, I am afraid I came up with another case that
  Petter> doesn't quite work, and which I think may be difficult to
  Petter> catch with a regexp: calling a static method in another
  Petter> class, like:

  Petter>   Logger.getLogger();

  Petter> Maybe a regexp that assumes that something that starts with
  Petter> a capital letter, followed by a dot is a class to be
  Petter> imported? A bit shaky to me.  Still, even without it, this
  Petter> is a command that I will be using a lot.


This is a known issue. I am debating with myself the logic of this
package. It might just make sense to look for all occurrences of words
beginning with capital letters not in comments. I can't think of any
way of identifying static calls short of this. 

Here is a new version of jde-import-all.el which at least checks for
return types. I've re-worked the bulk import removal also. 

Cheers

Phil



;;; jde-import-all.el --- Import everything at once.

;; Copyright (C) 2004 Phillip Lord

;; Author: Phillip Lord <[EMAIL PROTECTED]>

;; 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.

;;; Commentary:
;;
;; Build imports for all types in the buffer.  This package has two
;; entry points.  `jde-import-all' finds all declared types in the
;; buffer, then presents the user with a nice dialog which they can
;; then choose as many as they wish.  `jde-import-all-unambiguous'
;; finds all the declared types in the buffer, and then imports
;; without asking the user any imports which identify a class
;; unambigiously.
;;
;; There are not user options other than those in `jde-import' which
;; this respects.
;;
;; This package is really meant for rolling into jde-import.  There's
;; also a dialog near the end which uses the efc namespace, because it
;; probably belongs there.
;; 
;; This package uses regexp searching to find all the types, as the
;; semantic grammar doesn't appear to go deep enough for use
;; here.  Like other parts of JDE it depends on use of coding standards
;; (upper case for types).  It will miss types not conforming to this. 
;; 
;; There is also fledgling support for mass import removal. The
;; `jde-import-all-kill-extra-imports-in-project'


;;; Bugs:
;;
;; This does not correctly identifer InnerClasses which need to be
;; imported via their parent. Probably requires both lisp and
;; beanshell support.
;; 

;;; History:
;; 
;; - fixed bug with regexp, which failed to pick up "implements"
;; types, or multiple types after extends
;; 

;;; Code:


(require 'sregex)
(require 'jde-import)

(defun jde-import-all-regexp()
  "Get the regexp set to find all types."
  ;; I'm starting to have doubts about this regexp. Its very nice,
  ;; very large, and works. However it goes to lots of efforts to
  ;; discover types explicitly based on a parse of the buffer. It
  ;; would be a lot quicker and a lot easier to just look for words
  ;; not in comments, starting with a upper case, which should match
  ;; everything anyway. 

  ;; I guess I should weaken the requirement for an upper case first
  ;; letter where I can do so non ambigiously. This would mean that
  ;; where the coding standards had not been followed still some types
  ;; would be imported. This for the matches following "new" or
  ;; "extends" and so on. I couldn't do this for the cast regexp or
  ;; formal parameters (although the latter could be re-written), so
  ;; coding standards would be left there. This way this regexp would
  ;; actually be doing something useful. 
  (let ((white-nl
         '(1+ (or (syntax ?-) "\n")))
        (white-nl-0
         '(0+ (or (syntax ?-) "\n")))
        (non-identifier
         '(1+ wordchar))
        ;; match an upper case begining word, which we will take as a
        ;; type.
        (identifier
         '(group
           (char (?A . ?Z))
           (1+ wordchar))))
    (list
     (cons
      ;; match anything following instanceof, new, or extends. These
      ;; are keywords, so are guarenteed to be classes
      (sregex '(or "new" "instanceof" "throws")
              white-nl
              '(group (1+ wordchar)))
      1)
     (cons
      ;; match anything following extends or implements. This will
      ;; then be split on commas. Both extends and implements can
      ;; carry multiple Class names after them, as Interfaces can
      ;; extend multiple other interfaces.
      (sregex '(or "extends" "implements")
              white-nl-0
              `(group
                (1+ (or ,identifier "," (syntax ?-)))))
      'jde-import-match-comma-split)
     (cons
      ;; match anything in between parens and starting with upper
      ;; case. This picks up casts. This will also pick up things like
      ;; debugGraphics.setDebugOptions(FLASH_OPTIONS); where
      ;; FLASH_OPTIONS is actually a static. Generally this won't be a
      ;; problem because it shouldn't resolve as a class. `
      (sregex '(char ?( )
                     identifier
                     '(char ?)))
      1)
     ;; match an .class type thing..
     (cons
      (sregex identifier ".class")
      1)
     ;; match variable declaration
     (cons
      (sregex white-nl
              identifier
              white-nl
              non-identifier
              white-nl-0
              '(or ";" "="))
      1)
     (cons 
      ;; match return types. 
      (sregex
       ;; one or more of these identifiers with a space
       `(1+
         (or "public" "private" "abstract" "final" 
             "static" "strictfp" "protected")
         ,white-nl)
       ;; followed by a type
       `(group ,identifier)
       ;; followed by a method name
       white-nl
       non-identifier
       ;; followed by a brace
       "(")
      1)
     ;; match formal parameters
     (cons
      (sregex '(or "(" ",")
              white-nl-0
              identifier
              white-nl
              non-identifier
              white-nl-0
              '(or "," ")"))
      1)
     )))

(defun jde-import-all-get-types()
  "Return all the types in the current buffer.

The result is a list with no duplicates."
  (let ((elements))
    (mapcar
     (lambda(matcher)
       (let ((matches
              (jde-import-all-gather matcher)))
         (mapcar
          (lambda(match)
            (add-to-list 'elements match))
          matches)))
     (jde-import-all-regexp))
    elements))

(defun jde-import-all-gather(matcher)
  "Get all matches in the current buffer.

MATCHER is a cons (regexp . group). Looks for matches to the regexp
and stores the match in the group. Only matches not in strings are
accepted. This list can contain duplicates."
  (let ((regexp (car matcher))
        (group (cdr matcher))
        (case-fold-search nil)
        (retn))
    (save-excursion
      (goto-char (point-min))
      (while (re-search-forward
              regexp
              (point-max) t)
        (setq retn
              (append
               (if (numberp group)
                   (jde-import-default-match group)
                 (funcall group))
               retn))))
    retn))
  
(defun jde-import-default-match(group)
  (goto-char (match-end group))
  (unless
      (jde-parse-comment-or-quoted-p)
    (list
     (match-string-no-properties group))))

(defun jde-import-match-comma-split()
  (goto-char (match-end 1))
  (unless
      (jde-parse-comment-or-quoted-p)
    (split-string
     (match-string-no-properties 1)
     "[ ,]+")))

(defun jde-import-all-import-types(list)
  "Import all types.

Accepts a list of Strings of unqualified classes"
  ;; we have a list of strings
  (setq list
        (jde-import-all-expand-strip-exclude list))
  ;; so we now have a list of lists of strings. We want to over a
  ;; dialog showing all of these one after the other..
  (let ((dialog
         (jde-import-all-dialog
          "Multi Classes Option"
          :options list
          :text "Select imports to insert."))
        (selection))
    (efc-dialog-show dialog)
    (setq selection
          (delq nil (oref dialog selection)))
    (if (and selection
             (< 0 (length selection)))
        (jde-import-insert-imports-into-buffer selection))))
        
(defun jde-import-all-expand-strip-exclude(list)
  "Qualifies, strips and excludes imports.

This function takes in a list of imports. It removes all the existing
imports, qualifies them and then strips excluded imports. It returns a
list of lists of strings. And all in one function."
  (delq nil
        (mapcar
         (lambda(unqualified-class)
           ;; we want to remove any imports which already exists
           (if (jde-import-get-existing-import unqualified-class)
               nil
             (jde-import-exclude-imports
              ;; we want to kill of the java.lang
              ;; packges. exclude-imports will probably do this for
              ;; us. But this is not good as it will leave the other
              ;; possibilities. So "String" will be imported as
              ;; "apache.xpath.String" (on my machine) which is almost
              ;; exactly the wrong behaviour
              ;;
              ;; I'm not really happy with this but what can you do.
              (jde-import-all-kill-lang-classes
              ;; we want to expand any unqualified names, this returns
               ;; a list of string possibilities
               (jde-jeval-r
                (concat  "jde.util.JdeUtilities.getQualifiedName(\""
                         unqualified-class "\");"))))))
         list)))

(defun jde-import-all-kill-lang-classes(list)
  (let ((ismatch nil))
    (mapc
     (lambda(item)
       (if (string-match "^java\\.lang\\.[^.]*$" item)
           (setq ismatch t)))
     list)
    (if ismatch
        nil
      list)))

(defun jde-import-all-unambiguous()
  "Import all unambigious imports.

This function imports all imports for which the name identifies a
unique class in the classpath. This avoids presenting the user with a
large dialog, which can take a while to build if there are many
imports."
  (interactive)
  (let ((list
         (jde-import-all-expand-strip-exclude (jde-import-all-get-types)))
        (retn))
    (delq nil
          ;; take single length sublists, and return item..
          (mapcar
           (lambda(item)
             (if (= 1 (length item))
                 (add-to-list 'retn
                              (car item))))
           list))
    (if (< 0 (length retn))
        (jde-import-insert-imports-into-buffer retn))))
                   

(defun jde-import-all()
  "Attempt to import all types in the current buffer.

The user will be prompted with an interactive dialog, prompting
for alternatives."
  (interactive)
  (jde-import-all-import-types
   (jde-import-all-get-types)))



;; define a nifty option dialog for asking for multiple options.
;; I've never used eieio before. Very cute...
(defclass efc-multi-option-dialog (efc-option-dialog)
  ((options :initarg :options
            :documentation
            "Option of options from which to choose")
   (radio-buttons :initarg :radio-buttons
                  :documentation
                  "Buttons for selecting options")
   (text :initarg :text
         :type string
         :initform "Select option."
         :documentation
         "Text at top")
   (build-message :initarg :text
                  :type string
                  :initform "Building Dialog"
                  :documentation
                  "Warning message while building dialog, as this can be slow")
   (selection :initarg :selection
              :documentation
              "Options choosen."))
  "Provides a dialog with several sets of OPTIONS.
The dialog sets SELECTION to the options selected by the user.")

(defmethod initialize-instance ((this efc-multi-option-dialog) &rest fields)
  "Dialog constructor."
  (call-next-method))

(defmethod efc-dialog-create ((this efc-multi-option-dialog))
  (message "%s..." (oref this build-message))
  (widget-insert (oref this text))
  (widget-insert "\n\n")
  ;; use radio buttons slot as list of radio buttons rather than.
  (oset this radio-buttons
        (mapcar
         (lambda(list)
           (prog1
               (widget-create
                (list
                 'radio-button-choice
                 :value
                 (efc-multi-option-dialog-default this list)
                 :args (mapcar
                        (lambda (x)
                          (list 'item x))
                        list)))
             (widget-insert "\n")))
         (efc-multi-option-dialog-sort this
                                       (oref this options))))
  (widget-insert "\n")
  (message "%s...done" (oref this text)))

(defmethod efc-dialog-ok((this efc-multi-option-dialog))
  ;; set the selection up as a list rather a simple result
  (oset this selection
        (mapcar
         (lambda(widget)
           (widget-value widget))
         (oref this radio-buttons)))
  (delete-window)
  (set-buffer (oref this initbuf))
  (pop-to-buffer (oref this initbuf))
  (kill-buffer (oref this buf))
  (exit-recursive-edit))

(defmethod efc-multi-option-dialog-default ((this efc-multi-option-dialog) list)
  "Pick the default from a collection of options."
  (if (= 1 (length list))
      (car list)))

(defmethod efc-multi-option-dialog-sort ((this efc-multi-option-dialog) list)
  "Sort the options."
  ;; sort the ones with the most options first...
  (sort list
        (lambda(a b)
          (> (length a)
             (length b)))))


;; define a more specific dialog, which searches things differently.

(defclass jde-import-all-dialog(efc-multi-option-dialog) nil)

(defmethod initialize-instance ((this jde-import-all-dialog) &rest fields)
  "Dialog constructor."
  (call-next-method))

(defmethod efc-multi-option-dialog-sort ((this jde-import-all-dialog) list)
  "Sort the options."
  ;; sort the ones with the most options first...
  (sort list
        (lambda(a b)
          ;; sort lexically
          (if (= (length a)
                 (length b))
              (string< (car a)
                       (car b))
            ;; or by length
            (> (length a)
               (length b))))))


;;; code to kill lots of extra imports in many different buffers. This
 ;;; is a bit of a hack at the moment. 
(defun jde-import-all-search-path()
  "Find source root directories.
This code is chopped straight from `jde-find' which is a little
unfortunate."
  (read-from-minibuffer 
   "Search directories: "
   (cons 
    (mapconcat 
     (lambda (x) x)
     (cond
      (jde-sourcepath
       (mapcar 
        (lambda (path)
          (jde-normalize-path path 'jde-sourcepath))
        jde-sourcepath))
      (jde-compile-option-sourcepath 
       (mapcar 
        (lambda (path)
          (jde-normalize-path path 'jde-compile-option-sourcepath))
        jde-compile-option-sourcepath))
      (jde-compile-option-classpath 
       (mapcar 
        (lambda (path)
          (jde-normalize-path path 'jde-compile-option-classpath))
        jde-compile-option-classpath))
      (jde-global-classpath 
       (mapcar 
        (lambda (path)
          (jde-normalize-path path 'jde-global-classpath))
        jde-global-classpath))
      (t
       (list default-directory)))       
     " ")
    0)
   nil nil 'jde-find-root-history))

(defun jde-import-all-find-internal (buffer &optional dirs)
  "Actually find files."
  (if (not (executable-find 
            (if (eq system-type 'windows-nt) "find.exe" "find")))
      (error (list "This command requires the Unix find utility.")))
  (let* ((directories-option
          (if dirs dirs "."))
         (cmd 
          (format "find %s -type f -name \"%s\""
                  directories-option
                  (car jde-find-file-regexp))))
    (shell-command cmd buffer)
    (bury-buffer buffer)))

(put 'jde-import-all-kill-extra-imports-in-project 'disabled
     "It is not possible to undo this action")

(defun jde-import-all-kill-extra-imports-in-project()
  "Kill extraneous imports in many buffers.

This function uses the unix find command to discover all java files 
in the current project and then removes all extranous import statements
from them. 

This function is a little bit scary and may not behave correctly.
Even if it does it can't be undone. And even if you don't want to
undo it, its user interface is very poor as it's very noisy telling
you lots of things you don't want to know, and nothing that you 
actually want to know. In short its a hack.
"
  (interactive)
  (save-some-buffers)
  (with-temp-buffer
    (let ((files-buffer (current-buffer))
          (current-buffers (buffer-list))
          (total-files)
          (onfile 0))
      (message "Discovering files...")
      (jde-import-all-find-internal
       files-buffer
       (jde-import-all-search-path))
      (message "Discovering files...done")
      (setq total-files 
            (count-lines (point-min) (point-max)))
      (jde-log-msg "Starting mass import delete")
      (dolist (file (split-string (buffer-string)))
        (message "On %s from %s file %s"
                 (incf onfile) total-files
                 file)
        (jde-import-all-kill-imports file current-buffers)))))

(defun jde-import-all-kill-imports(file current-buffers)
  (let ((file-buffer (find-file-noselect file)))
    (save-excursion
      (set-buffer file-buffer)
      (jde-import-all-kill-extra-imports)
      (save-buffer)
      (if (not (member file-buffer current-buffers))
          (kill-buffer (current-buffer))))))

(defun jde-import-all-kill-extra-imports()
  "Kill extra imports in the buffer.

This function kills extra imports in the current buffer. This has
similar functionality to `jde-import-kill-extra-imports'. However this
function is designed to have no false negative kills at all, making is
suitable therefore for a mass removal function. It's also meant to be
fast, so it does no beanshell look ups. It also logs to the jde-log
buffer, which is essential for automated usage.

The basic logic of this function is to find all imports, keep all
\".*\" imports. Then for all fully qualified name imports it gets the
type that they are importing, and then simply looks forward for the
name of that type. Any occurrence of that type which is not commented
or quoted and the import is kept.
"
  (interactive)
  (let ((imports 
         (semantic-find-nonterminal-by-token 
          'include
          (semantic-bovinate-toplevel t))))
    (when imports
      (save-excursion
      ;; move to the end of the import list...
      (goto-char (semantic-token-end 
                  (car (last imports))))
      (mapcar 
       (lambda(import)
         (save-excursion
           (let ((import-name 
                  (semantic-token-name import))
                 (class-name))
             (unless
                 (string-match "[*]" import-name)
               (setq class-name
                     (car
                      (last (split-string import-name "[.]"))))
               (unless
                   (jde-import-all-type-used class-name)
                 (jde-import-all-delete-import
                              import))))))
       imports)))))

(defun jde-import-all-type-used(class-name)
  (let ((found nil))
    (while
        (and 
         (not found)
         (re-search-forward
          (sregex
           'not-wordchar
           class-name
           'not-wordchar)
          (point-max)
          t))
      (setq found
            (not (jde-parse-comment-or-quoted-p))))
    found))
  
(defun jde-import-all-delete-import(token)
  (jde-import-all-delete-log token)
  (save-excursion
    (goto-char (semantic-token-start token))
    (let ((from (point))
          (to (progn
                (next-line 1)
                (line-beginning-position))))
      (kill-region from to))))

(defun jde-import-all-delete-log(token)
  (jde-log-msg "jde-import-all: Removing %s from %s "
               (semantic-token-name token)
               (buffer-file-name)))


;; test functions...
(defun jde-import-find-all-java()
  (interactive)
  (jde-import-all-find-internal
   (jde-import-all-search-path)))

(defun jde-import-all-search-path-test()
  (interactive)
  (message "search path is %s" 
           (jde-import-all-search-path)))


(defun jde-import-all-show-gather()
  "Show all of the type declarations shown in a special buffer"
  (interactive)
  (save-excursion
    (set-buffer (get-buffer-create "*jde import*"))
    (erase-buffer))
  (mapcar
   (lambda(matcher)
     (let ((matches
            (jde-import-all-gather matcher)))
       (save-excursion
         (set-buffer "*jde import*")
         (insert (format "Matching %s" matcher))
         (insert "\n")
         (mapcar
          (lambda(match)
            (insert (concat match "\n")))
          matches))))
   (jde-import-all-regexp))
  (switch-to-buffer-other-window "*jde import*"))

(defun jde-import-all-show()
  "Show all of the type declarations"
  (interactive)
  (let ((import
         (jde-import-all-get-types)))
    (save-excursion
      (set-buffer (get-buffer-create "*jde import*"))
      (erase-buffer)
      (mapcar
       (lambda(match)
         (insert match)
         (insert "\n"))
       import))))

(provide 'jde-import-all)

;;; jde-import-all.el ends here

Reply via email to