branch: javaimp_devel commit 4b918c0bc257c6d3f608c7514051826051b0cc42 Author: Filipp Gunbin <fgun...@fastmail.fm> Commit: Filipp Gunbin <fgun...@fastmail.fm>
in progress --- packages/javaimp/javaimp.el | 371 ++++++++++++++++++------------------------- 1 files changed, 152 insertions(+), 219 deletions(-) diff --git a/packages/javaimp/javaimp.el b/packages/javaimp/javaimp.el index fcca837..a2ab7c4 100644 --- a/packages/javaimp/javaimp.el +++ b/packages/javaimp/javaimp.el @@ -12,7 +12,7 @@ ;; Allows to manage Java import statements in Maven projects. ;; ;; Quick start: customize `javaimp-import-group-alist', `javaimp-jdk-home' -;; and call `javaimp-maven-visit-root', then in a Java buffer visiting a +;; 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 @@ -56,7 +56,7 @@ ;; ;; Details on commands. ;; -;; `javaimp-maven-visit-root' is the first command you should issue to +;; `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. @@ -74,7 +74,7 @@ ;; '("\\`\\(my\\.company\\.\\|my\\.company2\\.\\)" . 80)) ;; ;; (setq javaimp-jdk-home (getenv "JAVA_HOME")) -;; (setq javaimp-include-current-project-classes t) +;; (setq javaimp-include-current-module-classes t) ;; (setq javaimp-additional-source-dirs '("generated-sources/thrift")) ;; ;; (add-hook 'java-mode-hook @@ -85,10 +85,10 @@ ;; ;; TODO before version 1.0: ;; -;; - correct submodule tree for each top-level project (now top-level projects -;; hold linear submodule list and this prevents modification checking for parent -;; poms). If a project doesn't have any children, then it should be in the list -;; by itself. +;; - correct submodule tree for each top-level project (now top-level +;; projects hold linear submodule list and this prevents modification +;; checking for parent poms). Each project should represented as (NAME +;; OBJECT . CHILDREN). ;; ;; - cl-defstruct for data ;; @@ -102,17 +102,26 @@ ;; calling `cygpath'. See https://cygwin.com/ml/cygwin/2013-03/msg00228.html. ;; ;; - include <packaging> into module info +;; +;; - each module's parent should be set according to its "parent" node +;; +;; - when a module has a parent but do not inherits, its jars are not added +;; +;; - save/restore state +;; +;; - API functions should check pom file modifications and refresh if needed ;; ;;; Code: +(require 'cl-macs) (require 'seq) ;;; User options (defgroup javaimp () - "Add and reorder Java import statements in Maven projects.") + "Add and reorder Java import statements in Maven projects") (defcustom javaimp-import-group-alist '(("\\`javax?\\." . 10)) "Specifies how to group classes and how to order resulting @@ -130,7 +139,10 @@ The order of classes which were not matched is defined by `javaimp-import-group-alist'") (defcustom javaimp-jdk-home nil - "Path to the JDK") + "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-additional-source-dirs nil "List of directories where additional (e.g. generated) @@ -152,13 +164,14 @@ supported yet.") (defcustom javaimp-mvn-program "mvn" "Path to the `mvn' program") -(defcustom javaimp-cygpath-program "cygpath" +(defcustom javaimp-cygpath-program + (if (eq system-type 'cygwin) "cygpath") "Path to the `cygpath' program") (defcustom javaimp-jar-program "jar" "Path to the `jar' program") -(defcustom javaimp-include-current-project-classes t +(defcustom javaimp-include-current-module-classes t "If non-nil, current project's classes are included into completion alternatives. @@ -167,11 +180,11 @@ Only top-level classes are included.") ;;; Variables and constants -(defvar javaimp-maven-root-modules nil - "Loaded root Maven modules") +(defvar javaimp-project-forest nil + "Visited projects") -(defvar javaimp-jar-classes-cache nil - "Jar classes cache") +(defvar javaimp-jar-cache nil + "Cache for jar contents") (defconst javaimp-debug-buf-name "*javaimp-debug*") @@ -195,121 +208,60 @@ Only top-level classes are included.") (car (cddr el))) -;;; Data representation - -;; FIXME: use cl-defstruct! - -;; Module - -(defsubst javaimp-make-mod (artifact pom-file source-dir test-source-dir build-dir - pom-file-mod-ts jars-list parent parent-ts final-name) - (list artifact pom-file source-dir test-source-dir build-dir - pom-file-mod-ts jars-list parent parent-ts final-name)) - -(defsubst javaimp-get-mod-artifact (module) - (nth 0 module)) - -(defsubst javaimp-get-mod-pom-file (module) - (nth 1 module)) -(defsubst javaimp-set-mod-pom-file (module value) - (setcar (nthcdr 1 module) value)) - -(defsubst javaimp-get-mod-source-dir (module) - (nth 2 module)) - -(defsubst javaimp-get-mod-test-source-dir (module) - (nth 3 module)) - -(defsubst javaimp-get-mod-build-dir (module) - (nth 4 module)) - -(defsubst javaimp-get-mod-pom-mod-ts (module) - (nth 5 module)) -(defsubst javaimp-set-mod-pom-mod-ts (module value) - (setcar (nthcdr 5 module) value)) - -(defsubst javaimp-get-mod-pom-deps (module) - (nth 6 module)) -(defsubst javaimp-set-mod-pom-deps (module value) - (setcar (nthcdr 6 module) value)) - -(defsubst javaimp-get-mod-parent (module) - (nth 7 module)) -(defsubst javaimp-set-mod-parent (module value) - (setcar (nthcdr 7 module) value)) - -(defsubst javaimp-get-mod-parent-ts (module) - (nth 8 module)) -(defsubst javaimp-set-mod-parent-ts (module value) - (setcar (nthcdr 8 module) value)) - -(defsubst javaimp-get-mod-final-name (module) - (nth 9 module)) -(defsubst javaimp-set-mod-final-name (module value) - (setcar (nthcdr 9 module) value)) +;; Structs -;; Artifact +(cl-defstruct javaimp-node + parent children contents) -(defsubst javaimp-make-artifact (group-id artifact-id version) - (list group-id artifact-id version)) +(cl-defstruct javaimp-module + id file file-ts final-name + source-dir test-source-dir build-dir + dep-jars) -(defsubst javaimp-artifact-group-id (artifact) - (car artifact)) - -(defsubst javaimp-artifact-artifact-id (artifact) - (cadr artifact)) - -(defsubst javaimp-artifact-version (artifact) - (nth 2 artifact)) - -(defun javaimp-artifact-to-string (artifact) +(defun javaimp-print-id (id) (format "%s:%s:%s" - (javaimp-artifact-artifact-id artifact) - (javaimp-artifact-group-id artifact) - (javaimp-artifact-version artifact))) ;FIXME: `artifact' is not a function! - -(defun javaimp-parse-artifact (artifact) - (apply #'javaimp-make-artifact (split-string artifact ":"))) - - -;; JAR - -(defsubst javaimp-make-jar (jar-path jar-mod-ts classes-list) - (cons jar-path (cons jar-mod-ts classes-list))) - -(defsubst javaimp-get-jar-path (jar) - (car jar)) - -(defsubst javaimp-get-jar-mod-ts (jar) - (cadr jar)) + (javaimp-id-artifact id) + (javaimp-id-group id) + (javaimp-id-version id))) -(defsubst javaimp-set-jar-mod-ts (jar value) - (setcar (cdr jar) value)) +(cl-defstruct (javaimp-id + (:print-function #'javaimp-print-id)) + group artifact version) -(defsubst javaimp-get-jar-classes-list (jar) - (cddr jar)) - -(defsubst javaimp-set-jar-classes-list (jar value) - (setcdr (cdr jar) value)) +(cl-defstruct javaimp-jar + file file-ts classes) -;;; Loading Maven projects tree +;;; Loading Maven projects + +;; TODO if it's already there? ;;;###autoload -(defun javaimp-maven-visit-root (path) - "Loads all modules starting from root module identified by -PATH. PATH should point to a directory containing pom.xml." - (interactive "DVisit maven root project: ") - (let* ((root-pom (expand-file-name - (concat (file-name-as-directory path) "pom.xml"))) - (modules (if (file-readable-p root-pom) - (javaimp-maven-load-module-tree root-pom) - (error "Cannot read root pom: %s" root-pom))) - (root-project (assoc root-pom javaimp-maven-root-modules))) - (if root-project - (setcdr root-project modules) - (push (cons root-pom modules) javaimp-maven-root-modules)) - (message "Loaded modules for %s" path))) +(defun javaimp-maven-visit-project (path) + "Loads a project and its submodules. PATH should point to a +directory containing pom.xml." + (interactive "DVisit maven project: ") + (let ((file (expand-file-name + (concat (file-name-as-directory path) "pom.xml")))) + (unless (file-readable-p file) + (error "Cannot read file: %s" file)) + (push (javaimp-maven-load-tree file) javaimp-project-forest) + (message "Loaded tree for %s" file))) + +(defun javaimp-maven-load-tree (file) + ;; TODO + "Creates a tree of Maven projects starting from FILE" + (let* ((effective-pom (javaimp-parse-effective-pom pom)) + (project-elts + (cond ((assq 'projects effective-pom) ;project contains <module> tag(s) + (javaimp-xml-child-list (assq 'projects effective-pom) 'project)) + ((assq 'project effective-pom) ;single-module project + (list (assq 'project effective-pom))) + (t + (error "Cannot find projects in XML tree")))) + (modules-alist (javaimp-maven-process-projects project-elts))) + (javaimp-fill-pom-file-paths modules-alist pom) + modules-alist)) (defun javaimp-parse-effective-pom (pom) "Calls `mvn help:effective:pom and returns XML parse tree" @@ -333,26 +285,13 @@ PATH. PATH should point to a directory containing pom.xml." (match-end 0))))) (xml-parse-region xml-start-pos xml-end-pos))))) -(defun javaimp-maven-load-module-tree (pom) - "Returns an alist of all Maven modules in a hierarchy starting -with POM" - (let* ((effective-pom (javaimp-parse-effective-pom pom)) - (project-elts - (cond ((assq 'projects effective-pom) ;project contains <module> tag(s) - (javaimp-xml-child-list (assq 'projects effective-pom) 'project)) - ((assq 'project effective-pom) ;single-module project - (list (assq 'project effective-pom))) - (t - (error "Cannot find projects in XML tree")))) - (modules-alist (javaimp-maven-process-projects project-elts))) - (javaimp-fill-pom-file-paths modules-alist pom) - modules-alist)) + (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-get-mod-artifact module)) + (let* ((artifact (javaimp-mod-artifact module)) (path (cdr (or (seq-find (lambda (el) (equal artifact (car el))) @@ -376,6 +315,8 @@ with POM" (car (process-lines javaimp-cygpath-program "-u" path)) path)) +;; todo set everything immediately + (defun javaimp-maven-process-projects (projects-elts) (mapcar (lambda (project-elt) @@ -462,22 +403,18 @@ platform default format." the temporary buffer and returns its result" (message "Calling \"mvn %s\" on pom: %s" target pom-file) (with-temp-buffer - (let* ((pom-file (if (eq system-type 'cygwin) - (car (process-lines javaimp-cygpath-program - "-m" pom-file)) - pom-file)) + (let* ((pom-file (javaimp-cygpath-convert-maybe pom-file)) (status - ;; FIXME on GNU/Linux Maven strangely outputs ^M chars. Check - ;; also jar output with the same var binding below. - (let ((coding-system-for-read (when (eq system-type 'cygwin) 'utf-8-dos))) + ;; FIXME check 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))) - (output-buf (current-buffer))) + (b (current-buffer))) (with-current-buffer (get-buffer-create javaimp-debug-buf-name) (erase-buffer) - (insert-buffer-substring output-buf)) + (insert-buffer-substring b)) (or (and (numberp status) (= status 0)) - (error "Maven target \"%s\" failed with status \"%s\"" - target status)) + (error "Maven target \"%s\" failed with status \"%s\"" target status)) (goto-char (point-min)) (funcall handler)))) @@ -487,7 +424,7 @@ the temporary buffer and returns its result" (defun javaimp-maven-fetch-module-deps (module) "Returns list of dependency jars for MODULE" (javaimp-call-mvn - (javaimp-get-mod-pom-file module) "dependency:build-classpath" + (javaimp-mod-pom-file module) "dependency:build-classpath" (lambda () (let (deps-line) (goto-char (point-min)) @@ -512,51 +449,44 @@ the temporary buffer and returns its result" (not (equal (float-time curr-ts) (float-time last-ts))) (javaimp-any-file-ts-updated (cdr files)))))) -(defun javaimp-get-dep-jars-cached (module parent) +(defun javaimp-get-deps-cached (module parent) "Returns a list of dependency jar file paths for a MODULE. Both MODULE and PARENT poms are checked for updates because PARENT pom may have some versions which are inherited by the MODULE." (when (javaimp-any-file-ts-updated - (remq nil (list (cons (javaimp-get-mod-pom-file module) - (javaimp-get-mod-pom-mod-ts module)) + (remq nil (list (cons (javaimp-mod-pom-file module) + (javaimp-mod-pom-mod-ts module)) (when parent (cons - (javaimp-get-mod-pom-file parent) + (javaimp-mod-pom-file parent) ;; here we check the saved parent ts because it ;; matters what version we had when we were ;; reloading this pom the last time - (javaimp-get-mod-parent-ts module)))))) + (javaimp-mod-parent-ts module)))))) ;; (re-)fetch dependencies (javaimp-set-mod-pom-deps module (javaimp-maven-fetch-module-deps module)) ;; update timestamps (javaimp-set-mod-pom-mod-ts - module (javaimp-get-file-ts (javaimp-get-mod-pom-file module))) + module (javaimp-get-file-ts (javaimp-mod-pom-file module))) (when parent (javaimp-set-mod-parent-ts - module (javaimp-get-file-ts (javaimp-get-mod-pom-file parent))))) - (javaimp-get-mod-pom-deps module)) + module (javaimp-get-file-ts (javaimp-mod-pom-file parent))))) + (javaimp-mod-pom-deps module)) (defun javaimp-get-jdk-jars () - "Returns list of jars from the jre/lib subdirectory of the JDK -directory" - (when javaimp-jdk-home - (directory-files (concat (file-name-as-directory javaimp-jdk-home) - (file-name-as-directory "jre/lib")) - t "\\.jar$"))) + (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\\'")))) -(defun javaimp-get-jar-classes-cached (jar) - (let ((current-jar-mod-ts - (nth 5 (file-attributes (javaimp-get-jar-path jar))))) - (unless (equal (float-time (javaimp-get-jar-mod-ts jar)) - (float-time current-jar-mod-ts)) - (javaimp-set-jar-classes-list jar (javaimp-fetch-jar-classes jar)) - (javaimp-set-jar-mod-ts jar current-jar-mod-ts)) - (javaimp-get-jar-classes-list jar))) +;; Working with jar and its cache (defun javaimp-fetch-jar-classes (jar) - (let ((jar-file (javaimp-get-jar-path jar)) + (let ((jar-file (javaimp-jar-path jar)) result) (message "Reading classes in jar: %s" jar-file) (with-temp-buffer @@ -572,42 +502,41 @@ directory" result)) result))) -(defun javaimp-collect-jar-classes (jar-paths) +(defun javaimp-get-jar-classes-cached (jar) + (let ((current-jar-mod-ts + (nth 5 (file-attributes (javaimp-jar-path jar))))) + (unless (equal (float-time (javaimp-jar-mod-ts jar)) + (float-time current-jar-mod-ts)) + (javaimp-set-jar-classes-list jar (javaimp-fetch-jar-classes jar)) + (javaimp-set-jar-mod-ts jar current-jar-mod-ts)) + (javaimp-jar-classes-list jar))) + +(defun javaimp-collect-classes (jar-paths) (let (result jar) (dolist (jar-path jar-paths result) - (setq jar (assoc jar-path javaimp-jar-classes-cache)) + (setq jar (assoc jar-path javaimp-jar-cache)) (unless jar (setq jar (javaimp-make-jar jar-path nil nil)) - (push jar javaimp-jar-classes-cache)) + (push jar javaimp-jar-cache)) (setq result (append (javaimp-get-jar-classes-cached jar) result))))) -(defun javaimp-get-module-from-root (roots predicate) - (if (null roots) - nil - (let ((result (javaimp-get-module (cdr (car roots)) predicate))) - (or result - (javaimp-get-module-from-root (cdr roots) predicate))))) - -(defun javaimp-get-module (modules predicate) - (cond ((null modules) - nil) - ((funcall predicate (car modules)) - (car modules)) - (t - (javaimp-get-module (cdr modules) predicate)))) - -(defun javaimp-get-module-by-file (file) - (javaimp-get-module-from-root - javaimp-maven-root-modules - (lambda (mod) - (or (string-prefix-p (javaimp-get-mod-source-dir mod) file) - (string-prefix-p (javaimp-get-mod-test-source-dir mod) file))))) - -(defun javaimp-get-module-by-artifact (artifact) - (javaimp-get-module-from-root - javaimp-maven-root-modules - (lambda (mod) - (equal (javaimp-get-mod-artifact mod) artifact)))) +;; Searching and navigating through projects + +(defun javaimp-find-module (predicate) + (javaimp--find-module javaimp-project-forest predicate)) + +(defun javaimp--find-module (projects predicate) + (if projects + (let ((project (car projects))) + (or (javaimp--get-module (cdr project) predicate) + (javaimp--find-module (cdr projects) predicate))))) + +(defun javaimp--get-module (modules predicate) + (if modules + (let ((module (car modules))) + (if (funcall predicate module) + module + (javaimp--get-module (cdr modules) predicate))))) ;;; Some functions for use in other programs @@ -615,17 +544,20 @@ directory" (defun javaimp-get-source-directories () (append (mapcar (lambda (root) - (mapcar #'javaimp-get-mod-source-dir (cdr root))) - javaimp-maven-root-modules))) + (mapcar #'javaimp-mod-source-dir (cdr root))) + javaimp-project-forest))) (defun javaimp-get-all-modules () "Returns flat list of all child modules." (apply #'seq-concatenate 'list - (mapcar #'cdr javaimp-maven-root-modules))) + (mapcar #'cdr javaimp-project-forest))) ;;; Adding and organizing imports +;; TODO without prefix arg narrow alternatives by local name; with prefix +;; arg - include all classes in alternatives + ;;;###autoload (defun javaimp-add-import (classname) "Imports CLASSNAME in the current file. Interactively, @@ -636,27 +568,28 @@ module." (let* ((file (expand-file-name (or buffer-file-name (error "Buffer is not visiting a file!")))) - (module (or (javaimp-get-module-by-file file) - (error "Cannot determine module for file: %s" file))) - (parent (javaimp-get-module-by-artifact - (javaimp-get-mod-parent module)))) + (module (or (javaimp-find-module + (lambda (m) + (or (string-prefix-p (javaimp-mod-source-dir m) file) + (string-prefix-p (javaimp-mod-test-source-dir m) file)))) + (error "Cannot find module by file: %s" file)))) (list (completing-read "Import: " (append - (javaimp-collect-jar-classes - (append (javaimp-get-dep-jars-cached module parent) - (javaimp-get-jdk-jars))) - (and javaimp-include-current-project-classes - (javaimp-get-module-classes module))) + (let ((jars (append (javaimp-get-deps-cached module) + (javaimp-get-jdk-jars)))) + (javaimp-collect-classes jars)) + (and javaimp-include-current-module-classes + (javaimp-classes-from-source module))) nil t nil nil (symbol-name (symbol-at-point)))))) (javaimp-organize-imports classname)) -(defun javaimp-get-module-classes (module) +(defun javaimp-classes-from-source (module) "Scans current project and returns a list of top-level classes in both the source directory and test source directory" - (let ((src-dir (javaimp-get-mod-source-dir module)) - (test-src-dir (javaimp-get-mod-test-source-dir module)) - (build-dir (javaimp-get-mod-build-dir module))) + (let ((src-dir (javaimp-mod-source-dir module)) + (test-src-dir (javaimp-mod-test-source-dir module)) + (build-dir (javaimp-mod-build-dir module))) (append (and javaimp-additional-source-dirs (seq-mapcat @@ -774,12 +707,12 @@ argument is a list of additional classes to import." (defun javaimp-invalidate-jar-classes-cache () "Resets jar classes cache (debugging only)" (interactive) - (setq javaimp-jar-classes-cache nil)) + (setq javaimp-jar-cache nil)) (defun javaimp-forget-all-visited-modules () - "Resets `javaimp-maven-root-modules' (debugging only)" + "Resets `javaimp-project-forest' (debugging only)" (interactive) - (setq javaimp-maven-root-modules nil)) + (setq javaimp-project-forest nil)) (defun javaimp-reset () "Resets all data (debugging only)"