branch: javaimp_devel commit 2f8450a2b4cdf0297b11a929aeeb90753506d7d9 Author: Filipp Gunbin <fgun...@fastmail.fm> Commit: Filipp Gunbin <fgun...@fastmail.fm>
in progress --- packages/javaimp/javaimp.el | 325 ++++++++++++++++++++----------------------- 1 files changed, 152 insertions(+), 173 deletions(-) diff --git a/packages/javaimp/javaimp.el b/packages/javaimp/javaimp.el index 6a50e76..3bf11e3 100644 --- a/packages/javaimp/javaimp.el +++ b/packages/javaimp/javaimp.el @@ -11,26 +11,45 @@ ;; Allows to manage Java import statements in Maven projects. ;; -;; Quick start: customize `javaimp-import-group-alist', `javaimp-jdk-home' -;; and call `javaimp-maven-visit-project', then in a Java buffer visiting a -;; file under that module or one of its submodules call -;; `javaimp-organize-imports' or `javaimp-add-import'. `javaimp-add-import' -;; will provide you a helpful completion, and the default value (the one -;; you'll get if you hit `M-n' in the minibuffer) is the symbol under point, -;; so usually it's enough to hit `M-n', then add some starting letters of a -;; package and hit `TAB'. The module does not add all needed imports -;; automatically! It only helps you to quickly add imports when stepping -;; through compilation errors. +;; Quick start: +;; +;; - customize `javaimp-import-group-alist' +;; +;; - call `javaimp-maven-visit-project', giving it the top-level project +;; directory where pom.xml resides +;; +;; Then in a Java buffer visiting a file under that project or one of its +;; submodules call `javaimp-organize-imports' or `javaimp-add-import'. +;; +;; +;; Details: +;; +;; `javaimp-add-import' will provide you a helpful completion with the list +;; of classes taken from this module's dependencies. Completion default +;; value (the one you'll get if you hit `M-n' in the minibuffer) is the +;; symbol under point, so usually it's enough to hit `M-n', then add some +;; starting letters of a package and hit `TAB'. +;; +;; This module does not add all needed imports automatically! It only helps +;; you to quickly add imports when stepping through compilation errors. ;; ;; If Maven failed, you can see its output in the buffer named by ;; `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. When it is detected that a particular jar or pom.xml file's -;; timestamp changed, it is re-read and cache is updated. +;; to complete. If a modules's pom.xml or any of its parents pom.xml was +;; changed (modification timestamp is checked), `mvn +;; dependency:build-classpath' is re-run on the current module. If a jar +;; file was changed, its contents is reloaded. +;; +;; If you make some changes which change project hierarchy, you should +;; re-visit the parent again with `javaimp-maven-visit-project'. +;; +;; May work unreliable with inner classes, but you can always import +;; top-level class and use qualified name. ;; -;; Details on variables. +;; User options: ;; ;; `javaimp-import-group-alist' defines the order of import statement ;; groups. By default java.* and javax.* imports are assigned an order of @@ -38,33 +57,40 @@ ;; project's imports typically should come after, so the sample config below ;; sets 80 for them. ;; -;; `javaimp-jdk-home' is a path for JDK. It is used to scan JDK jars. -;; Usually you will need to set this. +;; `javaimp-jdk-home' is a path for JDK. It is used to scan JDK jars. By +;; default, it is set from the JAVA_HOME environment variable. ;; ;; `javaimp-additional-source-dirs' is a list specifying directories where ;; additional (e.g. generated) source files reside. Each directory is a ;; relative path from ${project.build.directory} project property value. ;; -;; `javaimp-mvn-program' defines path of the `mvn' program. Use if it's -;; not on `exec-path'. +;; `javaimp-mvn-program' defines path to the `mvn' program. Customize it +;; if `mvn' is not on `exec-path'. ;; -;; `javaimp-cygpath-program' defines path of the `cygpath' program (applies -;; to Cygwin only, of course). Use if it's not on `exec-path'. +;; `javaimp-cygpath-program' defines path to the `cygpath' program (Cygwin +;; only). Customize it if `cygpath' is not on `exec-path' ;; -;; `javaimp-jar-program' defines path of the `jar' program. Use if it's -;; not on `exec-path'. +;; `javaimp-jar-program' defines path to the `jar' program. Customize it +;; if `jar' is not on `exec-path'. ;; -;; Details on commands. +;; Commands: ;; -;; `javaimp-maven-visit-project' is the first command you should issue to -;; use this module. It reads the pom structure recursively and records -;; which files belong to which module. Maven help:effective-pom command is -;; used to do that. +;; `javaimp-maven-visit-project' asks for the project directory, calls +;; `mvn help:effective-pom' on the pom.xml in that directory, reads project +;; structure from the output and records which files belong to which +;; (sub)modules and other information about the modules. Then the project +;; becomes known to javaimp and `javaimp-add-import' may be called inside +;; project's files. ;; -;; `javaimp-organize-imports' groups import statement and writes those -;; group according to the value of `javaimp-import-group-alist'. Imports +;; `javaimp-add-import' asks for a class to import, adds import statement +;; for the selected class and calls `javaimp-organize-imports'. It provides +;; completion alternatives made from all this module dependencies' classes. +;; +;; `javaimp-organize-imports' rearranges import statements so that they +;; are listed in groups according to the value of +;; `javaimp-import-group-alist', with blank lines between groups. Imports ;; which are not matched by any regexp in that variable are assigned a -;; default order defined by `javaimp-import-default-order' (50 by default). +;; default order defined by `javaimp-import-default-order'. ;; ;; Sample .emacs initialization: ;; @@ -73,8 +99,6 @@ ;; (add-to-list 'javaimp-import-group-alist ;; '("\\`\\(my\\.company\\.\\|my\\.company2\\.\\)" . 80)) ;; -;; (setq javaimp-jdk-home (getenv "JAVA_HOME")) -;; (setq javaimp-include-current-module-classes t) ;; (setq javaimp-additional-source-dirs '("generated-sources/thrift")) ;; ;; (add-hook 'java-mode-hook @@ -118,7 +142,7 @@ (require 'seq) -;;; User options +;; User options (defgroup javaimp () "Add and reorder Java import statements in Maven projects") @@ -138,11 +162,9 @@ 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 nil - "Path to the JDK. If you have JAVA_HOME environment variable -set up, this variable can be set like this: - -(setq javaimp-jdk-home (getenv \"JAVA_HOME\"))") +(defcustom javaimp-jdk-home (getenv "JAVA_HOME") + "Path to the JDK. By default, it is set from the JAVA_HOME +environment variable." (defcustom javaimp-additional-source-dirs nil "List of directories where additional (e.g. generated) @@ -172,13 +194,13 @@ supported yet.") "Path to the `jar' program") (defcustom javaimp-include-current-module-classes t - "If non-nil, current project's classes are included into completion -alternatives. - -Only top-level classes are included.") + "If non-nil, current module's classes are included into +completion alternatives. `javaimp-add-import' will find all java +files in the current project and add their fully-qualified names +to the completion alternatives list.") -;;; Variables and constants +;; Variables and constants (defvar javaimp-project-forest nil "Visited projects") @@ -188,8 +210,31 @@ Only top-level classes are included.") (defconst javaimp-debug-buf-name "*javaimp-debug*") +;; Structs + +(cl-defstruct javaimp-node + parent children contents) + +(cl-defstruct javaimp-module + id parent-id file file-ts final-name packaging + source-dir test-source-dir build-dir + modules + dep-jars) + +(defun javaimp-print-id (id) + (format "%s:%s:%s" + (javaimp-id-artifact id) + (javaimp-id-group id) + (javaimp-id-version id))) + +(cl-defstruct (javaimp-id + (:print-function #'javaimp-print-id)) + group artifact version) + +(cl-defstruct javaimp-jar + file file-ts classes) + -;;; XML routines (defun javaimp-xml-child-list (xml-tree child-name) "Returns list of children of XML-TREE filtered by CHILD-NAME" @@ -207,8 +252,6 @@ Only top-level classes are included.") "Returns a first child of EL" (car (cddr el))) - -;; Cygwin (defun javaimp-cygpath-convert-maybe (path) (if (eq system-type 'cygwin) @@ -216,31 +259,7 @@ Only top-level classes are included.") path)) -;; Structs - -(cl-defstruct javaimp-node - parent children contents) - -(cl-defstruct javaimp-module - id parent-id file file-ts final-name packaging - source-dir test-source-dir build-dir - dep-jars) - -(defun javaimp-print-id (id) - (format "%s:%s:%s" - (javaimp-id-artifact id) - (javaimp-id-group id) - (javaimp-id-version id))) - -(cl-defstruct (javaimp-id - (:print-function #'javaimp-print-id)) - group artifact version) - -(cl-defstruct javaimp-jar - file file-ts classes) - - -;;; Maven +;; Project loading ;; TODO what if it's already there? @@ -253,39 +272,45 @@ directory containing pom.xml." (concat (file-name-as-directory path) "pom.xml")))) (unless (file-readable-p file) (error "Cannot read file: %s" file)) - (let ((tree (javaimp--maven-load-tree file))) + (let ((tree (javaimp--maven-xml-load-tree file))) (if tree (push tree javaimp-project-forest))) (message "Loaded tree for %s" file))) -(defun javaimp--maven-load-tree (file) +;; TODO file should start to sink down from there; at each step append directory +;; from <module> to it + +(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-read-effective-pom file))) + (let ((xml-tree (javaimp--maven-xml-read-effective-pom file))) (cond ((assq 'project xml-tree) ;; no real children - (let ((project-elt (assq 'project xml-tree))) - (message "Independent submodules: %s" - (mapconcat #'javaimp-xml-first-child - (javaimp--maven-get-module-elts project-elt) - ", ")) - (let ((module (javaimp--maven-parse-module project-elt))) - (javaimp--build-tree (javaimp-module-id module) nil (list module))))) + (let ((project-elt (assq 'project xml-tree)) + (submodules (javaimp-xml-child-list + (javaimp-xml-child 'modules project-elt) 'module))) + (and submodules + (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-child-list (assq 'projects xml-tree) 'project)) - (all-modules (mapcar #'javaimp--maven-parse-module project-elts))) - (javaimp--build-tree (javaimp-module-id (car all-modules)) nil all-modules))) + (all-modules (mapcar #'javaimp--maven-xml-parse-module project-elts))) + (javaimp--maven-build-tree + (javaimp-module-id (car all-modules)) nil all-modules file))) (t ;; neither <project> nor <projects> - error (error "Invalid `help:effective-pom' output"))))) -(defun javaimp--maven-read-effective-pom (pom) +(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 @@ -314,7 +339,7 @@ the temporary buffer and returns its result" (with-temp-buffer (let* ((pom-file (javaimp-cygpath-convert-maybe pom-file)) (status - ;; FIXME check Maven output on Gnu/Linux + ;; TODO check in Maven output on Gnu/Linux (let ((coding-system-for-read (if (eq system-type 'cygwin) 'utf-8-dos))) (process-file javaimp-mvn-program nil t nil "-f" pom-file target))) @@ -327,12 +352,10 @@ the temporary buffer and returns its result" (goto-char (point-min)) (funcall handler)))) -(defun javaimp--maven-get-module-elts (project-elt) - (javaimp-xml-child-list - (javaimp-xml-child 'modules project-elt) 'module)) -(defun javaimp--maven-parse-module (elt) - ;; TODO file - instead of javaimp-fill-pom-file-paths +(defun javaimp--maven-xml-parse-module (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 ;; ;; TODO javaimp-maven-process-projects ;; @@ -366,32 +389,62 @@ the temporary buffer and returns its result" projects-elts)) (defun javaimp--maven-extract-id (elt) - (javaimp-make-artifact + (make-javaimp-id (javaimp-xml-first-child (javaimp-xml-child 'groupId elt)) (javaimp-xml-first-child (javaimp-xml-child 'artifactId elt)) (javaimp-xml-first-child (javaimp-xml-child 'version elt)))) -;; Module tree - -(defun javaimp--build-tree (id parent-node all-modules) +(defun javaimp--maven-build-tree (id parent-node all-modules file) (let ((this (or (javaimp--find-by-id id all-modules) (error "Cannot find module %s!" id))) - ;; although each real parent has <modules> section, better way to - ;; build hirarchy is to analyze <parent> node in each child + ;; although each real parent has <modules> section, more reliable + ;; way to build hirarchy is to analyze <parent> node in each child (children (javaimp--find-by-parent-id id all-modules))) (if (and (null children) - (string= (javaimp-module-packaging module) "pom")) ;TODO remove mvn specifics + (string= (javaimp-module-packaging module) "pom")) ;; this module is a pure aggregate module - it has neither real ;; children nor source files, so skip it nil - ;; otherwise, make node + ;; here we can finally set the `file' slot as the path is known at + ;; this time + (setf (javaimp-module-file this) file) + ;; make node (let ((this-node (make-javaimp-node parent-node nil this))) (setf (javaimp-node-children this-node) (mapcar (lambda (child) - (javaimp--build-tree (javaimp-module-id child) this-node all-modules)) + (let ((child-file + (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)) this-node)))) - + +(defun javaimp--maven-get-submodule-file (module parent-file parent-rel-paths) + ;; 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 matche + (let* ((parent-dir (file-name-directory parent-file)) + (files (mapcar (lambda (rel-path) + (concat parent-dir + (file-name-as-directory rel-path) + "pom.xml")) + parent-rel-paths))) + (or (seq-find + (lambda (file) + (let* ((xml-tree (with-temp-buffer + (insert-file-contents file) + (xml-parse-region (point-min) (point-max)))) + (project-elt (assq 'project xml-tree)) + (id (javaimp--maven-extract-id project-elt)) + (parent-id (javaimp--maven-extract-id (assq 'parent project-elt)))) + ;; TODO we need lax matching because some id + ;; components may be missing + (and (equal (javaimp-module-id module) id) + (equal (javaimp-module-parent-id module) parent-id))))) + files) + (error "Cannot find file for module: %s" (javaimp-module-id module)))) + (defun javaimp--find-by-id (id modules) ;; TODO seq-find ) @@ -401,80 +454,6 @@ the temporary buffer and returns its result" ) -;; pom file parsing - this will not be needed? - -(defun javaimp-fill-pom-file-paths (modules pom) - "Subroutine of `javaimp-maven-load-module-tree'" - (let ((artifact-alist (javaimp-traverse-pom-tree (list pom)))) - (dolist (module modules) - (let* ((artifact (javaimp-mod-artifact module)) - (path - (cdr (or (seq-find (lambda (el) - (equal artifact (car el))) - artifact-alist) - ;; Compare just id if comparison by all fields failed - (seq-find (lambda (el) - (equal (javaimp-artifact-artifact-id artifact) - (javaimp-artifact-artifact-id (car el)))) - artifact-alist) - (error "Cannot find path for artifact: %s" artifact))))) - (javaimp-set-mod-pom-file module path))))) - - -(defun javaimp-parse-pom-file (pom-file) - "Subroutine of `javaimp-traverse-pom-tree'. Parses POM-FILE. -Car of result is artifact info. Cdr of result is submodules -relative path list." - (message "Parsing pom file mapping for %s" pom-file) - (let* ((xml-tree (with-temp-buffer - (insert-file-contents pom-file) - (xml-parse-region (point-min) (point-max)))) - (project-elt (cond ((assq 'top xml-tree) - (javaimp-xml-child 'project (assq 'top xml-tree))) - ((assq 'project xml-tree) - (assq 'project xml-tree)) - (t - (error "Cannot find <project> element in pom %s!" pom-file))))) - (cons (javaimp--maven-extract-id project-elt) - (mapcar #'javaimp-xml-first-child - (javaimp--maven-get-module-elts project-elt))))) - -(defun javaimp-traverse-pom-tree (pom-file-list) - "Traverses pom tree and returns alist where each element is of -the form (\"ARTIFACT\" . \"POM-FILE-PATH\"). Result paths are in -platform default format." - (if pom-file-list - (append - ;; this item - (let* ((this-pom (car pom-file-list)) - (pom-file-data (javaimp-parse-pom-file this-pom))) - (append - ;; this pom itself - (list (cons (car pom-file-data) this-pom)) - ;; children of this pom - (javaimp-traverse-pom-tree - (javaimp-create-absolute-submodules-paths - this-pom (cdr pom-file-data))))) - ;; rest items - (javaimp-traverse-pom-tree (cdr pom-file-list))))) - -(defun javaimp-create-absolute-submodules-paths (base-pom paths) - "Subroutine of `javaimp-traverse-pom-tree'" - (mapcar - (lambda (rel-path) - (expand-file-name - (concat - ;; base path - (file-name-directory base-pom) - ;; submodule relative path - (file-name-as-directory - (javaimp-cygpath-convert-maybe rel-path)) - ;; well-known pom name - "pom.xml"))) - paths)) - - - ;;; Working with module dependency JARs (defun javaimp-maven-fetch-module-deps (module)