]> code.delx.au - gnu-emacs-elpa/blobdiff - packages/javaimp/javaimp.el
Fix some quoting problems in doc strings
[gnu-emacs-elpa] / packages / javaimp / javaimp.el
index f44be4e9f9b534d2459263da76f2eed3454a6406..d1c68657d898689f0a18dbabc73e3c226d0d4305 100644 (file)
 ;; `javaimp-debug-buf-name' (default is "*javaimp-debug*").
 ;;
 ;; Contents of jar files and Maven project structures (pom.xml) are cached,
-;; so usually only first command should take a considerable amount of time
-;; to complete.  If a modules's pom.xml or any of its parents' pom.xml was
-;; changed (i.e. any of them was modified after information was loaded),
-;; `mvn dependency:build-classpath' is re-run on the current module.  If a
-;; jar file was changed, its contents are re-read.
-;;
-;; If you make some changes which change project hierarchy, you should
-;; re-visit the parent again with `javaimp-maven-visit-project'.
+;; so usually only the first command should take a considerable amount of
+;; time to complete.  If a module's pom.xml or any of its parents' pom.xml
+;; (within visited tree) was modified after information was loaded, `mvn
+;; dependency:build-classpath' is re-run on the current module.  If a jar
+;; file was changed, its contents are re-read.
 ;;
 ;; Currently inner classes are filtered out from completion alternatives.
 ;; You can always import top-level class and use qualified name.
 ;;
 ;; - use functions `cygwin-convert-file-name-from-windows' and
 ;; `cygwin-convert-file-name-to-windows' when they are available instead of
-;; calling `cygpath'.  See https://cygwin.com/ml/cygwin/2013-03/msg00228.html.
-;; - save/restore state
+;; calling `cygpath'.  See https://cygwin.com/ml/cygwin/2013-03/msg00228.html
+;;
+;; - save/restore state, on restore check if a root exists and delete it if
+;; not
+;;
 ;; - `javaimp-add-import': without prefix arg narrow alternatives by local name;
 ;; with prefix arg include all classes in alternatives
-;; - types for defcustom
+;;
+;; - :type for defcustom
 
 ;;; Code:
 
 ;; User options
 
 (defgroup javaimp ()
-  "Add and reorder Java import statements in Maven projects")
+  "Add and reorder Java import statements in Maven projects"
+  :group 'c)
 
 (defcustom javaimp-import-group-alist '(("\\`javax?\\." . 10))
   "Specifies how to group classes and how to order resulting
 groups in the imports list.
 
-Each element should be of the form `(CLASSNAME-REGEXP . ORDER)'
-where `CLASSNAME-REGEXP' is a regexp matching the fully qualified
+Each element should be of the form (CLASSNAME-REGEXP . ORDER)
+where CLASSNAME-REGEXP is a regexp matching the fully qualified
 class name.  Lowest-order groups are placed earlier.
 
 The order of classes which were not matched is defined by
@@ -94,9 +96,10 @@ The order of classes which were not matched is defined by
   "Defines the order of classes which were not matched by
 `javaimp-import-group-alist'")
 
-(defcustom javaimp-jdk-home (getenv "JAVA_HOME")
-  "Path to the JDK.  It is used to find JDK jars to scan.  By
-default, it is set from the JAVA_HOME environment variable.")
+(defcustom javaimp-java-home (getenv "JAVA_HOME")
+  "Path to the JDK.  Directory jre/lib underneath this path is
+searched for JDK libraries.  By default, it is initialized from
+the JAVA_HOME environment variable.")
 
 (defcustom javaimp-additional-source-dirs nil
   "List of directories where additional (e.g. generated)
@@ -138,7 +141,7 @@ to the completion alternatives list.")
 ;; Variables and constants
 
 (defvar javaimp-project-forest nil
-  "Visited projects.")
+  "Visited projects")
 
 (defvar javaimp-cached-jars nil
   "Alist of cached jars.  Each element is of the form (FILE
@@ -189,12 +192,13 @@ to the completion alternatives list.")
   (nth 5 (file-attributes file)))
 
 (defun javaimp--get-jdk-jars ()
-  (if javaimp-jdk-home
-      (let ((jre-lib-dir
-            (concat (file-name-as-directory javaimp-jdk-home)
-                    (file-name-as-directory "jre")
-                    (file-name-as-directory "lib"))))
-       (directory-files jre-lib-dir t "\\.jar\\'"))))
+  (and javaimp-java-home
+       (file-accessible-directory-p javaimp-java-home)
+       (let ((lib-dir
+             (concat (file-name-as-directory javaimp-java-home)
+                     (file-name-as-directory "jre")
+                     (file-name-as-directory "lib"))))
+        (directory-files lib-dir t "\\.jar\\'"))))
 
 (defun javaimp-cygpath-convert-maybe (path &optional mode is-really-path)
   "On Cygwin, converts PATH using cygpath according to MODE and
@@ -230,7 +234,7 @@ to which modules and other module information.
 After being processed by this command, the module tree becomes
 known to javaimp and `javaimp-add-import' maybe called inside any
 module file."
-  (interactive "DVisit maven project: ")
+  (interactive "DVisit maven project in directory: ")
   (let ((file (expand-file-name
               (concat (file-name-as-directory path) "pom.xml"))))
     (unless (file-readable-p file)
@@ -241,7 +245,14 @@ module file."
                        (equal (javaimp-module-file (javaimp-node-contents tree))
                               file))
                      javaimp-project-forest))
-    (let ((tree (javaimp--maven-xml-load-tree file)))
+    (message "Loading file %s..." file)
+    (let* ((xml-tree
+           (javaimp--maven-call file "help:effective-pom"
+                                #'javaimp--maven-xml-effective-pom-handler))
+          (projects (javaimp--maven-xml-extract-projects xml-tree))
+          (modules (mapcar #'javaimp--maven-xml-parse-project projects))
+          ;; first module is always root
+          (tree (javaimp--maven-build-tree (car modules) nil modules file)))
       (if tree
          (push tree javaimp-project-forest)))
     (message "Loaded tree for %s" file)))
@@ -249,71 +260,46 @@ module file."
 \f
 ;; Maven XML routines
 
-(defun javaimp--maven-xml-load-tree (file)
-  "Invokes `mvn help:effective-pom' on FILE and using its output
-creates a tree of Maven projects starting from FILE.  Children
-which link to the parent via the <parent> element are inheriting
-children and are also included.  Subordinate modules with no
-inheritance are not included."
-  (let ((xml-tree (javaimp--maven-xml-read-effective-pom file)))
-    (cond ((assq 'project xml-tree)
-          (let ((project-elt (assq 'project xml-tree))
-                (submodules (javaimp--xml-children
-                             (javaimp--xml-child 'modules project-elt) 'module)))
-            (and submodules
-                 ;; no real children
-                 (message "Independent submodules: %s"
-                          (mapconcat #'javaimp--xml-first-child submodules ", ")))
-            (let ((module (javaimp--maven-xml-parse-module project-elt)))
-              (javaimp--maven-build-tree
-               (javaimp-module-id module) nil (list module) file))))
-         ((assq 'projects xml-tree)
-          ;; we have are inheriting children - they and their children, if
-          ;; any, are listed in a linear list
-          (let* ((project-elts (javaimp--xml-children
-                                (assq 'projects xml-tree) 'project))
-                 (all-modules (mapcar #'javaimp--maven-xml-parse-module project-elts)))
-            (message "Total modules: %d" (length all-modules))
-            (javaimp--maven-build-tree
-             (javaimp-module-id (car all-modules)) nil all-modules file)))
+(defun javaimp--maven-xml-effective-pom-handler ()
+  (let ((start
+        (save-excursion
+          (progn
+            (goto-char (point-min))
+            (re-search-forward "<\\?xml\\|<projects?")
+            (match-beginning 0))))
+       (end
+        (save-excursion
+          (progn
+            (goto-char (point-min))
+            (re-search-forward "<\\(projects?\\)")
+            ;; corresponding close tag is the end of parse region
+            (search-forward (concat "</" (match-string 1) ">"))
+            (match-end 0)))))
+    (xml-parse-region start end)))
+
+(defun javaimp--maven-xml-extract-projects (xml-tree)
+  "Analyzes result of `mvn help:effective-pom' and returns list
+of <project> elements"
+  (let ((project (assq 'project xml-tree))
+       (projects (assq 'projects xml-tree)))
+    (cond (project
+          (list project))
+         (projects
+          (javaimp--xml-children projects 'project))
          (t
-          ;; neither <project> nor <projects> - error
-          (error "Invalid `help:effective-pom' output")))))
-
-(defun javaimp--maven-xml-read-effective-pom (pom)
-  "Calls `mvn help:effective:pom and returns XML parse tree"
-  (message "Loading root pom %s..." pom)
-  (javaimp--maven-call
-   pom "help:effective-pom"
-   (lambda ()
-     (let ((xml-start-pos
-           (save-excursion
-             (progn
-               (goto-char (point-min))
-               (re-search-forward "<\\?xml\\|<projects?")
-               (match-beginning 0))))
-          (xml-end-pos
-           (save-excursion
-             (progn
-               (goto-char (point-min))
-               (re-search-forward "<\\(projects?\\)")
-               ;; corresponding closing tag is the end of parse region
-               (search-forward (concat "</" (match-string 1) ">"))
-               (match-end 0)))))
-       (xml-parse-region xml-start-pos xml-end-pos)))))
-
-(defun javaimp--maven-xml-parse-module (project-elt)
-  (let ((build-elt (javaimp--xml-child 'build project-elt)))
+          (error "Neither <project> nor <projects> was found in pom")))))
+
+(defun javaimp--maven-xml-parse-project (project)
+  (let ((build-elt (javaimp--xml-child 'build project)))
     (make-javaimp-module
-     :id (javaimp--maven-xml-extract-id project-elt)
-     :parent-id (javaimp--maven-xml-extract-id (javaimp--xml-child 'parent project-elt))
-     ;; we set `file' slot later because raw <project> element does not contain
-     ;; pom file path, so we need to construct it during tree construction
+     :id (javaimp--maven-xml-extract-id project)
+     :parent-id (javaimp--maven-xml-extract-id (javaimp--xml-child 'parent project))
+     ;; <project> element does not contain pom file path (we set :file slot later)
      :file nil
      :final-name (javaimp--xml-first-child
                  (javaimp--xml-child 'finalName build-elt))
      :packaging (javaimp--xml-first-child
-                (javaimp--xml-child 'packaging project-elt))
+                (javaimp--xml-child 'packaging project))
      :source-dir (file-name-as-directory
                  (javaimp-cygpath-convert-maybe
                   (javaimp--xml-first-child
@@ -327,7 +313,7 @@ inheritance are not included."
                  (javaimp--xml-first-child (javaimp--xml-child 'directory build-elt))))
      :modules (mapcar (lambda (module-elt)
                        (javaimp--xml-first-child module-elt))
-                     (javaimp--xml-children (javaimp--xml-child 'modules project-elt) 'module))
+                     (javaimp--xml-children (javaimp--xml-child 'modules project) 'module))
      :dep-jars nil                   ; dep-jars is initialized lazily on demand
      :load-ts (current-time))))
 
@@ -381,41 +367,42 @@ the temporary buffer and returns its result"
       (goto-char (point-min))
       (funcall handler))))
 
-(defun javaimp--maven-build-tree (id parent-node all-modules file)
-  (message "Building tree for project: %s" id)
-  (let ((this (or (seq-find (lambda (m) (equal (javaimp-module-id m) id))
-                           all-modules)
-                 (error "Cannot find module %s!" id)))
-       ;; although each real parent has <modules> section, more reliable
-       ;; way to build hirarchy is to analyze <parent> node in each child
-       (children (seq-filter (lambda (m) (equal (javaimp-module-parent-id m) id))
-                             all-modules)))
+(defun javaimp--maven-build-tree (this parent-node all file)
+  (message "Building tree for module: %s" (javaimp-module-id this))
+  (let ((children
+        ;; reliable way to find children is to look for modules with "this" as
+        ;; the parent
+        (seq-filter (lambda (m) (equal (javaimp-module-parent-id m)
+                                       (javaimp-module-id this)))
+                             all)))
     (if (and (null children)
-            (string= (javaimp-module-packaging this) "pom"))
-       (progn (message "Skipping empty aggregate module %s" (javaimp-module-id this))
+            (equal (javaimp-module-packaging this) "pom"))
+       (progn (message "Skipping empty aggregate module: %s" (javaimp-module-id this))
               nil)
-      ;; here we can finally set the `file' slot as the path is known at
-      ;; this time
+      ;; filepath was not set before, but now we know it
       (setf (javaimp-module-file this) file)
-      ;; make node
-      (let ((this-node (make-javaimp-node
+      ;; node
+      (let* ((this-node (make-javaimp-node
                        :parent parent-node
                        :children nil
-                       :contents this)))
-       (setf (javaimp-node-children this-node)
+                       :contents this))
+            ;; recursively build child nodes
+            (child-nodes
              (mapcar (lambda (child)
                        (let ((child-file
+                              ;; !! this is hack
                               (javaimp--maven-get-submodule-file
                                child file (javaimp-module-modules this))))
                          (javaimp--maven-build-tree
-                          (javaimp-module-id child) this-node all-modules child-file)))
-                     children))
+                          child this-node all child-file)))
+                     children)))
+       (setf (javaimp-node-children this-node) child-nodes)
        this-node))))
 
 (defun javaimp--maven-get-submodule-file (submodule parent-file rel-paths-from-parent)
-  ;; seems that the only reliable way to match a module parsed from
-  ;; <project> element with module relative path taken from <modules> is to
-  ;; visit pom and check that id and parent-id matches
+  ;; Seems that the only reliable way to match a module parsed from <project>
+  ;; element with module relative path taken from <modules> is to visit pom and
+  ;; check that id and parent-id matches
   (let* ((parent-dir (file-name-directory parent-file))
         (files (mapcar (lambda (rel-path)
                          (concat parent-dir
@@ -433,41 +420,42 @@ the temporary buffer and returns its result"
 ;;; Loading dep-jars
 
 (defun javaimp--maven-update-module-maybe (node)
-  (let (need-update)
-    ;; are deps not initialized?
-    (let ((module (javaimp-node-contents node)))
-      (if (null (javaimp-module-dep-jars module))
-         (setq need-update t)))
-    ;; were any pom.xml files updated after last load?
+  (let ((module (javaimp-node-contents node))
+       need-update)
+    ;; check if deps are initialized
+    (or (javaimp-module-dep-jars module)
+       (progn (message "Loading dependencies: %s" (javaimp-module-id module))
+              (setq need-update t)))
+    ;; check if any pom up to the top one has changed
     (let ((tmp node))
       (while (and tmp
                  (not need-update))
-       (let ((module (javaimp-node-contents tmp)))
-         (if (> (float-time (javaimp--get-file-ts (javaimp-module-file module)))
+       (let ((checked (javaimp-node-contents tmp)))
+         (if (> (float-time (javaimp--get-file-ts (javaimp-module-file checked)))
                 (float-time (javaimp-module-load-ts module)))
-             (setq need-update t)))
+             (progn
+               (message "Reloading %s (pom changed)" (javaimp-module-id checked))
+               (setq need-update t))))
        (setq tmp (javaimp-node-parent tmp))))
     (when need-update
-      ;; update current module
-      (let ((module (javaimp-node-contents node)))
-       ;; reload & update dep-jars
-       (setf (javaimp-module-dep-jars module)
-             (javaimp--maven-fetch-dep-jars module))
-       ;; update load-ts
-       (setf (javaimp-module-load-ts module) (current-time))))))
+      (let* ((new-dep-jars (javaimp--maven-fetch-dep-jars module))
+            (new-load-ts (current-time)))
+       (setf (javaimp-module-dep-jars module) new-dep-jars)
+       (setf (javaimp-module-load-ts module) new-load-ts)))))
 
 (defun javaimp--maven-fetch-dep-jars (module)
-  (let ((raw-line
-        (javaimp--maven-call
-         (javaimp-module-file module) "dependency:build-classpath"
-         (lambda ()
-           (goto-char (point-min))
-           (search-forward "Dependencies classpath:")
-           (forward-line 1)
-           (thing-at-point 'line))))
-       (separator-regex (concat "[" path-separator "\n" "]+")))
-    (split-string (javaimp-cygpath-convert-maybe raw-line 'unix t) separator-regex t)))
-
+  (let* ((path (javaimp--maven-call (javaimp-module-file module)
+                                   "dependency:build-classpath"
+                                   #'javaimp--maven-build-classpath-handler))
+        (converted-path (javaimp-cygpath-convert-maybe path 'unix t))
+        (path-separator-regex (concat "[" path-separator "\n" "]+")))
+    (split-string converted-path path-separator-regex t)))
+
+(defun javaimp--maven-build-classpath-handler ()
+  (goto-char (point-min))
+  (search-forward "Dependencies classpath:")
+  (forward-line 1)
+  (thing-at-point 'line))
 
 \f
 ;; Working with jar classes
@@ -568,7 +556,7 @@ the temporary buffer and returns its result"
 asks for a class to import, adds import statement and calls
 `javaimp-organize-imports'.  Import statements are not
 duplicated.  Completion alternatives are constructed based on
-this module's dependencies' classes, jdk classes and top-level
+this module's dependencies' classes, JDK classes and top-level
 classes in the current module."
   (interactive
    (progn
@@ -643,18 +631,22 @@ classes in the current module."
 (defun javaimp-organize-imports (&rest new-imports)
   "Groups import statements according to the value of
 `javaimp-import-group-alist' (which see) and prints resulting
-groups leaving one blank line in between.
+groups leaving one blank line between groups.
 
-Classes within a single group are ordered in a lexicographic
-order.
+If the file already contains some import statements, this command
+rewrites them, starting with the same place.  Else, if the the
+file contains package directive, this command inserts one blank
+line below and then imports.  Otherwise, imports are inserted at
+the beginning of buffer.
 
-Imports not matched by any regexp in `javaimp-import-group-alist'
+Classes within a single group are ordered in a lexicographic
+order.  Imports not matched by any regexp in `javaimp-import-group-alist'
 are assigned a default order defined by
 `javaimp-import-default-order'.
 
 NEW-IMPORTS is a list of additional imports; each element should
 be of the form (CLASS . TYPE), where CLASS is a string and TYPE
-is `'ordinary' or `'static'.  Interactively, NEW-IMPORTS is nil."
+is `ordinary' or `static'.  Interactively, NEW-IMPORTS is nil."
   (interactive)
   (barf-if-buffer-read-only)
   (save-excursion
@@ -673,9 +665,10 @@ is `'ordinary' or `'static'.  Interactively, NEW-IMPORTS is nil."
                  (delete-region first (point))))
            (javaimp--prepare-for-insertion first)
            (setq all-imports
-                 (delete-duplicates all-imports
-                                    :test (lambda (first second)
-                                            (equal (car first) (car second)))))
+                 (cl-delete-duplicates
+                   all-imports
+                   :test (lambda (first second)
+                           (equal (car first) (car second)))))
            ;; assign order
            (let ((with-order
                   (mapcar
@@ -709,8 +702,8 @@ is `'ordinary' or `'static'.  Interactively, NEW-IMPORTS is nil."
         ;; if there were any imports, we start inserting at the same place
         (goto-char start))
        ((re-search-forward "^\\s-*package\\s-" nil t)
-        ;; if there's a package directive, move to the next line, creating it
-        ;; if needed
+        ;; if there's a package directive, insert one blank line below and
+        ;; leave point after it
         (end-of-line)
         (if (eobp)
             (insert ?\n)