1 ;;; javaimp.el --- Add and reorder Java import statements in Maven projects -*- lexical-binding: t; -*-
3 ;; Copyright (C) 2014, 2015, 2016 Free Software Foundation, Inc.
5 ;; Author: Filipp Gunbin <fgunbin@fastmail.fm>
6 ;; Maintainer: Filipp Gunbin <fgunbin@fastmail.fm>
8 ;; Keywords: java, maven, programming
12 ;; Allows to manage Java import statements in Maven projects.
16 ;; - customize `javaimp-import-group-alist'
18 ;; - call `javaimp-maven-visit-project', giving it the top-level project
19 ;; directory where pom.xml resides
21 ;; Then in a Java buffer visiting a file under that project or one of its
22 ;; submodules call `javaimp-organize-imports' or `javaimp-add-import'.
24 ;; This module does not add all needed imports automatically! It only helps
25 ;; you to quickly add imports when stepping through compilation errors.
30 ;; If Maven failed, you can see its output in the buffer named by
31 ;; `javaimp-debug-buf-name' (default is "*javaimp-debug*").
33 ;; Contents of jar files and Maven project structures (pom.xml) are cached,
34 ;; so usually only first command should take a considerable amount of time
35 ;; to complete. If a modules's pom.xml or any of its parents' pom.xml was
36 ;; changed (i.e. any of them was modified after information was loaded),
37 ;; `mvn dependency:build-classpath' is re-run on the current module. If a
38 ;; jar file was changed, its contents are re-read.
40 ;; If you make some changes which change project hierarchy, you should
41 ;; re-visit the parent again with `javaimp-maven-visit-project'.
43 ;; Currently inner classes are filtered out from completion alternatives.
44 ;; You can always import top-level class and use qualified name.
47 ;; Example of initialization:
51 ;; (add-to-list 'javaimp-import-group-alist
52 ;; '("\\`\\(my\\.company\\.\\|my\\.company2\\.\\)" . 80))
54 ;; (setq javaimp-additional-source-dirs '("generated-sources/thrift"))
56 ;; (add-hook 'java-mode-hook
58 ;; (local-set-key "\C-ci" 'javaimp-add-import)
59 ;; (local-set-key "\C-co" 'javaimp-organize-imports)))
64 ;; - use functions `cygwin-convert-file-name-from-windows' and
65 ;; `cygwin-convert-file-name-to-windows' when they are available instead of
66 ;; calling `cygpath'. See https://cygwin.com/ml/cygwin/2013-03/msg00228.html.
68 ;; - save/restore state
70 ;; - `javaimp-add-import': without prefix arg narrow alternatives by local name;
71 ;; with prefix arg include all classes in alternatives
84 "Add and reorder Java import statements in Maven projects")
86 (defcustom javaimp-import-group-alist '(("\\`javax?\\." . 10))
87 "Specifies how to group classes and how to order resulting
88 groups in the imports list.
90 Each element should be of the form `(CLASSNAME-REGEXP . ORDER)'
91 where `CLASSNAME-REGEXP' is a regexp matching the fully qualified
92 class name. Lowest-order groups are placed earlier.
94 The order of classes which were not matched is defined by
95 `javaimp-import-default-order'.")
97 (defcustom javaimp-import-default-order 50
98 "Defines the order of classes which were not matched by
99 `javaimp-import-group-alist'")
101 (defcustom javaimp-jdk-home (getenv "JAVA_HOME")
102 "Path to the JDK. It is used to find JDK jars to scan. By
103 default, it is set from the JAVA_HOME environment variable.")
105 (defcustom javaimp-additional-source-dirs nil
106 "List of directories where additional (e.g. generated)
109 Each directory is a relative path from ${project.build.directory} project
112 Typically you would check documentation for a Maven plugin, look
113 at the parameter's default value there and add it to this list.
115 E.g. \"${project.build.directory}/generated-sources/<plugin_name>\"
116 becomes \"generated-sources/<plugin_name>\" (note the absence
117 of the leading slash.
119 Custom values set in plugin configuration in pom.xml are not
122 (defcustom javaimp-mvn-program "mvn"
123 "Path to the `mvn' program. Customize it if the program is not
126 (defcustom javaimp-cygpath-program
127 (if (eq system-type 'cygwin) "cygpath")
128 "Path to the `cygpath' program (Cygwin only). Customize it if
129 the program is not on `exec-path'.")
131 (defcustom javaimp-jar-program "jar"
132 "Path to the `jar' program used to read contents of jar files.
133 Customize it if the program is not on `exec-path'.")
135 (defcustom javaimp-include-current-module-classes t
136 "If non-nil, current module's classes are included into
137 completion alternatives. `javaimp-add-import' will find all java
138 files in the current project and add their fully-qualified names
139 to the completion alternatives list.")
142 ;; Variables and constants
144 (defvar javaimp-project-forest nil
147 (defvar javaimp-cached-jars nil
148 "Alist of cached jars. Each element is of the form (FILE
151 (defconst javaimp-debug-buf-name "*javaimp-debug*")
155 (cl-defstruct javaimp-node
156 parent children contents)
158 (cl-defstruct javaimp-module
163 source-dir test-source-dir build-dir
168 (cl-defstruct javaimp-id
169 group artifact version)
171 (cl-defstruct javaimp-cached-jar
172 file read-ts classes)
177 (defun javaimp--xml-children (xml-tree child-name)
178 "Returns list of children of XML-TREE filtered by CHILD-NAME"
179 (seq-filter (lambda (child)
181 (eq (car child) child-name)))
184 (defun javaimp--xml-child (name el)
185 "Returns a child of EL named by symbol NAME"
186 (assq name (cddr el)))
188 (defun javaimp--xml-first-child (el)
189 "Returns a first child of EL"
192 (defun javaimp--get-file-ts (file)
193 (nth 5 (file-attributes file)))
195 (defun javaimp--get-jdk-jars ()
198 (concat (file-name-as-directory javaimp-jdk-home)
199 (file-name-as-directory "jre")
200 (file-name-as-directory "lib"))))
201 (directory-files jre-lib-dir t "\\.jar\\'"))))
203 (defun javaimp-cygpath-convert-maybe (path &optional mode is-really-path)
204 "On Cygwin, converts PATH using cygpath according to MODE and
205 IS-REALLY-PATH. If MODE is `unix' (the default), adds -u switch.
206 If MODE is `windows', adds -m switch. If `is-really-path' is
207 non-nil, adds `-p' switch. On other systems, PATH is returned
209 (if (eq system-type 'cygwin)
211 (unless mode (setq mode 'unix))
213 (push (cond ((eq mode 'unix) "-u")
214 ((eq mode 'windows) "-m")
215 (t (error "Invalid mode: %s" mode)))
217 (and is-really-path (push "-p" args))
219 (car (apply #'process-lines javaimp-cygpath-program args))))
226 (defun javaimp-maven-visit-project (path)
227 "Loads a project and its submodules. PATH should point to a
228 directory containing pom.xml.
230 Calls `mvn help:effective-pom' on the pom.xml in the PATH, reads
231 project structure from the output and records which files belong
232 to which modules and other module information.
234 After being processed by this command, the module tree becomes
235 known to javaimp and `javaimp-add-import' maybe called inside any
237 (interactive "DVisit maven project: ")
238 (let ((file (expand-file-name
239 (concat (file-name-as-directory path) "pom.xml"))))
240 (unless (file-readable-p file)
241 (error "Cannot read file: %s" file))
242 ;; delete previous loaded tree, if any
243 (setq javaimp-project-forest
244 (seq-remove (lambda (tree)
245 (equal (javaimp-module-file (javaimp-node-contents tree))
247 javaimp-project-forest))
248 (let ((tree (javaimp--maven-xml-load-tree file)))
250 (push tree javaimp-project-forest)))
251 (message "Loaded tree for %s" file)))
254 ;; Maven XML routines
256 (defun javaimp--maven-xml-load-tree (file)
257 "Invokes `mvn help:effective-pom' on FILE and using its output
258 creates a tree of Maven projects starting from FILE. Children
259 which link to the parent via the <parent> element are inheriting
260 children and are also included. Subordinate modules with no
261 inheritance are not included."
262 (let ((xml-tree (javaimp--maven-xml-read-effective-pom file)))
263 (cond ((assq 'project xml-tree)
264 (let ((project-elt (assq 'project xml-tree))
265 (submodules (javaimp--xml-children
266 (javaimp--xml-child 'modules project-elt) 'module)))
269 (message "Independent submodules: %s"
270 (mapconcat #'javaimp--xml-first-child submodules ", ")))
271 (let ((module (javaimp--maven-xml-parse-module project-elt)))
272 (javaimp--maven-build-tree
273 (javaimp-module-id module) nil (list module) file))))
274 ((assq 'projects xml-tree)
275 ;; we have are inheriting children - they and their children, if
276 ;; any, are listed in a linear list
277 (let* ((project-elts (javaimp--xml-children
278 (assq 'projects xml-tree) 'project))
279 (all-modules (mapcar #'javaimp--maven-xml-parse-module project-elts)))
280 (message "Total modules: %d" (length all-modules))
281 (javaimp--maven-build-tree
282 (javaimp-module-id (car all-modules)) nil all-modules file)))
284 ;; neither <project> nor <projects> - error
285 (error "Invalid `help:effective-pom' output")))))
287 (defun javaimp--maven-xml-read-effective-pom (pom)
288 "Calls `mvn help:effective:pom and returns XML parse tree"
289 (message "Loading root pom %s..." pom)
291 pom "help:effective-pom"
296 (goto-char (point-min))
297 (re-search-forward "<\\?xml\\|<projects?")
298 (match-beginning 0))))
302 (goto-char (point-min))
303 (re-search-forward "<\\(projects?\\)")
304 ;; corresponding closing tag is the end of parse region
305 (search-forward (concat "</" (match-string 1) ">"))
307 (xml-parse-region xml-start-pos xml-end-pos)))))
309 (defun javaimp--maven-xml-parse-module (project-elt)
310 (let ((build-elt (javaimp--xml-child 'build project-elt)))
312 :id (javaimp--maven-xml-extract-id project-elt)
313 :parent-id (javaimp--maven-xml-extract-id (javaimp--xml-child 'parent project-elt))
314 ;; we set `file' slot later because raw <project> element does not contain
315 ;; pom file path, so we need to construct it during tree construction
317 :final-name (javaimp--xml-first-child
318 (javaimp--xml-child 'finalName build-elt))
319 :packaging (javaimp--xml-first-child
320 (javaimp--xml-child 'packaging project-elt))
321 :source-dir (file-name-as-directory
322 (javaimp-cygpath-convert-maybe
323 (javaimp--xml-first-child
324 (javaimp--xml-child 'sourceDirectory build-elt))))
325 :test-source-dir (file-name-as-directory
326 (javaimp-cygpath-convert-maybe
327 (javaimp--xml-first-child
328 (javaimp--xml-child 'testSourceDirectory build-elt))))
329 :build-dir (file-name-as-directory
330 (javaimp-cygpath-convert-maybe
331 (javaimp--xml-first-child (javaimp--xml-child 'directory build-elt))))
332 :modules (mapcar (lambda (module-elt)
333 (javaimp--xml-first-child module-elt))
334 (javaimp--xml-children (javaimp--xml-child 'modules project-elt) 'module))
335 :dep-jars nil ; dep-jars is initialized lazily on demand
336 :load-ts (current-time))))
338 (defun javaimp--maven-xml-extract-id (elt)
340 :group (javaimp--xml-first-child (javaimp--xml-child 'groupId elt))
341 :artifact (javaimp--xml-first-child (javaimp--xml-child 'artifactId elt))
342 :version (javaimp--xml-first-child (javaimp--xml-child 'version elt))))
344 (defun javaimp--maven-xml-file-matches (file id parent-id)
345 (let* ((xml-tree (with-temp-buffer
346 (insert-file-contents file)
347 (xml-parse-region (point-min) (point-max))))
348 (project-elt (assq 'project xml-tree))
349 (tested-id (javaimp--maven-xml-extract-id project-elt))
350 (tested-parent-id (javaimp--maven-xml-extract-id (assq 'parent project-elt))))
351 ;; seems that the only mandatory component in tested ids is artifact, while
352 ;; group and version may be inherited and thus not presented in pom.xml
353 (let ((test (if (or (null (javaimp-id-group tested-id))
354 (null (javaimp-id-version tested-id))
355 (null (javaimp-id-group tested-parent-id))
356 (null (javaimp-id-version tested-parent-id)))
358 (message "File %s contains incomplete id, using lax match" file)
359 (lambda (first second)
360 (equal (javaimp-id-artifact first) (javaimp-id-artifact second))))
362 (and (funcall test tested-id id)
363 (funcall test tested-parent-id parent-id)))))
368 (defun javaimp--maven-call (pom-file target handler)
369 "Runs Maven target TARGET on POM-FILE, then calls HANDLER in
370 the temporary buffer and returns its result"
371 (message "Calling \"mvn %s\" on pom: %s" target pom-file)
373 (let* ((pom-file (javaimp-cygpath-convert-maybe pom-file))
375 ;; TODO check
in Maven output on Gnu/Linux
376 (let ((coding-system-for-read
377 (if (eq system-type 'cygwin) 'utf-8-dos)))
378 (process-file javaimp-mvn-program nil t nil "-f" pom-file target)))
379 (buf (current-buffer)))
380 (with-current-buffer (get-buffer-create javaimp-debug-buf-name)
382 (insert-buffer-substring buf))
383 (or (and (numberp status) (= status 0))
384 (error "Maven target \"%s\" failed with status \"%s\"" target status))
385 (goto-char (point-min))
388 (defun javaimp--maven-build-tree (id parent-node all-modules file)
389 (message "Building tree for project: %s" id)
390 (let ((this (or (seq-find (lambda (m) (equal (javaimp-module-id m) id))
392 (error "Cannot find module %s!" id)))
393 ;; although each real parent has <modules> section, more reliable
394 ;; way to build hirarchy is to analyze <parent> node in each child
395 (children (seq-filter (lambda (m) (equal (javaimp-module-parent-id m) id))
397 (if (and (null children)
398 (string= (javaimp-module-packaging this) "pom"))
399 (progn (message "Skipping empty aggregate module %s" (javaimp-module-id this))
401 ;; here we can finally set the `file' slot as the path is known at
403 (setf (javaimp-module-file this) file)
405 (let ((this-node (make-javaimp-node
409 (setf (javaimp-node-children this-node)
410 (mapcar (lambda (child)
412 (javaimp--maven-get-submodule-file
413 child file (javaimp-module-modules this))))
414 (javaimp--maven-build-tree
415 (javaimp-module-id child) this-node all-modules child-file)))
419 (defun javaimp--maven-get-submodule-file (submodule parent-file rel-paths-from-parent)
420 ;; seems that the only reliable way to match a module parsed from
421 ;; <project> element with module relative path taken from <modules> is to
422 ;; visit pom and check that id and parent-id matches
423 (let* ((parent-dir (file-name-directory parent-file))
424 (files (mapcar (lambda (rel-path)
426 (file-name-as-directory rel-path)
428 rel-paths-from-parent)))
431 (javaimp--maven-xml-file-matches
432 file (javaimp-module-id submodule) (javaimp-module-parent-id submodule)))
434 (error "Cannot find file for module: %s" (javaimp-module-id submodule)))))
439 (defun javaimp--maven-update-module-maybe (node)
441 ;; are deps not initialized?
442 (let ((module (javaimp-node-contents node)))
443 (if (null (javaimp-module-dep-jars module))
444 (setq need-update t)))
445 ;; were any pom.xml files updated after last load?
449 (let ((module (javaimp-node-contents tmp)))
450 (if (> (float-time (javaimp--get-file-ts (javaimp-module-file module)))
451 (float-time (javaimp-module-load-ts module)))
452 (setq need-update t)))
453 (setq tmp (javaimp-node-parent tmp))))
455 ;; update current module
456 (let ((module (javaimp-node-contents node)))
457 ;; reload & update dep-jars
458 (setf (javaimp-module-dep-jars module)
459 (javaimp--maven-fetch-dep-jars module))
461 (setf (javaimp-module-load-ts module) (current-time))))))
463 (defun javaimp--maven-fetch-dep-jars (module)
466 (javaimp-module-file module) "dependency:build-classpath"
468 (goto-char (point-min))
469 (search-forward "Dependencies classpath:")
471 (thing-at-point 'line))))
472 (separator-regex (concat "[" path-separator "\n" "]+")))
473 (split-string (javaimp-cygpath-convert-maybe raw-line 'unix t) separator-regex t)))
477 ;; Working with jar classes
479 (defun javaimp--get-jar-classes (file)
480 (let ((cached (cdr (assoc file javaimp-cached-jars))))
482 ;; create, load & put into cache
484 (make-javaimp-cached-jar
486 :read-ts (javaimp--get-file-ts file)
487 :classes (javaimp--fetch-jar-classes file)))
488 (push (cons file cached) javaimp-cached-jars))
489 ((> (float-time (javaimp--get-file-ts (javaimp-cached-jar-file cached)))
490 (float-time (javaimp-cached-jar-read-ts cached)))
492 (setf (javaimp-cached-jar-classes cached) (javaimp--fetch-jar-classes file))
494 (setf (javaimp-cached-jar-read-ts cached) (current-time))))
495 ;; return from cached
496 (javaimp-cached-jar-classes cached)))
498 (defun javaimp--fetch-jar-classes (file)
499 (message "Reading classes in file: %s" file)
501 (let ((coding-system-for-read (and (eq system-type 'cygwin) 'utf-8-dos)))
502 ;; On Cygwin, "jar" is a Windows program, so file path needs to be
503 ;; converted appropriately.
504 (process-file javaimp-jar-program nil t nil
505 ;; `jar' accepts commands/options as a single string
506 "tf" (javaimp-cygpath-convert-maybe file 'windows))
507 (goto-char (point-min))
508 (while (search-forward "/" nil t)
510 (goto-char (point-min))
512 (while (re-search-forward "\\(^[[:alnum:]._]+\\)\\.class$" nil t)
513 (push (match-string 1) result))
517 ;; Some API functions
519 (defun javaimp-get-all-modules ()
520 (javaimp-select-nodes (lambda (module) t)))
522 (defun javaimp-find-node (predicate)
523 (javaimp--find-in-forest javaimp-project-forest predicate))
525 (defun javaimp-select-nodes (predicate)
526 (javaimp--select-from-forest javaimp-project-forest predicate))
529 ;; Tree search routines
531 (defun javaimp--find-in-forest (forest predicate)
533 (dolist (tree forest)
534 (javaimp--find-node tree predicate))))
536 (defun javaimp--find-node (tree predicate)
538 (progn (if (funcall predicate (javaimp-node-contents tree))
540 (dolist (child (javaimp-node-children tree))
541 (javaimp--find-node child predicate)))))
543 (defun javaimp--select-from-forest (forest predicate)
544 (apply #'seq-concatenate 'list
545 (mapcar (lambda (tree)
546 (javaimp--select-nodes tree predicate))
549 (defun javaimp--select-nodes (tree predicate)
551 (append (if (funcall predicate (javaimp-node-contents tree))
553 (apply #'seq-concatenate 'list
554 (mapcar (lambda (child)
555 (javaimp--select-nodes child predicate))
556 (javaimp-node-children tree))))))
562 (defun javaimp-add-import (classname)
563 "Imports CLASSNAME in the current file. Interactively,
564 asks for a class to import, adds import statement and calls
565 `javaimp-organize-imports'. Import statements are not
566 duplicated. Completion alternatives are constructed based on
567 this module's dependencies' classes, JDK classes and top-level
568 classes in the current module."
571 (barf-if-buffer-read-only)
572 (let* ((file (expand-file-name
574 (error "Buffer is not visiting a file!"))))
575 (node (or (javaimp-find-node
577 (or (string-prefix-p (javaimp-module-source-dir m) file)
578 (string-prefix-p (javaimp-module-test-source-dir m) file))))
579 (error "Cannot find module by file: %s" file))))
580 (javaimp--maven-update-module-maybe node)
581 (let ((module (javaimp-node-contents node)))
582 (list (completing-read
585 ;; we're not caching full list of classes coming from module
586 ;; dependencies because jars may change and we need to reload
588 (let ((jars (append (javaimp-module-dep-jars module)
589 (javaimp--get-jdk-jars))))
590 (apply #'seq-concatenate 'list
591 (mapcar #'javaimp--get-jar-classes jars)))
592 (and javaimp-include-current-module-classes
593 (javaimp--get-module-classes module)))
594 nil t nil nil (symbol-name (symbol-at-point))))))))
595 (javaimp-organize-imports (cons classname 'ordinary)))
597 (defun javaimp--get-module-classes (module)
598 "Returns list of top-level classes in current module"
600 (let ((build-dir (javaimp-module-build-dir module)))
603 (let ((dir (concat build-dir (file-name-as-directory rel-dir))))
604 (and (file-accessible-directory-p dir)
605 (javaimp--get-directory-classes dir nil))))
606 javaimp-additional-source-dirs)))
607 (let ((dir (javaimp-module-source-dir module)))
608 (and (file-accessible-directory-p dir)
609 (javaimp--get-directory-classes dir nil)))
610 (let ((dir (javaimp-module-test-source-dir module)))
611 (and (file-accessible-directory-p dir)
612 (javaimp--get-directory-classes dir nil)))))
614 (defun javaimp--get-directory-classes (dir prefix)
616 ;; .java files in current directory
617 (mapcar (lambda (file)
618 (concat prefix (file-name-sans-extension (car file))))
619 (seq-filter (lambda (file) (null (cadr file))) ;only files
620 (directory-files-and-attributes dir nil "\\.java\\'" t)))
621 ;; descend into subdirectories
622 (apply #'seq-concatenate 'list
623 (mapcar (lambda (subdir)
624 (let ((name (car subdir)))
625 (javaimp--get-directory-classes
626 (concat dir (file-name-as-directory name)) (concat prefix name "."))))
627 (seq-filter (lambda (file) (and (cadr file) ;only directories
628 (null (member (car file) '("." "..")))))
629 (directory-files-and-attributes dir nil nil t))))))
632 ;; Organizing imports
635 (defun javaimp-organize-imports (&rest new-imports)
636 "Groups import statements according to the value of
637 `javaimp-import-group-alist' (which see) and prints resulting
638 groups leaving one blank line in between.
640 Classes within a single group are ordered in a lexicographic
643 Imports not matched by any regexp in `javaimp-import-group-alist'
644 are assigned a default order defined by
645 `javaimp-import-default-order'.
647 NEW-IMPORTS is a list of additional imports; each element should
648 be of the form (CLASS . TYPE), where CLASS is a string and TYPE
649 is `'ordinary' or `'static'. Interactively, NEW-IMPORTS is nil."
651 (barf-if-buffer-read-only)
653 (goto-char (point-min))
654 (let* ((old-data (javaimp--parse-imports))
655 (first (car old-data))
656 (last (cadr old-data))
657 (all-imports (append new-imports (cddr old-data))))
658 ;; delete old imports, if any
663 (delete-region first (point))))
664 (javaimp--prepare-for-insertion first)
666 (delete-duplicates all-imports
667 :test (lambda (first second)
668 (equal (car first) (car second)))))
673 (let ((order (or (assoc-default (car import)
674 javaimp-import-group-alist
676 javaimp-import-default-order)))
677 (cons import order)))
681 (lambda (first second)
682 ;; sort by order, name
683 (if (= (cdr first) (cdr second))
684 (string< (caar first) (caar second))
685 (< (cdr first) (cdr second))))))
686 (javaimp--insert-imports with-order)))))
688 (defun javaimp--parse-imports ()
689 (let (first last list)
690 (while (re-search-forward "^\\s-*import\\s-+\\(static\\s-+\\)?\\([._[:word:]]+\\)" nil t)
691 (push (cons (match-string 2) (if (match-string 1) 'static 'ordinary)) list)
692 (setq last (line-beginning-position))
693 (or first (setq first last)))
694 (cons first (cons last list))))
696 (defun javaimp--prepare-for-insertion (start)
698 ;; if there were any imports, we start inserting at the same place
700 ((re-search-forward "^\\s-*package\\s-" nil t)
701 ;; if there's a package directive, move to the next line, creating it
707 ;; then insert one blank line and we're done
710 ;; otherwise, just go to bob
711 (goto-char (point-min)))))
713 (defun javaimp--insert-imports (imports)
714 (let ((static (seq-filter (lambda (elt)
715 (eq (cdar elt) 'static))
717 (ordinary (seq-filter (lambda (elt)
718 (eq (cdar elt) 'ordinary))
720 (javaimp--insert-import-group "import static %s;" static)
721 (and static ordinary (insert ?\n))
722 (javaimp--insert-import-group "import %s;" ordinary)))
724 (defun javaimp--insert-import-group (pattern imports)
726 (dolist (import imports)
727 ;; if adjacent imports have different order value, insert a newline
729 (let ((order (cdr import)))
731 (/= order last-order)
733 (insert (format pattern (caar import)) ?\n)
734 (setq last-order order)))))
738 ;;; javaimp.el ends here