]> code.delx.au - gnu-emacs-elpa/blob - packages/javaimp/javaimp.el
Merge commit '11fbd70347a8cc62817c6d4ebf2291471ebdd607' from avy
[gnu-emacs-elpa] / packages / javaimp / javaimp.el
1 ;;; javaimp.el --- Add and reorder Java import statements in Maven projects -*- lexical-binding: t; -*-
2
3 ;; Copyright (C) 2014, 2015 Free Software Foundation, Inc.
4
5 ;; Author: Filipp Gunbin <fgunbin@fastmail.fm>
6 ;; Maintainer: Filipp Gunbin <fgunbin@fastmail.fm>
7 ;; Version: 0.6
8 ;; Keywords: java, maven, programming
9
10 ;;; Commentary:
11
12 ;; Allows to manage Java import statements in Maven projects.
13 ;;
14 ;; Quick start: customize `javaimp-import-group-alist', `javaimp-jdk-home'
15 ;; and call `javaimp-maven-visit-root', then in a Java buffer visiting a
16 ;; file under that module or one of its submodules call
17 ;; `javaimp-organize-imports' or `javaimp-add-import'. `javaimp-add-import'
18 ;; will provide you a helpful completion, and the default value (the one
19 ;; you'll get if you hit `M-n' in the minibuffer) is the symbol under point,
20 ;; so usually it's enough to hit `M-n', then add some starting letters of a
21 ;; package and hit `TAB'. The module does not add all needed imports
22 ;; automatically! It only helps you to quickly add imports when stepping
23 ;; through compilation errors.
24 ;;
25 ;; If Maven failed, you can see its output in the buffer named by
26 ;; `javaimp-debug-buf-name' (default is "*javaimp-debug*").
27 ;;
28 ;; Contents of jar files and Maven project structures (pom.xml) are cached,
29 ;; so usually only first command should take a considerable amount of time
30 ;; to complete. When it is detected that a particular jar or pom.xml file's
31 ;; timestamp changed, it is re-read and cache is updated.
32 ;;
33 ;; Details on variables.
34 ;;
35 ;; `javaimp-import-group-alist' defines the order of import statement
36 ;; groups. By default java.* and javax.* imports are assigned an order of
37 ;; 10, which is low, so it puts those imports at the beginning. Your
38 ;; project's imports typically should come after, so the sample config below
39 ;; sets 80 for them.
40 ;;
41 ;; `javaimp-jdk-home' is a path for JDK. It is used to scan JDK jars.
42 ;; Usually you will need to set this.
43 ;;
44 ;; `javaimp-additional-source-dirs' is a list specifying directories where
45 ;; additional (e.g. generated) source files reside. Each directory is a
46 ;; relative path from ${project.build.directory} project property value.
47 ;;
48 ;; `javaimp-mvn-program' defines path of the `mvn' program. Use if it's
49 ;; not on `exec-path'.
50 ;;
51 ;; `javaimp-cygpath-program' defines path of the `cygpath' program (applies
52 ;; to Cygwin only, of course). Use if it's not on `exec-path'.
53 ;;
54 ;; `javaimp-jar-program' defines path of the `jar' program. Use if it's
55 ;; not on `exec-path'.
56 ;;
57 ;; Details on commands.
58 ;;
59 ;; `javaimp-maven-visit-root' is the first command you should issue to
60 ;; use this module. It reads the pom structure recursively and records
61 ;; which files belong to which module. Maven help:effective-pom command is
62 ;; used to do that.
63 ;;
64 ;; `javaimp-organize-imports' groups import statement and writes those
65 ;; group according to the value of `javaimp-import-group-alist'. Imports
66 ;; which are not matched by any regexp in that variable are assigned a
67 ;; default order defined by `javaimp-import-default-order' (50 by default).
68 ;;
69 ;; Sample setup (put this into your .emacs):
70 ;;
71 ;; (require 'javaimp)
72 ;;
73 ;; (add-to-list 'javaimp-import-group-alist '("\\`\\(ru\\.yota\\.\\|tv\\.okko\\.\\)" . 80))
74 ;;
75 ;; (setq javaimp-jdk-home (getenv "JAVA_HOME"))
76 ;; (setq javaimp-include-current-project-classes t)
77 ;; (setq javaimp-additional-source-dirs '("generated-sources/thrift"))
78 ;;
79 ;; (add-hook 'java-mode-hook
80 ;; (lambda ()
81 ;; (local-set-key "\C-ci" 'javaimp-add-import)
82 ;; (local-set-key "\C-co" 'javaimp-organize-imports)))
83 ;;
84 ;;
85 ;; TODO:
86 ;;
87 ;; Support adding static imports by giving a prefix argument to
88 ;; `javaimp-add-import'.
89 ;;
90 ;; Use functions `cygwin-convert-file-name-from-windows' and
91 ;; `cygwin-convert-file-name-to-windows' when they are available instead of
92 ;; calling `cygpath'. See
93 ;; https://cygwin.com/ml/cygwin/2013-03/msg00228.html.
94
95
96 ;;; Code:
97
98 \f
99 ;;; User options
100
101 (defgroup javaimp ()
102 "Add and reorder Java import statements in Maven projects.")
103
104 (defcustom javaimp-import-group-alist '(("\\`javax?\\." . 10))
105 "Specifies how to group classes and how to order resulting
106 groups in the imports list.
107
108 Each element should be of the form `(CLASSNAME-REGEXP . ORDER)'
109 where `CLASSNAME-REGEXP' is a regexp matching the fully qualified
110 class name.
111
112 The order of classes which were not matched is defined by
113 `javaimp-import-default-order'.")
114
115 (defcustom javaimp-import-default-order 50
116 "Defines the order of classes which were not matched by
117 `javaimp-import-group-alist'")
118
119 (defcustom javaimp-jdk-home nil
120 "Path to the JDK")
121
122 (defcustom javaimp-additional-source-dirs nil
123 "List of directories where additional (e.g. generated)
124 source files reside.
125
126 Each directory is a relative path from ${project.build.directory} project
127 property value.
128
129 Typically you would check documentation for a Maven plugin, look
130 at the parameter's default value there and add it to this list.
131
132 E.g. \"${project.build.directory}/generated-sources/<plugin_name>\"
133 becomes \"generated-sources/<plugin_name>\" (note the absence
134 of the leading slash.
135
136 Custom values set in plugin configuration in pom.xml are not
137 supported yet.")
138
139 (defcustom javaimp-mvn-program "mvn"
140 "Path to the `mvn' program")
141
142 (defcustom javaimp-cygpath-program "cygpath"
143 "Path to the `cygpath' program")
144
145 (defcustom javaimp-jar-program "jar"
146 "Path to the `jar' program")
147
148 (defcustom javaimp-include-current-project-classes t
149 "If non-nil, current project's classes are included into completion
150 alternatives.
151
152 Only top-level classes are included.")
153
154 \f
155 ;;; Variables and constants
156
157 (defvar javaimp-maven-root-modules nil
158 "Loaded root Maven modules")
159
160 (defvar javaimp-jar-classes-cache nil
161 "Jar classes cache")
162
163 (defconst javaimp-debug-buf-name "*javaimp-debug*")
164
165 \f
166 ;;; Dealing with XML
167
168 (defun javaimp-xml-child-list (xml-tree child-name)
169 "Returns list of children of XML-TREE filtered by CHILD-NAME"
170 (let (result)
171 (dolist (child (cddr xml-tree) result)
172 (when (and (listp child)
173 (eq (car child) child-name))
174 (push child result)))))
175
176 (defun javaimp-xml-child (name el)
177 "Returns a child of EL named by symbol NAME"
178 (assq name (cddr el)))
179
180 (defun javaimp-xml-first-child (el)
181 "Returns a first child of EL"
182 (car (cddr el)))
183
184 \f
185 ;; A module is represented as a list of the form `(ARTIFACT POM-FILE
186 ;; SOURCE-DIR TEST-SOURCE-DIR BUILD-DIR POM-FILE-MOD-TS PARENT PARENT-TS)'.
187
188 (defsubst javaimp-make-mod (artifact pom-file source-dir
189 test-source-dir build-dir
190 pom-file-mod-ts jars-list
191 parent parent-ts)
192 (list artifact pom-file source-dir test-source-dir build-dir
193 pom-file-mod-ts jars-list parent parent-ts))
194
195 (defsubst javaimp-get-mod-artifact (module)
196 (nth 0 module))
197
198 (defsubst javaimp-get-mod-pom-file (module)
199 (nth 1 module))
200
201 (defsubst javaimp-get-mod-source-dir (module)
202 (nth 2 module))
203
204 (defsubst javaimp-get-mod-test-source-dir (module)
205 (nth 3 module))
206
207 (defsubst javaimp-get-mod-build-dir (module)
208 (nth 4 module))
209
210 (defsubst javaimp-get-mod-pom-mod-ts (module)
211 (nth 5 module))
212 (defsubst javaimp-set-mod-pom-mod-ts (module value)
213 (setcar (nthcdr 5 module) value))
214
215 (defsubst javaimp-get-mod-pom-deps (module)
216 (nth 6 module))
217 (defsubst javaimp-set-mod-pom-deps (module value)
218 (setcar (nthcdr 6 module) value))
219
220 (defsubst javaimp-get-mod-parent (module)
221 (nth 7 module))
222 (defsubst javaimp-set-mod-parent (module value)
223 (setcar (nthcdr 7 module) value))
224
225 (defsubst javaimp-get-mod-parent-ts (module)
226 (nth 8 module))
227 (defsubst javaimp-set-mod-parent-ts (module value)
228 (setcar (nthcdr 8 module) value))
229
230 \f
231 ;; An artifact is represented as a list: (GROUP-ID ARTIFACT-ID VERSION).
232
233 ;; FIXME: use cl-defstruct!
234
235 (defun javaimp-make-artifact (group-id artifact-id version)
236 (list group-id artifact-id version))
237
238 (defun javaimp-artifact-group-id (artifact)
239 (car artifact))
240
241 (defun javaimp-artifact-artifact-id (artifact)
242 (cadr artifact))
243
244 (defun javaimp-artifact-version (artifact)
245 (nth 2 artifact))
246
247 (defun javaimp-artifact-to-string (artifact)
248 (format "%s:%s:%s"
249 (javaimp-artifact-artifact-id artifact)
250 (javaimp-artifact-group-id artifact)
251 (javaimp-artifact-version artifact))) ;FIXME: `artifact' is not a function!
252
253 (defun javaimp-parse-artifact (artifact)
254 (apply #'javaimp-make-artifact (split-string artifact ":")))
255
256
257 \f
258 ;; A jar is represented as follows: `(JAR-PATH JAR-MOD-TS . CLASSES-LIST).
259
260 (defsubst javaimp-make-jar (jar-path jar-mod-ts classes-list)
261 (cons jar-path (cons jar-mod-ts classes-list)))
262
263 (defsubst javaimp-get-jar-path (jar)
264 (car jar))
265
266 (defsubst javaimp-get-jar-mod-ts (jar)
267 (cadr jar))
268
269 (defsubst javaimp-set-jar-mod-ts (jar value)
270 (setcar (cdr jar) value))
271
272 (defsubst javaimp-get-jar-classes-list (jar)
273 (cddr jar))
274
275 (defsubst javaimp-set-jar-classes-list (jar value)
276 (setcdr (cdr jar) value))
277
278 \f
279 ;;; Loading maven projects tree
280
281 ;;;###autoload
282 (defun javaimp-maven-visit-root (path)
283 "Loads all modules starting from root module identified by
284 PATH. PATH should point to a directory."
285 (interactive "DVisit maven root project: ")
286 (let ((root-pom (expand-file-name
287 (concat (file-name-as-directory path) "pom.xml")))
288 modules existing-module)
289 (unless (file-readable-p root-pom)
290 (error "Cannot read root pom: %s" root-pom))
291 (setq modules (javaimp-maven-load-module-tree root-pom))
292 ;; if a root module with such path is already loaded, replace its
293 ;; modules
294 (setq existing-module (assoc root-pom javaimp-maven-root-modules))
295 (if existing-module
296 (setcdr existing-module modules)
297 (push (cons root-pom modules) javaimp-maven-root-modules))
298 (message "Loaded modules for %s" path)))
299
300 (defun javaimp-get-projects (xml-tree)
301 (cond ((assq 'projects xml-tree)
302 (javaimp-xml-child-list (assq 'projects xml-tree) 'project))
303 ((assq 'project xml-tree)
304 (list (assq 'project xml-tree)))
305 (t
306 (error "Cannot find projects in mvn output"))))
307
308 (defun javaimp-maven-load-module-tree (pom)
309 "Returns an alist of all Maven modules in a hierarchy starting
310 with POM"
311 (message "Loading root pom %s..." pom)
312 (javaimp-call-mvn
313 pom "help:effective-pom"
314 (lambda ()
315 (let (xml-start-pos xml-end-pos)
316 ;; find where we should start parsing XML
317 (goto-char (point-min))
318 (re-search-forward "<\\?xml\\|<projects?")
319 (setq xml-start-pos (match-beginning 0))
320 ;; determine the start tag
321 (goto-char (point-min))
322 (re-search-forward "<\\(projects?\\)")
323 ;; find closing tag which is also the end of the region to parse
324 (search-forward (concat "</" (match-string 1) ">"))
325 (setq xml-end-pos (match-end 0))
326 ;; parse
327 (let ((artifact-pomfile-alist
328 (javaimp-build-artifact-pomfile-alist (list pom)))
329 (children (javaimp-get-projects
330 (xml-parse-region xml-start-pos xml-end-pos))))
331 (javaimp-maven-build-children children artifact-pomfile-alist))))))
332
333 (defun javaimp-make-artifact-from-xml (node)
334 (javaimp-make-artifact
335 (javaimp-xml-first-child (javaimp-xml-child 'groupId node))
336 (javaimp-xml-first-child (javaimp-xml-child 'artifactId node))
337 (javaimp-xml-first-child (javaimp-xml-child 'version node))))
338
339 (defun javaimp-get-pom-file-path-lax (artifact artifact-pomfile-alist)
340 (assoc-default
341 artifact artifact-pomfile-alist
342 (lambda (tested target)
343 (or (equal target tested)
344 (equal (javaimp-artifact-artifact-id target)
345 (javaimp-artifact-artifact-id tested))))))
346
347 (defun javaimp-maven-build-children (projects artifact-pomfile-alist)
348 (let (result)
349 (dolist (proj projects result)
350 (let* ((artifact (javaimp-make-artifact-from-xml proj))
351 (pom-file-path (javaimp-get-pom-file-path-lax
352 artifact artifact-pomfile-alist))
353 (build (javaimp-xml-child 'build proj))
354 (source-dir (javaimp-xml-first-child
355 (javaimp-xml-child 'sourceDirectory build)))
356 (test-source-dir (javaimp-xml-first-child
357 (javaimp-xml-child 'testSourceDirectory
358 build)))
359 (build-dir (javaimp-xml-first-child
360 (javaimp-xml-child 'directory build)))
361 (parent (javaimp-make-artifact-from-xml
362 (javaimp-xml-child 'parent proj))))
363 (push (javaimp-make-mod
364 artifact
365 pom-file-path
366 (file-name-as-directory
367 (if (eq system-type 'cygwin)
368 (car (process-lines javaimp-cygpath-program "-u"
369 source-dir))
370 source-dir))
371 (file-name-as-directory
372 (if (eq system-type 'cygwin)
373 (car (process-lines javaimp-cygpath-program "-u"
374 test-source-dir))
375 test-source-dir))
376 (file-name-as-directory
377 (if (eq system-type 'cygwin)
378 (car (process-lines javaimp-cygpath-program "-u"
379 build-dir))
380 build-dir))
381 nil nil parent nil)
382 result)))))
383
384 (defun javaimp-build-artifact-pomfile-alist (pom-file-list)
385 "Recursively builds an alist where each element is of the
386 form (\"ARTIFACT\" . \"POM-FILE-PATH\"). This is needed because
387 there is no pom file path in the output of `mvn
388 help:effective-pom'. Each pom file path in POM-FILE-LIST should
389 be in platform's default format."
390 (when pom-file-list
391 (let ((pom-file (car pom-file-list))
392 xml-tree project)
393 (message "Saving artifact id -> pom file mapping for %s" pom-file)
394 (with-temp-buffer
395 (insert-file-contents pom-file)
396 (setq xml-tree (xml-parse-region (point-min) (point-max))))
397 (setq project (if (assq 'top xml-tree)
398 (assq 'project (cddr (assq 'top xml-tree)))
399 (assq 'project xml-tree)))
400 (cons
401 ;; this pom
402 (cons (javaimp-make-artifact-from-xml project) pom-file)
403 (append
404 ;; submodules
405 (javaimp-build-artifact-pomfile-alist
406 (mapcar (lambda (submodule)
407 (expand-file-name
408 (concat
409 ;; this pom's path
410 (file-name-directory pom-file)
411 ;; relative submodule directory
412 (file-name-as-directory
413 (let ((submodule-path (car (cddr submodule))))
414 (if (eq system-type 'cygwin)
415 (car (process-lines javaimp-cygpath-program "-u"
416 submodule-path))
417 submodule-path)))
418 ;; well-known file name
419 "pom.xml")))
420 (javaimp-xml-child-list (assq 'modules (cddr project)) 'module)))
421 ;; rest items
422 (javaimp-build-artifact-pomfile-alist (cdr pom-file-list)))))))
423
424 (defun javaimp-call-mvn (pom-file target handler)
425 "Runs Maven target TARGET on POM-FILE, then calls HANDLER in
426 the temporary buffer and returns its result"
427 (message "Calling \"mvn %s\" on pom: %s" target pom-file)
428 (with-temp-buffer
429 (let* ((pom-file (if (eq system-type 'cygwin)
430 (car (process-lines javaimp-cygpath-program
431 "-m" pom-file))
432 pom-file))
433 (status
434 ;; FIXME on GNU/Linux Maven strangely outputs ^M chars. Check
435 ;; also jar output with the same var binding below.
436 (let ((coding-system-for-read (when (eq system-type 'cygwin) 'utf-8-dos)))
437 (process-file javaimp-mvn-program nil t nil "-f" pom-file target)))
438 (output-buf (current-buffer)))
439 (with-current-buffer (get-buffer-create javaimp-debug-buf-name)
440 (erase-buffer)
441 (insert-buffer-substring output-buf))
442 (unless (and (numberp status) (= status 0))
443 (error "Maven target \"%s\" failed with status \"%s\""
444 target status))
445 (funcall handler))))
446
447 \f
448 ;;; Reading and caching dependencies
449
450 (defun javaimp-maven-fetch-module-deps (module)
451 "Returns list of dependency jars for MODULE"
452 (javaimp-call-mvn
453 (javaimp-get-mod-pom-file module) "dependency:build-classpath"
454 (lambda ()
455 (let (deps-line)
456 (goto-char (point-min))
457 (search-forward "Dependencies classpath:")
458 (forward-line 1)
459 (setq deps-line (thing-at-point 'line))
460 (when (eq system-type 'cygwin)
461 (setq deps-line (car (process-lines javaimp-cygpath-program
462 "-up"
463 deps-line))))
464 (split-string deps-line (concat "[" path-separator "\n" "]+") t)))))
465
466 (defun javaimp-get-file-ts (file)
467 (nth 5 (file-attributes file)))
468
469 (defun javaimp-any-file-ts-updated (files)
470 (if (null files)
471 nil
472 (let ((curr-ts (javaimp-get-file-ts (car (car files))))
473 (last-ts (cdr (car files))))
474 (or (null last-ts) ; reading for the first time?
475 (not (equal (float-time curr-ts) (float-time last-ts)))
476 (javaimp-any-file-ts-updated (cdr files))))))
477
478 (defun javaimp-get-dep-jars-cached (module parent)
479 "Returns a list of dependency jar file paths for a MODULE.
480 Both MODULE and PARENT poms are checked for updates because
481 PARENT pom may have some versions which are inherited by the
482 MODULE."
483 (when (javaimp-any-file-ts-updated
484 (remq nil (list (cons (javaimp-get-mod-pom-file module)
485 (javaimp-get-mod-pom-mod-ts module))
486 (when parent
487 (cons
488 (javaimp-get-mod-pom-file parent)
489 ;; here we check the saved parent ts because it
490 ;; matters what version we had when we were
491 ;; reloading this pom the last time
492 (javaimp-get-mod-parent-ts module))))))
493 ;; (re-)fetch dependencies
494 (javaimp-set-mod-pom-deps
495 module (javaimp-maven-fetch-module-deps module))
496 ;; update timestamps
497 (javaimp-set-mod-pom-mod-ts
498 module (javaimp-get-file-ts (javaimp-get-mod-pom-file module)))
499 (when parent
500 (javaimp-set-mod-parent-ts
501 module (javaimp-get-file-ts (javaimp-get-mod-pom-file parent)))))
502 (javaimp-get-mod-pom-deps module))
503
504 (defun javaimp-get-jdk-jars ()
505 "Returns list of jars from the jre/lib subdirectory of the JDK
506 directory"
507 (when javaimp-jdk-home
508 (directory-files (concat (file-name-as-directory javaimp-jdk-home)
509 (file-name-as-directory "jre/lib"))
510 t "\\.jar$")))
511
512 (defun javaimp-get-jar-classes-cached (jar)
513 (let ((current-jar-mod-ts
514 (nth 5 (file-attributes (javaimp-get-jar-path jar)))))
515 (unless (equal (float-time (javaimp-get-jar-mod-ts jar))
516 (float-time current-jar-mod-ts))
517 (javaimp-set-jar-classes-list jar (javaimp-fetch-jar-classes jar))
518 (javaimp-set-jar-mod-ts jar current-jar-mod-ts))
519 (javaimp-get-jar-classes-list jar)))
520
521 (defun javaimp-fetch-jar-classes (jar)
522 (let ((jar-file (javaimp-get-jar-path jar))
523 result)
524 (message "Reading classes in jar: %s" jar-file)
525 (with-temp-buffer
526 (let ((jar-file (if (eq system-type 'cygwin)
527 (car (process-lines javaimp-cygpath-program
528 "-m" jar-file))
529 jar-file))
530 (coding-system-for-read (when (eq system-type 'cygwin) 'utf-8-dos)))
531 (process-file javaimp-jar-program nil t nil "-tf" jar-file))
532 (goto-char (point-min))
533 (while (re-search-forward "^\\(.+\\)\\.class$" nil t)
534 (push (replace-regexp-in-string "[/$]" "." (match-string 1))
535 result))
536 result)))
537
538 (defun javaimp-collect-jar-classes (jar-paths)
539 (let (result jar)
540 (dolist (jar-path jar-paths result)
541 (setq jar (assoc jar-path javaimp-jar-classes-cache))
542 (unless jar
543 (setq jar (javaimp-make-jar jar-path nil nil))
544 (push jar javaimp-jar-classes-cache))
545 (setq result (append (javaimp-get-jar-classes-cached jar) result)))))
546
547 (defun javaimp-get-module-from-root (roots predicate)
548 (if (null roots)
549 nil
550 (let ((result (javaimp-get-module (cdr (car roots)) predicate)))
551 (or result
552 (javaimp-get-module-from-root (cdr roots) predicate)))))
553
554 (defun javaimp-get-module (modules predicate)
555 (cond ((null modules)
556 nil)
557 ((funcall predicate (car modules))
558 (car modules))
559 (t
560 (javaimp-get-module (cdr modules) predicate))))
561
562 (defun javaimp-get-module-by-file (file)
563 (javaimp-get-module-from-root
564 javaimp-maven-root-modules
565 (lambda (mod)
566 (or (string-prefix-p (javaimp-get-mod-source-dir mod) file)
567 (string-prefix-p (javaimp-get-mod-test-source-dir mod) file)))))
568
569 (defun javaimp-get-module-by-artifact (artifact)
570 (javaimp-get-module-from-root
571 javaimp-maven-root-modules
572 (lambda (mod)
573 (equal (javaimp-get-mod-artifact mod) artifact))))
574
575 \f
576 ;;; Adding and organizing imports
577
578 ;;;###autoload
579 (defun javaimp-add-import (classname)
580 "Imports CLASSNAME in the current file. Interactively,
581 performs class name completion based on the current module's
582 dependencies, JDK jars and top-level classes in the current
583 module."
584 (interactive
585 (let* ((file (expand-file-name
586 (or buffer-file-name
587 (error "Buffer is not visiting a file!"))))
588 (module (or (javaimp-get-module-by-file file)
589 (error "Cannot determine module for file: %s" file)))
590 (parent (javaimp-get-module-by-artifact
591 (javaimp-get-mod-parent module))))
592 (list (completing-read
593 "Import: "
594 (append
595 (javaimp-collect-jar-classes
596 (append (javaimp-get-dep-jars-cached module parent)
597 (javaimp-get-jdk-jars)))
598 (and javaimp-include-current-project-classes
599 (javaimp-get-module-classes module)))
600 nil t nil nil (symbol-name (symbol-at-point))))))
601 (javaimp-organize-imports classname))
602
603 (defun javaimp-get-module-classes (module)
604 "Scans current project and returns a list of top-level classes in both the
605 source directory and test source directory"
606 (let ((src-dir (javaimp-get-mod-source-dir module))
607 (test-src-dir (javaimp-get-mod-test-source-dir module))
608 (build-dir (javaimp-get-mod-build-dir module)))
609 (append
610 (and javaimp-additional-source-dirs
611 (seq-mapcat
612 (lambda (rel-dir)
613 (let ((dir (file-name-as-directory (concat build-dir rel-dir))))
614 (and (file-accessible-directory-p dir)
615 (javaimp-get-directory-classes dir nil))))
616 javaimp-additional-source-dirs))
617 (and (file-accessible-directory-p test-src-dir)
618 (javaimp-get-directory-classes test-src-dir nil))
619 (and (file-accessible-directory-p src-dir)
620 (javaimp-get-directory-classes src-dir nil)))))
621
622 (defun javaimp-get-directory-classes (dir prefix)
623 "Returns the list of classes found in the directory DIR. PREFIX is the
624 initial package prefix."
625 (let (result)
626 ;; traverse subdirectories
627 (dolist (file (directory-files-and-attributes dir nil nil t))
628 (if (and (eq (cadr file) t)
629 (not (or (string= (car file) ".")
630 (string= (car file) ".."))))
631 (setq result
632 (append (javaimp-get-directory-classes
633 (concat dir (file-name-as-directory (car file)))
634 (concat prefix (car file) "."))
635 result))))
636 ;; add .java files in the current directory
637 (dolist (file (directory-files-and-attributes dir nil "\\.java\\'" t))
638 (unless (cadr file)
639 (push (concat prefix (file-name-sans-extension (car file))) result)))
640 result))
641
642 (defun javaimp-add-to-import-groups (new-class groups)
643 "Subroutine of `javaimp-organize-imports'"
644 (let* ((order (or (assoc-default new-class javaimp-import-group-alist
645 'string-match)
646 javaimp-import-default-order))
647 (group (assoc order groups)))
648 (if group
649 (progn
650 ;; add only if this class is not already there
651 (unless (member new-class (cdr group))
652 (setcdr group (cons new-class (cdr group))))
653 groups)
654 (cons (cons order (list new-class)) groups))))
655
656 (defun javaimp-insert-import-groups (groups static-p)
657 "Inserts all imports in GROUPS. Non-nil STATIC-P means that
658 all imports are static."
659 (when groups
660 (dolist (group (sort groups (lambda (g1 g2)
661 (< (car g1) (car g2)))))
662 (dolist (class (sort (cdr group) 'string<))
663 (insert (concat "import " (when static-p "static ") class ";\n")))
664 (insert ?\n))
665 ;; remove newline after the last group
666 (delete-char -1)))
667
668 ;;;###autoload
669 (defun javaimp-organize-imports (&rest new-classes)
670 "Groups and orders import statements in the current buffer. Groups are
671 formed and ordered according to `javaimp-import-group-alist'. Classes within a
672 single group are ordered in a lexicographic order. Optional NEW-CLASSES
673 argument is a list of additional classes to import."
674 (interactive)
675 (barf-if-buffer-read-only)
676 (save-excursion
677 (let (import-groups static-import-groups old-imports-start)
678 ;; existing imports
679 (goto-char (point-min))
680 (while (re-search-forward
681 "^\\s-*import\\s-+\\(static\\s-+\\)?\\([._[:word:]]+\\)"
682 nil t)
683 (if (null (match-string 1))
684 (setq import-groups
685 (javaimp-add-to-import-groups (match-string 2)
686 import-groups))
687 (setq static-import-groups
688 (javaimp-add-to-import-groups (match-string 2)
689 static-import-groups)))
690 (beginning-of-line)
691 (unless old-imports-start (setq old-imports-start (point)))
692 (delete-region (point) (line-beginning-position 2))
693 ;; delete whatever was between import statements
694 (when (/= (point) old-imports-start)
695 (delete-region old-imports-start (point))))
696 ;; new imports
697 (dolist (class new-classes)
698 (setq import-groups (javaimp-add-to-import-groups class import-groups)))
699 ;; insert all
700 (if (or import-groups static-import-groups)
701 (progn
702 ;; prepare the position
703 (cond (old-imports-start
704 ;; when there were any imports, do not touch blank lines
705 ;; before imports
706 (goto-char old-imports-start))
707 ((re-search-forward "^\\s-*package\\s-" nil t)
708 ;; when there is a package statement, insert one or two
709 ;; blank lines after it
710 (when (= (forward-line) 1) (insert ?\n)) ;; last line?
711 (insert ?\n))
712 (t
713 ;; otherwise, start at the bob, insert one empty line
714 ;; after point
715 (goto-char (point-min))
716 (insert ?\n)
717 (backward-char)))
718 (javaimp-insert-import-groups import-groups nil)
719 (and import-groups static-import-groups (insert ?\n))
720 (javaimp-insert-import-groups static-import-groups t))
721 (message "Nothing to organize")))))
722
723 ;;;###autoload
724 (defun javaimp-invalidate-jar-classes-cache ()
725 "Resets jar classes cache (debugging only)"
726 (interactive)
727 (setq javaimp-jar-classes-cache nil))
728
729 ;;;###autoload
730 (defun javaimp-forget-all-visited-modules ()
731 "Resets `javaimp-maven-root-modules' (debugging only)"
732 (interactive)
733 (setq javaimp-maven-root-modules nil))
734
735 ;;;###autoload
736 (defun javaimp-reset ()
737 "Resets all data (debugging only)"
738 (interactive)
739 (javaimp-forget-all-visited-modules)
740 (javaimp-invalidate-jar-classes-cache))
741
742 (provide 'javaimp)
743
744 ;;; javaimp.el ends here