X-Git-Url: https://code.delx.au/gnu-emacs-elpa/blobdiff_plain/a7801e14187f4116ce3cd0c78247cd30270a32bf..f10533854f4c7bb54247a11981191bf37b70cb36:/packages/javaimp/javaimp.el diff --git a/packages/javaimp/javaimp.el b/packages/javaimp/javaimp.el index f44be4e9f..d1c68657d 100644 --- a/packages/javaimp/javaimp.el +++ b/packages/javaimp/javaimp.el @@ -29,14 +29,11 @@ ;; `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. @@ -61,11 +58,15 @@ ;; ;; - 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: @@ -77,14 +78,15 @@ ;; 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." ;; 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 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\\|")) + (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 elements" + (let ((project (assq 'project xml-tree)) + (projects (assq 'projects xml-tree))) + (cond (project + (list project)) + (projects + (javaimp--xml-children projects 'project)) (t - ;; neither nor - 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\\|")) - (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 nor 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 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)) + ;; 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 section, more reliable - ;; way to build hirarchy is to analyze 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 - ;; element with module relative path taken from is to - ;; visit pom and check that id and parent-id matches + ;; Seems that the only reliable way to match a module parsed from + ;; element with module relative path taken from 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)) ;; 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)