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