emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] master 1bc7436: [javaimp]: Support gradle, split into multiple fi


From: Filipp Gunbin
Subject: [elpa] master 1bc7436: [javaimp]: Support gradle, split into multiple files
Date: Fri, 15 Nov 2019 13:21:52 -0500 (EST)

branch: master
commit 1bc7436e9c525c7ee8fcbe5d953241d338f6d1e0
Author: Filipp Gunbin <address@hidden>
Commit: Filipp Gunbin <address@hidden>

    [javaimp]: Support gradle, split into multiple files
    
    * packages/javaimp/javaimp.el: Move maven-specific functions to
      javaimp-maven.el.  Move common functions to javaimp-util.el
    * packages/javaimp/javaimp-maven.el: New file.
    * packages/javaimp/javaimp-gradle.el: New file.
    * packages/javaimp/javaimp-util.el: New file.
    * packages/javaimp/gradleTaskBody.inc.kts: New file.
---
 packages/javaimp/gradleTaskBody.inc.kts |  29 ++
 packages/javaimp/javaimp-gradle.el      | 149 ++++++++++
 packages/javaimp/javaimp-maven.el       | 181 ++++++++++++
 packages/javaimp/javaimp-tests.el       |  14 +-
 packages/javaimp/javaimp-util.el        | 166 +++++++++++
 packages/javaimp/javaimp.el             | 480 ++++++++------------------------
 6 files changed, 647 insertions(+), 372 deletions(-)

diff --git a/packages/javaimp/gradleTaskBody.inc.kts 
b/packages/javaimp/gradleTaskBody.inc.kts
new file mode 100644
index 0000000..697e743
--- /dev/null
+++ b/packages/javaimp/gradleTaskBody.inc.kts
@@ -0,0 +1,29 @@
+// use syntax which is valid both in Groovy and in Kotlin
+doLast {
+     println("id=${project.group}:${project.name}:${project.version}")
+     if (project.parent != null) {
+       
println("parent-id=${project.parent.group}:${project.parent.name}:${project.parent.version}")
+     }
+     println("file=${project.buildFile}")
+     if (project.hasProperty("archivesBaseName")) { // defined by java plugin
+         println("final-name=" + 
project.configurations.getByName("archives").artifacts.stream()
+           .filter { it.type.equals("jar") || it.type.equals("war") }
+           .map { it.file.path }
+           .findFirst()
+           .orElse(""))
+     } else {
+         println("final-name=")
+     }
+     println("build-dir=${project.buildDir}")
+     if (project.hasProperty("sourceSets")) { // defined by java plugin
+       println("source-dirs=" + project.sourceSets.stream()
+         .flatMap { it.allJava.srcDirs.stream().map { it.path } }
+         .collect(Collectors.joining(File.pathSeparator)))
+       println("dep-jars=" + project.sourceSets.stream()
+         .flatMap { it.compileClasspath.files.stream().filter { 
it.name.endsWith("jar") }.map { it.path } }
+         .collect(Collectors.joining(File.pathSeparator)))
+     } else {
+       println("source-dirs=")
+       println("dep-jars=")
+     }
+}
diff --git a/packages/javaimp/javaimp-gradle.el 
b/packages/javaimp/javaimp-gradle.el
new file mode 100644
index 0000000..77c7dae
--- /dev/null
+++ b/packages/javaimp/javaimp-gradle.el
@@ -0,0 +1,149 @@
+;;; javaimp-gradle.el --- javaimp gradle support  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2019  Free Software Foundation, Inc.
+
+;; Author: Filipp Gunbin <address@hidden>
+;; Maintainer: Filipp Gunbin <address@hidden>
+;; Version: 0.6.1
+
+;; 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 3 of the License, 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.  If not, see <http://www.gnu.org/licenses/>.
+
+(require 'javaimp-util)
+
+(defcustom javaimp-gradle-program "gradle"
+  "Path to the `gradle' program.  Customize it if the program is
+not on `exec-path'.  If the visited project's directory contains
+gradlew program, it is used in preference."
+  :group 'javaimp
+  :type 'string)
+
+(defconst javaimp--gradle-task-body
+  (with-temp-buffer
+    (insert-file-contents (expand-file-name "gradleTaskBody.inc.kts" 
javaimp--basedir))
+    (buffer-string))
+  "Task body, uses syntax which can be used both in Groovy and Kotlin")
+
+(defun javaimp--gradle-visit (file)
+  "Calls gradle on FILE to get various project information.
+
+Passes specially crafted init file as -I argument to gradle and
+invokes task contained in it.  This task returns all needed
+information."
+  (message "Visiting Gradle build file %s..." file)
+  (let* ((alists (javaimp--gradle-call file
+                                       javaimp--gradle-task-body
+                                       #'javaimp--gradle-handler
+                                       "javaimpTask"))
+         (modules (mapcar (lambda (alist)
+                            (javaimp--gradle-module-from-alist alist file))
+                          alists)))
+    (prog1
+        ;; first module is always root
+        (javaimp--build-tree (car modules) nil modules)
+      (message "Loaded tree for %s" file))))
+
+(defun javaimp--gradle-handler ()
+  (goto-char (point-min))
+  (let (alist res sym val)
+    (while (re-search-forward "^\\([[:alnum:]-]+\\)=\\(.*\\)$" nil t)
+      (setq sym (intern (match-string 1))
+            val (match-string 2))
+      (if (string-blank-p val)
+          (setq val nil))
+      (when (and (eq sym 'id) alist)    ;start of next module
+        (push alist res)
+        (setq alist nil))
+      (push (cons sym val) alist))
+    (when alist                         ;last module
+      (push alist res))
+    (nreverse res)))
+
+(defun javaimp--gradle-module-from-alist (alist file-orig)
+  (make-javaimp-module
+   :id (javaimp--gradle-id-from-colon-separated (cdr (assq 'id alist)))
+   :parent-id (javaimp--gradle-id-from-colon-separated (cdr (assq 'parent-id 
alist)))
+   :file (cdr (assq 'file alist))
+   :file-orig file-orig
+   ;; jar/war supported
+   :final-name (let ((final-name (javaimp-cygpath-convert-maybe
+                                  (cdr (assq 'final-name alist)))))
+                 (and final-name
+                      (member (file-name-extension final-name) '("jar" "war"))
+                      final-name))
+   :source-dirs (mapcar #'file-name-as-directory
+                        (javaimp--split-native-path
+                         (cdr (assq 'source-dirs alist))))
+   :build-dir (file-name-as-directory
+               (javaimp-cygpath-convert-maybe
+                (cdr (assq 'build-dir alist))))
+   :dep-jars (javaimp--split-native-path (cdr (assq 'dep-jars alist)))
+   :load-ts (current-time)
+   :dep-jars-path-fetcher #'javaimp--gradle-fetch-dep-jars-path))
+
+(defun javaimp--gradle-id-from-colon-separated (str)
+  (when str
+    (let ((parts (split-string str ":" t)))
+      (unless (= (length parts) 3)
+        (error "Invalid maven id: %s" str))
+      (make-javaimp-id :group (nth 0 parts) :artifact (nth 1 parts) :version 
(nth 2 parts)))))
+
+(defun javaimp--gradle-fetch-dep-jars-path (module)
+  ;; always invoke on root file becase module's file may not exist
+  ;; (even if reported as project.buildFile property)
+  (javaimp--gradle-call (javaimp-module-file-orig module)
+                        javaimp--gradle-task-body
+                        (lambda ()
+                          (re-search-forward "^dep-jars=\\(.*\\)$")
+                          (match-string 1))
+                        (concat (if (javaimp-module-parent-id module)
+                                    (concat ":" (javaimp-id-artifact 
(javaimp-module-id module))))
+                                ":javaimpTask")))
+
+(defun javaimp--gradle-call (file init-script-body handler task)
+  (let* ((is-kotlin (equal (file-name-extension file) "kts"))
+         (init-file (make-temp-file "javaimp" nil
+                                    (if is-kotlin ".kts")
+                                    (if is-kotlin
+                                        (javaimp--gradle-init-script-kotlin 
init-script-body)
+                                      (javaimp--gradle-init-script 
init-script-body))))
+         (local-gradlew (concat (file-name-directory file) "gradlew")))
+    (javaimp--call-build-tool (if (file-exists-p local-gradlew)
+                                  local-gradlew
+                                javaimp-gradle-program)
+                              handler
+                              "-q"
+                              "-b" (javaimp-cygpath-convert-maybe file)
+                              "-I" (javaimp-cygpath-convert-maybe init-file)
+                              task)))
+
+
+(defun javaimp--gradle-init-script (body)
+  (concat "
+import java.io.File
+import java.util.stream.Collectors
+allprojects {
+  task javaimpTask {"
+          body
+          "} }"))
+
+(defun javaimp--gradle-init-script-kotlin (body)
+  (concat "
+import java.io.File
+import java.util.stream.Collectors
+allprojects {
+  tasks.register(\"javaimpTask\") {"
+          body
+          "} }"))
+
+(provide 'javaimp-gradle)
diff --git a/packages/javaimp/javaimp-maven.el 
b/packages/javaimp/javaimp-maven.el
new file mode 100644
index 0000000..b523381
--- /dev/null
+++ b/packages/javaimp/javaimp-maven.el
@@ -0,0 +1,181 @@
+;;; javaimp-maven.el --- javaimp maven support  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2019  Free Software Foundation, Inc.
+
+;; Author: Filipp Gunbin <address@hidden>
+;; Maintainer: Filipp Gunbin <address@hidden>
+;; Version: 0.6.1
+
+;; 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 3 of the License, 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.  If not, see <http://www.gnu.org/licenses/>.
+
+
+;;; Code:
+
+(require 'javaimp-util)
+
+(defcustom javaimp-mvn-program "mvn"
+  "Path to the `mvn' program.  Customize it if the program is not
+on `exec-path'."
+  :group 'javaimp
+  :type 'string)
+
+
+(defun javaimp--maven-visit (file)
+  "Calls `mvn help:effective-pom' on FILE,
+reads project structure from the output and records which files
+belong to which modules and other module information"
+  (message "Visiting Maven POM file %s..." file)
+  (let* ((xml-tree (javaimp--call-build-tool javaimp-mvn-program
+                                             
#'javaimp--maven-effective-pom-handler
+                                             "-f" 
(javaimp-cygpath-convert-maybe file)
+                                             "help:effective-pom"))
+            (projects (javaimp--maven-projects-from-xml xml-tree))
+            (modules (mapcar (lambda (proj-elt)
+                            (javaimp--maven-module-from-xml proj-elt file))
+                          projects))
+            ;; first module is always root
+            (tree (javaimp--build-tree (car modules) nil modules)))
+    (when tree
+      ;; Set files in a separate step after building the tree because "real"
+      ;; parent of a child (given by <parent>) does not necessary contains the
+      ;; child in its <modules>.  This is rare, but happens.
+      (javaimp--maven-fill-modules-files file tree)
+      ;; check that no :file slot is empty
+      (let ((modules-without-files
+                (mapcar #'javaimp-node-contents
+                            (javaimp--select-nodes-from-tree
+                             tree (lambda (m)
+                                        (null (javaimp-module-file m)))))))
+           (if modules-without-files
+               (error "Cannot find file for module(s): %s"
+                          (mapconcat #'javaimp-module-id modules-without-files 
", "))))
+      tree)))
+
+(defun javaimp--maven-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-projects-from-xml (tree)
+  "Analyzes result of `mvn help:effective-pom' and returns list
+of <project> elements"
+  (let ((project (assq 'project tree))
+           (projects (assq 'projects tree)))
+    (cond (project
+              (list project))
+             (projects
+              (javaimp--xml-children projects 'project))
+             (t
+              (error "Neither <project> nor <projects> was found in pom")))))
+
+(defun javaimp--maven-module-from-xml (elt file-orig)
+  (let ((build-elt (javaimp--xml-child 'build elt)))
+    (make-javaimp-module
+     :id (javaimp--maven-id-from-xml elt)
+     :parent-id (javaimp--maven-id-from-xml (javaimp--xml-child 'parent elt))
+     ;; <project> element does not contain pom file path, so we set this slot
+     ;; later, see javaimp--maven-fill-modules-files
+     :file nil
+     :file-orig file-orig
+     ;; jar/war supported
+     :final-name (let ((packaging (or (javaimp--xml-first-child
+                                              (javaimp--xml-child 'packaging 
elt))
+                                      "jar")))
+                   (when (member packaging '("jar" "war"))
+                     (concat (javaimp--xml-first-child
+                              (javaimp--xml-child 'finalName build-elt))
+                             "." packaging)))
+     :source-dirs (list (file-name-as-directory
+                                (javaimp-cygpath-convert-maybe
+                                 (javaimp--xml-first-child
+                                  (javaimp--xml-child 'sourceDirectory 
build-elt))))
+                        (file-name-as-directory
+                                (javaimp-cygpath-convert-maybe
+                                     (javaimp--xml-first-child
+                                      (javaimp--xml-child 'testSourceDirectory 
build-elt)))))
+     :build-dir (file-name-as-directory
+                        (javaimp-cygpath-convert-maybe
+                         (javaimp--xml-first-child (javaimp--xml-child 
'directory build-elt))))
+     :dep-jars nil          ; dep-jars is initialized lazily on demand
+     :load-ts (current-time)
+     :dep-jars-path-fetcher #'javaimp--maven-fetch-dep-jars-path)))
+
+(defun javaimp--maven-id-from-xml (elt)
+  (make-javaimp-id
+   :group (javaimp--xml-first-child (javaimp--xml-child 'groupId elt))
+   :artifact (javaimp--xml-first-child (javaimp--xml-child 'artifactId elt))
+   :version (javaimp--xml-first-child (javaimp--xml-child 'version elt))))
+
+(defun javaimp--maven-fill-modules-files (file tree)
+  ;; Reads module id from FILE, looks up corresponding module in TREE, sets its
+  ;; :file slot, then recurses for each submodule.  A submodule file path is
+  ;; constructed by appending relative path taken from <module> to FILE's
+  ;; directory.
+  (let* ((xml-tree (with-temp-buffer
+                            (insert-file-contents file)
+                            (xml-parse-region (point-min) (point-max))))
+            (project-elt (assq 'project xml-tree))
+            (this-id (javaimp--maven-id-from-xml project-elt))
+            ;; seems that the only mandatory component in tested ids is 
artifact, while
+            ;; group and version may be inherited and thus not presented in 
pom.xml
+            (id-pred (if (or (null (javaimp-id-group this-id))
+                                     (null (javaimp-id-version this-id)))
+                             (progn
+                                   (message "File %s contains incomplete id, 
will check artifact only" file)
+                                   (lambda (tested-id)
+                                     (equal (javaimp-id-artifact this-id)
+                                                (javaimp-id-artifact 
tested-id))))
+                           (lambda (tested-id)
+                             (equal this-id tested-id))))
+            (module
+             (javaimp-node-contents
+              (or (javaimp--find-node-in-tree
+                       tree (lambda (m)
+                              (funcall id-pred (javaimp-module-id m))))
+                  (error "Cannot find module for id %s (taken from file %s)" 
this-id file)))))
+    (setf (javaimp-module-file module) file)
+    (let ((rel-paths
+              (mapcar #'javaimp--xml-first-child
+                          (javaimp--xml-children (javaimp--xml-child 'modules 
project-elt) 'module))))
+      (dolist (rel-path rel-paths)
+           (javaimp--maven-fill-modules-files (concat (file-name-directory 
file)
+                                                                          
(file-name-as-directory rel-path)
+                                                                          
"pom.xml")
+                                                              tree)))))
+
+(defun javaimp--maven-fetch-dep-jars-path (module)
+  (javaimp--call-build-tool javaimp-mvn-program
+                            (lambda ()
+                              (goto-char (point-min))
+                              (search-forward "Dependencies classpath:")
+                              (forward-line 1)
+                              (thing-at-point 'line))
+                            ;; always invoke for this module's pom.ml
+                            "-f" (javaimp-cygpath-convert-maybe
+                                  (javaimp-module-file module))
+                            "dependency:build-classpath"))
+
+(provide 'javaimp-maven)
diff --git a/packages/javaimp/javaimp-tests.el 
b/packages/javaimp/javaimp-tests.el
index cd8acb2..0dbe86f 100644
--- a/packages/javaimp/javaimp-tests.el
+++ b/packages/javaimp/javaimp-tests.el
@@ -1,23 +1,23 @@
-;;; javaimp-tests.el --- javaimp module tests  -*- lexical-binding: t; -*-
+;;; javaimp-tests.el --- javaimp tests  -*- lexical-binding: t; -*-
 
-;; Copyright (C) 2016  Free Software Foundation, Inc.
+;; Copyright (C) 2016-2019  Free Software Foundation, Inc.
 
 ;; Author: Filipp Gunbin <address@hidden>
 ;; Maintainer: Filipp Gunbin <address@hidden>
 
 (require 'ert)
-(require 'javaimp)
+(require 'javaimp-maven)
 
-(ert-deftest javaimp-test--maven-xml-extract-projects--project ()
+(ert-deftest javaimp-test--maven-projects-from-xml--project ()
   (with-temp-buffer
     (insert "<project/>")
-    (let ((projects (javaimp--maven-xml-extract-projects
+    (let ((projects (javaimp--maven-projects-from-xml
                     (xml-parse-region (point-min) (point-max)))))
       (should (eql (length projects) 1)))))
 
-(ert-deftest javaimp-test--maven-xml-extract-projects--projects ()
+(ert-deftest javaimp-test--maven-projects-from-xml--projects ()
   (with-temp-buffer
     (insert "<projects><project/><project/></projects>")
-    (let ((projects (javaimp--maven-xml-extract-projects
+    (let ((projects (javaimp--maven-projects-from-xml
                     (xml-parse-region (point-min) (point-max)))))
       (should (eql (length projects) 2)))))
diff --git a/packages/javaimp/javaimp-util.el b/packages/javaimp/javaimp-util.el
new file mode 100644
index 0000000..2284ddd
--- /dev/null
+++ b/packages/javaimp/javaimp-util.el
@@ -0,0 +1,166 @@
+;;; javaimp-util.el --- javaimp util  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2019  Free Software Foundation, Inc.
+
+;; Author: Filipp Gunbin <address@hidden>
+;; Maintainer: Filipp Gunbin <address@hidden>
+;; Version: 0.6.1
+
+;; 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 3 of the License, 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.  If not, see <http://www.gnu.org/licenses/>.
+
+
+;;; Code:
+
+(require 'xml)
+(require 'cl-lib)
+(require 'seq)
+
+(defcustom javaimp-cygpath-program
+  (if (eq system-type 'cygwin) "cygpath")
+  "Path to the `cygpath' program (Cygwin only).  Customize it if
+the program is not on `exec-path'."
+  :group 'javaimp
+  :type 'string)
+
+(defconst javaimp-debug-buf-name "*javaimp-debug*")
+
+(defconst javaimp--basedir (file-name-directory load-file-name))
+
+;; Structs
+
+(cl-defstruct javaimp-node
+  parent children contents)
+
+(cl-defstruct javaimp-module
+  id parent-id
+  file
+  file-orig
+  final-name                           ;may be relative (to build-dir)
+  source-dirs build-dir
+  dep-jars
+  load-ts
+  dep-jars-path-fetcher)
+
+(cl-defstruct javaimp-id
+  group artifact version)
+
+(cl-defstruct javaimp-cached-jar
+  file read-ts classes)
+
+
+(defun javaimp--xml-children (xml-tree child-name)
+  "Returns list of children of XML-TREE filtered by CHILD-NAME"
+  (seq-filter (lambda (child)
+               (and (consp child)
+                    (eq (car child) child-name)))
+             (cddr xml-tree)))
+
+(defun javaimp--xml-child (name el)
+  "Returns a child of EL named by symbol NAME"
+  (assq name (cddr el)))
+
+(defun javaimp--xml-first-child (el)
+  "Returns a first child of EL"
+  (car (cddr el)))
+
+
+(defun javaimp--get-file-ts (file)
+  (nth 5 (file-attributes file)))
+
+(defun javaimp-print-id (id)
+  (format "%s:%s:%s"
+          (javaimp-id-artifact id)
+          (javaimp-id-group id)
+          (javaimp-id-version id)))
+
+(defun javaimp--get-jdk-jars ()
+  (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\\'"))))
+
+
+;; TODO 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
+
+(defun javaimp-cygpath-convert-maybe (path &optional mode is-really-path)
+  "On Cygwin, converts PATH using cygpath according to MODE and
+IS-REALLY-PATH.  If MODE is `unix' (the default), adds -u switch.
+If MODE is `windows', adds -m switch.  If `is-really-path' is
+non-nil, adds `-p' switch.  On other systems, PATH is returned
+unchanged."
+  (if (and path (eq system-type 'cygwin))
+      (progn
+       (unless mode (setq mode 'unix))
+       (let (args)
+         (push (cond ((eq mode 'unix) "-u")
+                     ((eq mode 'windows) "-m")
+                     (t (error "Invalid mode: %s" mode)))
+               args)
+         (and is-really-path (push "-p" args))
+         (push path args)
+         (car (apply #'process-lines javaimp-cygpath-program args))))
+    path))
+
+
+(defun javaimp--call-build-tool (program handler &rest args)
+  "Runs PROGRAM with ARGS, then calls HANDLER in the temporary
+buffer and returns its result"
+  (message "Calling %s on args: %s" program args)
+  (with-temp-buffer
+    (let ((status (let ((coding-system-for-read
+                         (if (eq system-type 'cygwin) 'utf-8-dos)))
+                    ;; TODO check 
 in output on Gnu/Linux
+                    (apply #'process-file program nil t nil args)))
+         (buf (current-buffer)))
+      (with-current-buffer (get-buffer-create javaimp-debug-buf-name)
+       (erase-buffer)
+       (insert-buffer-substring buf))
+      (or (and (numberp status) (= status 0))
+         (error "\"%s\" failed with status \"%s\"" program status))
+      (goto-char (point-min))
+      (funcall handler))))
+
+(defun javaimp--split-native-path (path)
+  (when path
+    (let ((converted (javaimp-cygpath-convert-maybe path 'unix t))
+         (sep-regex (concat "[" path-separator "\n" "]+")))
+      (split-string converted sep-regex t))))
+
+(defun javaimp--build-tree (this parent-node all)
+  (message "Building tree for module: %s" (javaimp-module-id this))
+  (let ((children
+        ;; more or less 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)))
+    (let* ((this-node (make-javaimp-node
+                      :parent parent-node
+                      :children nil
+                      :contents this))
+          ;; recursively build child nodes
+          (child-nodes
+           (mapcar (lambda (child)
+                     (javaimp--build-tree child this-node all))
+                   children)))
+      (setf (javaimp-node-children this-node) child-nodes)
+      this-node)))
+
+(provide 'javaimp-util)
diff --git a/packages/javaimp/javaimp.el b/packages/javaimp/javaimp.el
index 8a3f4f3..b24b243 100644
--- a/packages/javaimp/javaimp.el
+++ b/packages/javaimp/javaimp.el
@@ -1,11 +1,11 @@
-;;; javaimp.el --- Add and reorder Java import statements in Maven projects  
-*- lexical-binding: t; -*-
+;;; javaimp.el --- Add and reorder Java import statements in Maven/Gradle 
projects  -*- lexical-binding: t; -*-
 
-;; Copyright (C) 2014-2018  Free Software Foundation, Inc.
+;; Copyright (C) 2014-2019  Free Software Foundation, Inc.
 
 ;; Author: Filipp Gunbin <address@hidden>
 ;; Maintainer: Filipp Gunbin <address@hidden>
 ;; Version: 0.6.1
-;; Keywords: java, maven, programming
+;; Keywords: java, maven, gradle, programming
 
 ;; 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
@@ -22,13 +22,13 @@
 
 ;;; Commentary:
 
-;; Allows to manage Java import statements in Maven projects.
+;; Allows to manage Java import statements in Maven/Gradle projects.
 ;;
 ;;   Quick start:
 ;;
 ;; - customize `javaimp-import-group-alist'
-;; - call `javaimp-maven-visit-project', giving it the top-level project
-;; directory where pom.xml resides
+;; - call `javaimp-visit-project', giving it the top-level project
+;; directory where pom.xml / build.gradle[.kts] 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'.
@@ -38,60 +38,47 @@
 ;;
 ;;   Some details:
 ;;
-;; If Maven failed, you can see its output in the buffer named by
-;; `javaimp-debug-buf-name' (default is "*javaimp-debug*").
+;; If Maven/Gradle 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 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.
+;; Contents of jar files and Maven/Gradle project structures are
+;; cached, so usually only the first command should take a
+;; considerable amount of time to complete.  If a module's build file
+;; or any of its parents' build files (within visited tree) was
+;; modified after information was loaded, dependencies are fetched
+;; from the build tool again.  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.
 ;;
 ;;
-;;   Example of initialization:
+;;   Example:
 ;;
 ;; (require 'javaimp)
-;;
 ;; (add-to-list 'javaimp-import-group-alist
 ;;   '("\\`\\(my\\.company\\.\\|my\\.company2\\.\\)" . 80))
-;;
 ;; (setq javaimp-additional-source-dirs '("generated-sources/thrift"))
-;;
 ;; (add-hook 'java-mode-hook
 ;;       (lambda ()
 ;;         (local-set-key "\C-ci" 'javaimp-add-import)
 ;;         (local-set-key "\C-co" 'javaimp-organize-imports)))
+;; (global-set-key (kbd "C-c j v") 'javaimp-visit-project)
 ;;
-;;
-;; TODO:
-;;
-;; - 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, 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
-;;
-;; - :type for defcustom
+
 
 ;;; Code:
 
-(require 'cl-lib)
-(require 'seq)
-(require 'xml)
+(require 'javaimp-maven)
+(require 'javaimp-gradle)
+
 
 
 ;; User options
 
 (defgroup javaimp ()
-  "Add and reorder Java import statements in Maven projects"
+  "Add and reorder Java import statements in Maven/Gradle
+projects"
   :group 'c)
 
 (defcustom javaimp-import-group-alist '(("\\`java\\." . 10) ("\\`javax\\." . 
15))
@@ -103,16 +90,22 @@ 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
-`javaimp-import-default-order'.")
+`javaimp-import-default-order'."
+  :group 'javaimp
+  :type '(alist :key-type string :value-type integer))
 
 (defcustom javaimp-import-default-order 50
   "Defines the order of classes which were not matched by
-`javaimp-import-group-alist'")
+`javaimp-import-group-alist'"
+  :group 'javaimp
+  :type 'integer)
 
 (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.")
+the JAVA_HOME environment variable."
+  :group 'javaimp
+  :type 'string)
 
 (defcustom javaimp-additional-source-dirs nil
   "List of directories where additional (e.g. generated)
@@ -129,29 +122,26 @@ becomes \"generated-sources/<plugin_name>\" (note the 
absence
 of the leading slash.
 
 Custom values set in plugin configuration in pom.xml are not
-supported yet.")
-
-(defcustom javaimp-mvn-program "mvn"
-  "Path to the `mvn' program.  Customize it if the program is not
-on `exec-path'.")
-
-(defcustom javaimp-cygpath-program
-  (if (eq system-type 'cygwin) "cygpath")
-  "Path to the `cygpath' program (Cygwin only).  Customize it if
-the program is not on `exec-path'.")
+supported yet."
+  :group 'javaimp
+  :type '(repeat (string :tag "Relative directory")))
 
 (defcustom javaimp-jar-program "jar"
   "Path to the `jar' program used to read contents of jar files.
-Customize it if the program is not on `exec-path'.")
+Customize it if the program is not on `exec-path'."
+  :group 'javaimp
+  :type 'string)
 
 (defcustom javaimp-include-current-module-classes t
   "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.")
+to the completion alternatives list."
+  :group 'javaimp
+  :type 'boolean)
 
 
-;; Variables and constants
+;; Variables
 
 (defvar javaimp-project-forest nil
   "Visited projects")
@@ -160,317 +150,70 @@ to the completion alternatives list.")
   "Alist of cached jars.  Each element is of the form (FILE
   . CACHED-JAR).")
 
-(defconst javaimp-debug-buf-name "*javaimp-debug*")
-
-;; Structs
-
-(cl-defstruct javaimp-node
-  parent children contents)
-
-(cl-defstruct javaimp-module
-  id parent-id
-  file
-  final-name
-  packaging
-  source-dir test-source-dir build-dir
-  dep-jars
-  load-ts)
-
-(cl-defstruct javaimp-id
-  group artifact version)
-
-(cl-defstruct javaimp-cached-jar
-  file read-ts classes)
 
 
-;; Utilities
-
-(defun javaimp--xml-children (xml-tree child-name)
-  "Returns list of children of XML-TREE filtered by CHILD-NAME"
-  (seq-filter (lambda (child)
-               (and (consp child)
-                    (eq (car child) child-name)))
-             (cddr xml-tree)))
-
-(defun javaimp--xml-child (name el)
-  "Returns a child of EL named by symbol NAME"
-  (assq name (cddr el)))
-
-(defun javaimp--xml-first-child (el)
-  "Returns a first child of EL"
-  (car (cddr el)))
-
-(defun javaimp--get-file-ts (file)
-  (nth 5 (file-attributes file)))
-
-(defun javaimp--get-jdk-jars ()
-  (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
-IS-REALLY-PATH.  If MODE is `unix' (the default), adds -u switch.
-If MODE is `windows', adds -m switch.  If `is-really-path' is
-non-nil, adds `-p' switch.  On other systems, PATH is returned
-unchanged."
-  (if (eq system-type 'cygwin)
-      (progn
-       (unless mode (setq mode 'unix))
-       (let (args)
-         (push (cond ((eq mode 'unix) "-u")
-                     ((eq mode 'windows) "-m")
-                     (t (error "Invalid mode: %s" mode)))
-               args)
-         (and is-really-path (push "-p" args))
-         (push path args)
-         (car (apply #'process-lines javaimp-cygpath-program args))))
-    path))
-
-
-;; Project loading
-
 ;;;###autoload
-(defun javaimp-maven-visit-project (path)
-  "Loads a project and its submodules.  PATH should point to a
-directory containing pom.xml.
-
-Calls `mvn help:effective-pom' on the pom.xml in the PATH, reads
-project structure from the output and records which files belong
-to which modules and other module information.
+(defun javaimp-visit-project (dir)
+  "Loads a project and its submodules.  DIR should point to a
+directory containing pom.xml / build.gradle[.kts].
 
 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 in directory: ")
-  (let ((file (expand-file-name
-              (concat (file-name-as-directory path) "pom.xml"))))
-    (unless (file-readable-p file)
-      (error "Cannot read file: %s" file))
-    ;; delete previous loaded tree, if any
-    (setq javaimp-project-forest
-         (seq-remove (lambda (tree)
-                       (equal (javaimp-module-file (javaimp-node-contents 
tree))
-                              file))
-                     javaimp-project-forest))
-    (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)))
-      (when tree
-       ;; Set files in a separate step after building the tree because "real"
-       ;; parent of a child (given by <parent>) does not necessary contains the
-       ;; child in its <modules>.  This is rare, but happens.
-       (javaimp--maven-fill-modules-files file tree)
-       ;; check that no :file slot is empty
-       (let ((modules-without-files
-              (mapcar #'javaimp-node-contents
-                      (javaimp--select-nodes-from-tree
-                       tree (lambda (m)
-                              (null (javaimp-module-file m)))))))
-         (if modules-without-files
-             (error "Cannot find file for module(s): %s"
-                    (mapconcat #'javaimp-module-id modules-without-files ", 
"))))
-       (push tree javaimp-project-forest)))
-    (message "Loaded tree for %s" file)))
+known to javaimp and `javaimp-add-import' may be called inside
+any module file."
+  (interactive "DVisit Maven or Gradle project in directory: ")
+  (let* ((exp-dir (expand-file-name (file-name-as-directory dir)))
+         build-file
+         (tree (cond
+                ((file-readable-p (setq build-file (concat exp-dir "pom.xml")))
+                 (javaimp--maven-visit build-file))
+                ((or (file-readable-p (setq build-file (concat exp-dir 
"build.gradle")))
+                     (file-readable-p (setq build-file (concat exp-dir 
"build.gradle.kts"))))
+                 (javaimp--gradle-visit build-file))
+                (t
+                 (error "Could not find build file in dir %s" dir)))))
+    (when tree
+      ;; delete previous tree(s) loaded from this build file, if any
+      (setq javaimp-project-forest
+           (seq-remove (lambda (tree)
+                         (equal (javaimp-module-file-orig 
(javaimp-node-contents tree))
+                                build-file))
+                       javaimp-project-forest))
+      (push tree javaimp-project-forest)
+      (message "Loaded tree for %s" dir))))
 
 
-;; Maven XML routines
-
-(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
-          (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)
-     :parent-id (javaimp--maven-xml-extract-id (javaimp--xml-child 'parent 
project))
-     ;; <project> element does not contain pom file path, so we set this slot
-     ;; later, see javaimp--maven-fill-modules-files
-     :file nil
-     :final-name (javaimp--xml-first-child
-                 (javaimp--xml-child 'finalName build-elt))
-     :packaging (javaimp--xml-first-child
-                (javaimp--xml-child 'packaging project))
-     :source-dir (file-name-as-directory
-                 (javaimp-cygpath-convert-maybe
-                  (javaimp--xml-first-child
-                   (javaimp--xml-child 'sourceDirectory build-elt))))
-     :test-source-dir (file-name-as-directory
-                      (javaimp-cygpath-convert-maybe
-                       (javaimp--xml-first-child
-                        (javaimp--xml-child 'testSourceDirectory build-elt))))
-     :build-dir (file-name-as-directory
-                (javaimp-cygpath-convert-maybe
-                 (javaimp--xml-first-child (javaimp--xml-child 'directory 
build-elt))))
-     :dep-jars nil                   ; dep-jars is initialized lazily on demand
-     :load-ts (current-time))))
-
-(defun javaimp--maven-xml-extract-id (elt)
-  (make-javaimp-id
-   :group (javaimp--xml-first-child (javaimp--xml-child 'groupId elt))
-   :artifact (javaimp--xml-first-child (javaimp--xml-child 'artifactId elt))
-   :version (javaimp--xml-first-child (javaimp--xml-child 'version elt))))
-
-
-
-;; Maven routines
-
-(defun javaimp--maven-call (pom-file target handler)
-  "Runs Maven target TARGET on POM-FILE, then calls HANDLER in
-the temporary buffer and returns its result"
-  (message "Calling \"mvn %s\" on pom: %s" target pom-file)
-  (with-temp-buffer
-    (let* ((pom-file (javaimp-cygpath-convert-maybe pom-file))
-          (status
-           ;; 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)))
-          (buf (current-buffer)))
-      (with-current-buffer (get-buffer-create javaimp-debug-buf-name)
-       (erase-buffer)
-       (insert-buffer-substring buf))
-      (or (and (numberp status) (= status 0))
-         (error "Maven target \"%s\" failed with status \"%s\"" target status))
-      (goto-char (point-min))
-      (funcall handler))))
-
-(defun javaimp--maven-build-tree (this parent-node all)
-  (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)))
-    (let* ((this-node (make-javaimp-node
-                      :parent parent-node
-                      :children nil
-                      :contents this))
-          ;; recursively build child nodes
-          (child-nodes
-           (mapcar (lambda (child)
-                     (javaimp--maven-build-tree child this-node all))
-                   children)))
-      (setf (javaimp-node-children this-node) child-nodes)
-      this-node)))
-
-(defun javaimp--maven-fill-modules-files (file tree)
-  ;; Reads module id from FILE, looks up corresponding module in TREE, sets its
-  ;; :file slot, then recurses for each submodule.  A submodule file path is
-  ;; constructed by appending relative path taken from <module> to FILE's
-  ;; directory.
-  (let* ((xml-tree (with-temp-buffer
-                    (insert-file-contents file)
-                    (xml-parse-region (point-min) (point-max))))
-        (project-elt (assq 'project xml-tree))
-        (this-id (javaimp--maven-xml-extract-id project-elt))
-        ;; seems that the only mandatory component in tested ids is artifact, 
while
-        ;; group and version may be inherited and thus not presented in pom.xml
-        (id-pred (if (or (null (javaimp-id-group this-id))
-                         (null (javaimp-id-version this-id)))
-                     (progn
-                       (message "File %s contains incomplete id, will check 
artifact only" file)
-                       (lambda (tested-id)
-                         (equal (javaimp-id-artifact this-id)
-                                (javaimp-id-artifact tested-id))))
-                   (lambda (tested-id)
-                     (equal this-id tested-id))))
-        (module
-         (javaimp-node-contents
-          (or (javaimp--find-node-in-tree
-               tree (lambda (m)
-                      (funcall id-pred (javaimp-module-id m))))
-              (error "Cannot find module for id %s (taken from file %s)" 
this-id file)))))
-    (setf (javaimp-module-file module) file)
-    (let ((rel-paths
-          (mapcar #'javaimp--xml-first-child
-                  (javaimp--xml-children (javaimp--xml-child 'modules 
project-elt) 'module))))
-      (dolist (rel-path rel-paths)
-       (javaimp--maven-fill-modules-files (concat (file-name-directory file)
-                                                  (file-name-as-directory 
rel-path)
-                                                  "pom.xml")
-                                          tree)))))
+;; Dependency jars
 
-
-;;; Loading dep-jars
-
-(defun javaimp--maven-update-module-maybe (node)
+(defun javaimp--update-module-maybe (node)
   (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
+    (unless (javaimp-module-dep-jars module)
+      (message "Loading dependencies: %s" (javaimp-module-id module))
+      (setq need-update t))
+    ;; check if this or any parent build file has changed since we
+    ;; loaded the module
     (let ((tmp node))
-      (while (and tmp
-                 (not need-update))
-       (let ((checked (javaimp-node-contents tmp)))
-         (if (> (float-time (javaimp--get-file-ts (javaimp-module-file 
checked)))
-                (float-time (javaimp-module-load-ts module)))
-             (progn
-               (message "Reloading %s (pom changed)" (javaimp-module-id 
checked))
-               (setq need-update t))))
+      (while (and tmp (not need-update))
+       (let ((cur (javaimp-node-contents tmp)))
+         (when (> (max (if (file-exists-p (javaimp-module-file cur))
+                            (float-time (javaimp--get-file-ts 
(javaimp-module-file cur)))
+                          -1)
+                        (if (file-exists-p (javaimp-module-file-orig cur))
+                            (float-time (javaimp--get-file-ts 
(javaimp-module-file-orig cur)))
+                          -1))
+                  (float-time (javaimp-module-load-ts module)))
+           (message "Reloading dependencies for %s (some build-file changed)"
+                     (javaimp-module-id cur))
+           (setq need-update t)))
        (setq tmp (javaimp-node-parent tmp))))
     (when need-update
-      (let* ((new-dep-jars (javaimp--maven-fetch-dep-jars module))
+      (let* ((path (funcall (javaimp-module-dep-jars-path-fetcher module) 
module))
+             (new-dep-jars (javaimp--split-native-path path))
             (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* ((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
-
 (defun javaimp--get-jar-classes (file)
   (let ((cached (cdr (assoc file javaimp-cached-jars))))
     (cond ((null cached)
@@ -559,6 +302,8 @@ the temporary buffer and returns its result"
 
 ;;; Adding imports
 
+;; TODO narrow alternatives by class local name
+
 ;;;###autoload
 (defun javaimp-add-import (classname)
   "Imports classname in the current file.  Interactively,
@@ -572,10 +317,11 @@ classes in the current module."
                                      (error "Buffer is not visiting a 
file!"))))
          (node (or (javaimp--find-node
                     (lambda (m)
-                      (or (string-prefix-p (javaimp-module-source-dir m) file)
-                          (string-prefix-p (javaimp-module-test-source-dir m) 
file))))
+                       (seq-some (lambda (dir)
+                                   (string-prefix-p dir file))
+                                 (javaimp-module-source-dirs m))))
                    (error "Cannot find module by file: %s" file))))
-     (javaimp--maven-update-module-maybe node)
+     (javaimp--update-module-maybe node)
      (let ((module (javaimp-node-contents node)))
        (list (completing-read
              "Import: "
@@ -596,22 +342,18 @@ classes in the current module."
 (defun javaimp--get-module-classes (module)
   "Returns list of top-level classes in current module"
   (append
-   (let ((build-dir (javaimp-module-build-dir module)))
-     ;; additional source dirs
-     (and (seq-mapcat
-          (lambda (rel-dir)
-            (let ((dir (concat build-dir (file-name-as-directory rel-dir))))
-              (and (file-accessible-directory-p dir)
-                   (javaimp--get-directory-classes dir nil))))
-          javaimp-additional-source-dirs)))
-   ;; source dir
-   (let ((dir (javaimp-module-source-dir module)))
-     (and (file-accessible-directory-p dir)
-         (javaimp--get-directory-classes dir nil)))
-   ;; test source dir
-   (let ((dir (javaimp-module-test-source-dir module)))
-     (and (file-accessible-directory-p dir)
-         (javaimp--get-directory-classes dir nil)))))
+   ;; source dirs
+   (seq-mapcat (lambda (dir)
+                 (and (file-accessible-directory-p dir)
+                     (javaimp--get-directory-classes dir nil)))
+               (javaimp-module-source-dirs module))
+   ;; additional source dirs
+   (seq-mapcat (lambda (rel-dir)
+                 (let ((dir (concat (javaimp-module-build-dir module)
+                                    (file-name-as-directory rel-dir))))
+                  (and (file-accessible-directory-p dir)
+                       (javaimp--get-directory-classes dir nil))))
+               javaimp-additional-source-dirs)))
 
 (defun javaimp--get-directory-classes (dir prefix)
   (append
@@ -694,7 +436,7 @@ is `ordinary' or `static'.  Interactively, NEW-IMPORTS is 
nil."
                                (string< (caar first) (caar second))
                              (< (cdr first) (cdr second))))))
              (javaimp--insert-imports with-order)))
-      (message "Nothing to organize!")))))
+        (message "Nothing to organize!")))))
 
 (defun javaimp--parse-imports ()
   "Returns (FIRST LAST . IMPORTS)"
@@ -747,6 +489,14 @@ is `ordinary' or `static'.  Interactively, NEW-IMPORTS is 
nil."
        (insert (format pattern (caar import)) ?\n)
        (setq last-order order)))))
 
+(defun javaimp-reset (arg)
+  "Forget loaded trees state.  With prefix arg, also reset jars
+cache."
+  (interactive "P")
+  (setq javaimp-project-forest nil)
+  (when arg
+    (setq javaimp-cached-jars nil)))
+
 (provide 'javaimp)
 
 ;;; javaimp.el ends here



reply via email to

[Prev in Thread] Current Thread [Next in Thread]