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