1 ;;; jde-project-file.el -- Integrated Development Environment for Java.
2 ;; $Revision: 1.1 $ $Date: 2007-11-26 15:16:48 $
4 ;; Author: Paul Kinnucan <paulk@mathworks.com>
5 ;; Maintainer: Paul Kinnucan
6 ;; Keywords: java, tools
8 ;; Copyright (C) 2004 Paul Kinnucan.
10 ;; GNU Emacs is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING. If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
27 ;; This is one of a set of packages that make up the
28 ;; Java Development Environment (JDE) for Emacs. See the
29 ;; JDE User's Guide for more information.
31 ;; The latest version of the JDE is available at
32 ;; <URL:http://jdee.sunsite.dk>.
34 ;; Please send any comments, bugs, or upgrade requests to
35 ;; Paul Kinnucan at paulk@mathworks.com.
40 (defconst jde-project-file-version "1.0"
41 "*The current JDE project file version number.")
43 (defgroup jde-project nil
48 (defcustom jde-project-context-switching-enabled-p t
49 "*Enable project context switching.
50 If non-nil, the JDE reloads a buffer's project file when you switch to the buffer from
51 another buffer belonging to another project. You can disable this feature if you prefer
52 to load project files manually. The debugger uses this variable to disable context-switching
53 temporarily when stepping through code."
57 (defun jde-toggle-project-switching ()
58 "Toggles project switching on or off."
60 (setq jde-project-context-switching-enabled-p
61 (not jde-project-context-switching-enabled-p)))
63 (defcustom jde-project-name "default"
64 "Specifies name of project to which the current buffer belongs."
68 (defcustom jde-project-file-name "prj.el"
69 "*Specify name of JDE project file.
70 When it loads a Java source file, the JDE looks for a lisp file of
71 this name (the default is prj.el in the source file hierarchy. If it
72 finds such a file, it loads the file. You can use this file to set the
73 classpath, compile options, and other JDE options on a
74 project-by-project basis."
78 (defcustom jde-project-hooks nil
79 "Specifies a list of functions to be run when a project
80 becomes active. The JDE runs the project hooks after
83 :type '(repeat (function :tag "Function")))
85 (defvar jde-loaded-project-file-version nil
86 "*Temporary var that holds the project file version of the project
90 (defun jde-project-file-version (ver)
91 (setq jde-loaded-project-file-version ver))
93 (defvar jde-current-project ""
94 "Path of the project file for the current project.")
96 (defun jde-find-project-file (dir)
97 "Finds the next project file upwards in the directory tree
98 from DIR. Returns nil if it cannot find a project file in DIR
99 or an ascendant directory."
100 (let* ((directory-sep-char ?/) ;; Override NT/XEmacs setting
101 (file (find jde-project-file-name
102 (directory-files dir) :test 'string=)))
104 (expand-file-name file dir)
105 (if (not (jde-root-dir-p dir))
106 (jde-find-project-file (expand-file-name ".." dir))))))
108 (defvar jde-buffer-project-file ""
109 "Path of project file associated with the current Java source buffer.")
110 (make-variable-buffer-local 'jde-buffer-project-file)
112 (defun jde-find-project-files (dir)
113 "Return all the project files in the current directory tree,
114 starting with the topmost."
115 (let* ((directory-sep-char ?/) ;; Override NT/XEmacs setting
116 (file (jde-find-project-file dir))
119 (setq files (append (list file) files))
120 (setq current-dir (file-name-directory file))
123 (if (not (jde-root-dir-p current-dir))
124 (jde-find-project-file
125 (expand-file-name ".." current-dir)))))
128 (defvar jde-loading-project nil
129 "Used by project loading system.")
131 (defvar jde-loading-project-file nil
132 "Used by project loading system.")
134 (defun jde-load-project-file ()
135 "Load the project file(s) for the Java source file in the current
136 buffer. Search for all the project file first in the directory
137 tree containing the current source buffer. If any files are found,
138 first reset all variables to their startup values. Then load
139 the project files starting with the topmost in the tree.
140 If no project files are found, set the JDE variables to their
141 Emacs startup values."
143 (setq jde-loading-project t)
145 (jde-find-project-files
146 ;; Need to normalize path to work around bug in the
147 ;; cygwin version of XEmacs.
148 (expand-file-name "." default-directory))))
151 (jde-set-variables-init-value)
152 (loop for file in prj-files do
153 (setq jde-loading-project-file file)
154 (jde-log-msg "jde-load-project-file: Loading %s" file)
155 ;; reset project file version
156 (setq jde-loaded-project-file-version nil)
158 (setq jde-loading-project-file nil))
159 (run-hooks 'jde-project-hooks))
160 (jde-set-variables-init-value t)))
161 (setq jde-loading-project nil))
164 (defun jde-load-all-project-files ()
166 "Loads the project file associated with each Java source buffer."
168 (lambda (java-buffer)
170 (set-buffer java-buffer)
171 (message "Loading project file for %s ..."
172 (buffer-file-name java-buffer))
173 (jde-load-project-file)))
174 (jde-get-java-source-buffers)))
177 (defun jde-open-project-file ()
178 "Opens the project file for the Java source file in the
181 (let ((prj-file (jde-find-project-file default-directory)))
184 (message "%s" "Project file not found."))))
187 (defun jde-save-delete (symbol buffer)
188 "Delete the call to SYMBOL from project file in BUFFER.
189 Leave point at the location of the call, or after the last expression."
192 (goto-char (point-min))
195 (let ((sexp (condition-case nil
196 (read (current-buffer))
197 (end-of-file (throw 'found nil)))))
198 (when (and (listp sexp)
199 (eq (car sexp) symbol))
200 (delete-region (save-excursion
204 (throw 'found nil)))))
208 (defun jde-symbol-p (symbol)
209 "Returns non-nil if SYMBOL is a JDE variable."
211 (get symbol 'custom-type)
212 (get symbol 'jde-project))
213 (or (string-match "^bsh-" (symbol-name symbol))
214 (string-match "^jde-" (symbol-name symbol)))))
216 (defvar jde-symbol-list nil
217 "*A list of jde variables which are processed by `jde-save-project'.")
219 (defun jde-symbol-list (&optional force-update)
220 "Return a list of variables to be processed by `jde-save-project'.
221 The first time this is called, the list is saved in `jde-symbol-list'.
222 If nonnil, FORCE-UPDATE forces regeneration of `jde-symbol-list'.
223 This is useful for updating customization variables defined by
224 packages loaded after startup of the JDEE."
226 (setq jde-symbol-list nil))
227 (unless jde-symbol-list
230 (if (jde-symbol-p symbol)
231 (setq jde-symbol-list (cons symbol jde-symbol-list))))))
234 (defun jde-set-project-name (name)
235 (put 'jde-project-name 'customized-value (list name))
236 (setq jde-project-name name))
238 (defun jde-put-project (symbol project value)
239 "Stores a new value for SYMBOL in PROJECT, or overwrites any
241 (let ((proj-alist (get symbol 'jde-project)))
242 (if (null proj-alist)
243 (put symbol 'jde-project (list (cons project (list value))))
244 (if (assoc project proj-alist)
245 (setcdr (assoc project proj-alist) (list value))
246 (put symbol 'jde-project (pushnew (cons project (list value)) proj-alist))))))
248 (defun jde-get-project (symbol project)
249 "Gets the value for SYMBOL that is associated with PROJECT, or nil
250 if none. To test if SYMBOL has any value for PROJECT, use
251 `jde-project-present-p'."
252 (car-safe (cdr-safe (assoc project (get symbol 'jde-project)))))
254 (defun jde-project-present-p (symbol project)
255 "Returns non-nil if SYMBOL has a value for PROJECT."
256 (assoc project (get symbol 'jde-project)))
258 (defun jde-save-open-buffer (project)
259 "Creates a new buffer or opens an existing buffer for PROJECT."
260 (let ((auto-insert nil) ; turn off auto-insert when
261 buffer standard-output) ; creating a new file
262 (setq buffer (find-file-noselect project))
263 (setq standard-output buffer)
266 (goto-char (point-min))
267 (jde-save-delete 'jde-project-file-version buffer)
269 (jde-save-delete 'jde-set-variables buffer)
271 (jde-save-delete 'jde-set-project-name buffer)
272 (delete-blank-lines))
273 (princ "(jde-project-file-version ")
274 (prin1 jde-project-file-version)
276 (princ "(jde-set-variables")
277 (jde-log-msg "jde-save-open-buffer: Opening buffer for %s" project)
280 (defun jde-save-close-buffer (project)
281 "Saves and closes the buffer associated with PROJECT."
284 (get-file-buffer project)
285 (find-buffer-visiting project)))
286 (standard-output buffer))
293 (jde-log-msg "jde-save-close-buffer: Closing buffer for %s" project)
294 (kill-buffer buffer))
295 (jde-log-msg "jde-save-close-buffer: Unable to find buffer for %s" project))))
297 (defun jde-save-variable (symbol projects)
298 "Saves all of the values of SYMBOL for each project file mentioned
302 (if (and (not (string= (car project) "default"))
303 (member (car project) projects))
306 (get-file-buffer (car project))
307 (find-buffer-visiting (car project))))
310 (setq standard-output (setq buffer (jde-save-open-buffer (car project))))
311 (setq standard-output buffer))
312 (jde-log-msg "jde-save-variable: Saving %S in %s" symbol (car project))
316 (prin1 (custom-quote (car (cdr project))))
318 (get symbol 'jde-project)))
320 (defun jde-save-needs-saving-p (symbol projects)
321 "Function used internally by the project saving mechanism to
322 determine whether or not to save a symbol in a project file. If there
323 are settings to be saved, this function also resolves which project
324 should receive the customized values."
325 (unless (= (length projects) 0)
326 (let ((value (symbol-value symbol))
328 current-proj proj-iter)
329 (setq current-proj (car projects))
331 ;; CASE: current value changed from saved value in current
333 ((and (jde-project-present-p symbol current-proj)
334 (not (null (get symbol 'customized-value))) ;; not decustomized.
335 (not (equal value (jde-get-project symbol current-proj))))
336 (jde-log-msg "jde-save-needs-saving-p: changed value for %S in project `%s'"
338 (jde-put-project symbol current-proj value)
340 ;; CASE: no value for symbol in current project - check all
341 ;; parent projects (plus default) to see if value has changed
342 ((and (not (jde-project-present-p symbol current-proj))
344 (setq val-to-save value)
345 (setq proj-iter (cdr projects))
346 (while (and proj-iter
347 (not (jde-project-present-p symbol (car proj-iter))))
348 (setq proj-iter (cdr proj-iter)))
351 (jde-get-project symbol (car proj-iter))))
352 (setq val-to-save (eval (car (get symbol 'customized-value))))
353 (and (not (null (get symbol 'customized-value))) ;; has been customized.
355 (null (get symbol 'saved-value)) ;; not saved in .emacs file, or
356 (not (equal val-to-save ;; different from value in .emacs file
357 (eval (car (get symbol 'saved-value))))))))))
358 (jde-log-msg "jde-save-needs-saving-p: override value %S from parent `%s' in project `%s'"
359 symbol (car proj-iter) current-proj)
360 (jde-put-project symbol current-proj val-to-save)
362 ;; CASE: current value same as value in the deepest project that
363 ;; holds that value - re-save it
365 (setq proj-iter projects)
366 (while (and proj-iter
367 (not (jde-project-present-p symbol (car proj-iter))))
368 (setq proj-iter (cdr proj-iter)))
370 (equal value (jde-get-project symbol (car proj-iter)))))
371 (jde-log-msg "jde-save-needs-saving-p: original value for %S in project `%s'"
372 symbol (car proj-iter))
375 (defun jde-save-project-internal (projects)
376 (let ((projects-reversed (nreverse projects)))
377 (jde-log-msg "jde-save-project-internal: projects: %S" projects-reversed)
378 (mapc 'jde-save-open-buffer projects-reversed)
379 (mapc (lambda (symbol)
380 (if (jde-save-needs-saving-p symbol projects-reversed)
381 (jde-save-variable symbol projects-reversed)))
383 (mapc 'jde-save-close-buffer projects-reversed)))
386 (defun jde-save-project ()
387 "Saves source file buffer options in one or more project files.
388 This command provides an easy way to create and update a project file
389 for a Java project. Simply open a source file, set the desired
390 options, using the JDE Options menu, then save the settings in the
391 project file, using this command. Now, whenever you open a source
392 file from the same directory tree, the saved settings will be restored
395 (let* ((directory-sep-char ?/) ;; Override NT/XEmacs setting
396 (project-file-paths (jde-find-project-files default-directory)))
397 (if (not project-file-paths)
398 (setq project-file-paths
399 (list (expand-file-name jde-project-file-name
400 (read-file-name "Save in directory: "
402 default-directory)))))
403 (jde-save-project-internal project-file-paths)))
406 (defun jde-create-new-project (new-dir)
407 "Creates a new JDE project file in directory NEW-DIR, saving any
408 current customized variables. If a project file already exists in the
409 given directory, the project is simply re-saved. This functions the
410 same as `jde-save-project' when no project files can be found for the
411 current source file. But, if there already exist projects somewhere
412 along the path, this command unconditionally creates a project file in
413 the directory specified, thus allowing the user to create and maintain
414 hierarchical projects."
415 (interactive "DCreate new project in directory: ")
416 (let* ((directory-sep-char ?/) ;; Override NT/XEmacs setting
417 (prj-file (expand-file-name jde-project-file-name new-dir))
418 (projects (jde-find-project-files new-dir)))
419 (if (not (member prj-file projects))
420 ;; create empty project file if none found
421 (let* ((auto-insert nil) ; disable auto-insert
422 (standard-output (find-file-noselect prj-file))
423 (message-log-max nil)) ; disable message log
424 (princ "(jde-project-file-version ")
425 (prin1 jde-project-file-version)
426 (princ ")\n(jde-set-variables)\n")
428 (set-buffer standard-output)
430 (kill-buffer standard-output)
431 (setq projects (nconc projects (list prj-file)))))
432 (jde-save-project-internal projects)))
435 (defvar jde-dirty-variables nil
436 "JDEE customization variables that have project-specific customizations.")
438 (defun jde-set-variables (&rest args)
439 "Initialize JDE customization variables.
441 Takes a variable number of arguments. Each argument
442 should be of the form:
446 The value of SYMBOL is set to VALUE.
447 This function is used in JDEE project files."
449 (let ((entry (car args)))
451 (let* ((symbol (nth 0 entry))
452 (value (nth 1 entry))
453 (customized (nth 2 entry))
454 (set (or (and (local-variable-if-set-p symbol nil) 'set)
455 (get symbol 'custom-set)
458 (add-to-list 'jde-dirty-variables symbol)
461 jde-loaded-project-file-version)
462 (put symbol 'customized-value (list value)))
463 (if jde-loading-project-file
465 (jde-log-msg "jde-set-variables: Loading %S from project %s" symbol
466 jde-loading-project-file)
467 (jde-put-project symbol
468 jde-loading-project-file
470 (jde-log-msg "jde-set-variables: Loading %S from unknown project" symbol))
471 (when (default-boundp symbol)
472 ;; Something already set this, overwrite it
473 (funcall set symbol (eval value)))
474 (setq args (cdr args)))))))
476 (defsubst jde-set-variable-init-value(symbol)
477 "Set a variable to the value it has at Emacs startup."
478 (let ((val-to-set (eval (car (or (get symbol 'saved-value)
479 (get symbol 'standard-value)))))
480 (set (or (get symbol 'custom-set) 'set-default)))
481 (if (or (get symbol 'customized-value)
482 (get symbol 'jde-project))
483 (funcall set symbol val-to-set))
484 (put symbol 'customized-value nil)
485 (put symbol 'jde-project nil)
486 (jde-put-project symbol "default" val-to-set)))
488 (defun jde-set-variables-init-value (&optional msg)
489 "Set each JDEE variable that has a project-specific customization
490 to the value it has at Emacs startup (i.e., before any projects
493 (if (or (interactive-p) msg)
494 (message "Setting customized JDE variables to startup values..."))
495 (if jde-dirty-variables
497 'jde-set-variable-init-value
498 jde-dirty-variables)))
500 ;; Code to update JDE customization variables when a user switches
501 ;; from a Java source buffer belonging to one project to a buffer
502 ;; belonging to another.
504 (defun jde-reload-project-file ()
505 "If project context-switching is enabled (see
506 `jde-project-context-switching-enabled-p') and a debugger
507 is not running (see `jde-debugger-running-p'), reloads the project file
508 for a newly activated Java buffer when the new buffer's project
509 differs from the old buffer's."
511 (let ((project-file-path (jde-find-project-file default-directory)))
512 (if (not project-file-path) (setq project-file-path ""))
514 jde-project-context-switching-enabled-p
515 (not (jde-debugger-running-p))
517 (file-truename jde-current-project)
518 (file-truename project-file-path))))
520 (setq jde-current-project project-file-path)
521 (jde-load-project-file)
522 (jde-wiz-set-bsh-project))))
524 "Project file reload error: %s"
525 (error-message-string err)))))
527 (defun jde-update-autoloaded-symbols ()
528 "Regenerate `jde-symbol-list' and reload
529 the project files for the current project. Insert
530 this function at the end of autoloaded JDEE packages
531 to register and initialize customization variables
532 defined by the current project's project file."
534 (jde-load-project-file))
537 (provide 'jde-project-file)
541 ;; $Log: jde-project-file.el,v $
542 ;; Revision 1.1 2007-11-26 15:16:48 michaels
543 ;; Update jde to author version 2.3.5.1.
545 ;; Revision 1.7 2004/11/13 14:16:16 jslopez
546 ;; Fixes typo. The description for the jde-project was showing in two lines
549 ;; Revision 1.6 2004/07/06 01:47:42 paulk
550 ;; - Move jde-get-java-source-buffers and allied functions to jde-util.el.
551 ;; - Create jde-get-project-source-buffers.
552 ;; - Replace jde-get-current-project-buffers with jde-get-project-source-buffers.
554 ;; Revision 1.5 2004/06/07 03:32:56 paulk
555 ;; Catch errors in jde-reload-project-file to avoid nullifying other entering Java buffer
558 ;; Revision 1.4 2004/06/04 13:49:19 paulk
559 ;; Fixed the following bugs:
560 ;; - Variable from default value to nil not saved in project file.
561 ;; - Variable whose customization has been erased is not removed from project file.
563 ;; Revision 1.3 2004/03/03 03:55:45 paulk
564 ;; Moved project-related stuff to this file from jde.el.
566 ;; Revision 1.2 2004/02/24 05:51:21 paulk
569 ;; Revision 1.1 2004/02/09 06:46:07 paulk
570 ;; When switching projects, the JDEE now reinitializes only those variables that have
571 ;; project-specific values, i.e., that have been set in project files. This dramatically
572 ;; decreases project switching time. Thanks to Phillip Lord.