Initial Commit
[packages] / xemacs-packages / jde / lisp / jde-project-file.el
1 ;;; jde-project-file.el -- Integrated Development Environment for Java.
2 ;; $Revision: 1.1 $ $Date: 2007-11-26 15:16:48 $ 
3
4 ;; Author: Paul Kinnucan <paulk@mathworks.com>
5 ;; Maintainer: Paul Kinnucan
6 ;; Keywords: java, tools
7
8 ;; Copyright (C) 2004 Paul Kinnucan.
9
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)
13 ;; any later version.
14
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.
19
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.
24
25 ;;; Commentary:
26
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.
30
31 ;; The latest version of the JDE is available at
32 ;; <URL:http://jdee.sunsite.dk>.
33
34 ;; Please send any comments, bugs, or upgrade requests to
35 ;; Paul Kinnucan at paulk@mathworks.com.
36
37 ;;; Code:
38
39
40 (defconst jde-project-file-version "1.0"
41   "*The current JDE project file version number.")
42
43 (defgroup jde-project nil
44   "JDE Project Options"
45   :group 'jde
46   :prefix "jde-")
47
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."
54   :group 'jde-project
55   :type 'boolean)
56
57 (defun jde-toggle-project-switching ()
58   "Toggles project switching on or off."
59   (interactive)
60   (setq jde-project-context-switching-enabled-p 
61         (not jde-project-context-switching-enabled-p)))
62
63 (defcustom jde-project-name "default"
64 "Specifies name of project to which the current buffer belongs."
65   :group 'jde-project
66   :type 'string)
67
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."
75   :group 'jde-project
76   :type 'string)
77
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
81 the jde-mode hooks."
82   :group 'jde-project
83   :type '(repeat (function :tag "Function")))
84
85 (defvar jde-loaded-project-file-version nil
86   "*Temporary var that holds the project file version of the project
87 being loaded.")
88
89
90 (defun jde-project-file-version (ver)
91   (setq jde-loaded-project-file-version ver))
92
93 (defvar jde-current-project ""
94   "Path of the project file for the current project.")
95
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=)))
103     (if file
104         (expand-file-name file dir)
105       (if (not (jde-root-dir-p dir))
106           (jde-find-project-file (expand-file-name ".." dir))))))
107
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)
111
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))
117          current-dir files)
118     (while file
119       (setq files (append (list file) files))
120       (setq current-dir (file-name-directory file))
121       (setq 
122        file
123        (if (not (jde-root-dir-p current-dir))
124            (jde-find-project-file
125             (expand-file-name ".." current-dir)))))
126     files))
127       
128 (defvar jde-loading-project nil
129   "Used by project loading system.")
130
131 (defvar jde-loading-project-file nil
132   "Used by project loading system.")
133
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."
142   (interactive)
143   (setq jde-loading-project t)
144   (let ((prj-files 
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))))
149     (if prj-files
150         (progn
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)
157             (load-file file)
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))
162
163
164 (defun jde-load-all-project-files ()
165   (interactive)
166   "Loads the project file associated with each Java source buffer."
167   (mapc
168    (lambda (java-buffer)
169      (save-excursion
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)))
175
176 ;;;###autoload
177 (defun jde-open-project-file ()
178   "Opens the project file for the Java source file in the
179 current buffer."
180   (interactive)
181   (let ((prj-file (jde-find-project-file default-directory)))
182     (if prj-file
183         (find-file prj-file)
184       (message "%s" "Project file not found."))))
185
186
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."
190   (save-excursion
191     (set-buffer buffer)
192     (goto-char (point-min))
193     (catch 'found
194       (while t
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
201                              (backward-sexp)
202                              (point))
203                            (point))
204             (throw 'found nil)))))
205     (unless (bolp)
206       (princ "\n"))))
207
208 (defun jde-symbol-p (symbol)
209   "Returns non-nil if SYMBOL is a JDE variable."
210   (and (or
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)))))
215
216 (defvar jde-symbol-list nil
217   "*A list of jde variables which are processed by `jde-save-project'.")
218
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."
225   (if force-update
226       (setq jde-symbol-list nil))
227   (unless jde-symbol-list 
228     (mapatoms
229      (lambda (symbol)
230        (if (jde-symbol-p symbol)
231            (setq jde-symbol-list (cons symbol jde-symbol-list))))))
232   jde-symbol-list)
233
234 (defun jde-set-project-name (name)
235   (put 'jde-project-name 'customized-value (list name))
236   (setq jde-project-name name))
237
238 (defun jde-put-project (symbol project value)
239   "Stores a new value for SYMBOL in PROJECT, or overwrites any
240 existing value."
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))))))
247
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)))))
253
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)))
257
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)
264     (save-excursion
265       (set-buffer buffer)
266       (goto-char (point-min))
267       (jde-save-delete 'jde-project-file-version buffer)
268       (delete-blank-lines)
269       (jde-save-delete 'jde-set-variables buffer)
270       (delete-blank-lines)
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)
275     (princ ")\n")
276     (princ "(jde-set-variables")
277     (jde-log-msg "jde-save-open-buffer: Opening buffer for %s" project)
278     buffer))
279
280 (defun jde-save-close-buffer (project)
281   "Saves and closes the buffer associated with PROJECT."
282   (let* ((buffer 
283           (if jde-xemacsp
284               (get-file-buffer project)
285             (find-buffer-visiting project)))
286          (standard-output buffer))
287     (if buffer
288       (progn
289         (princ ")\n")
290         (save-excursion
291           (set-buffer buffer)
292           (save-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))))
296
297 (defun jde-save-variable (symbol projects)
298   "Saves all of the values of SYMBOL for each project file mentioned
299 in PROJECTS."
300   (mapc
301    (lambda (project)
302      (if (and (not (string= (car project) "default"))
303               (member (car project) projects))
304          (let ((buffer 
305                 (if jde-xemacsp
306                     (get-file-buffer (car project))
307                   (find-buffer-visiting (car project))))
308                standard-output)
309            (if (null buffer)
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))
313            (princ "\n '(")
314            (princ symbol)
315            (princ " ")
316            (prin1 (custom-quote (car (cdr project))))
317            (princ ")"))))
318    (get symbol 'jde-project)))
319
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))
327           val-to-save
328           current-proj proj-iter)
329       (setq current-proj (car projects))
330       (cond
331       ;; CASE: current value changed from saved value in current
332        ;; project
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'"
337                      symbol current-proj)
338         (jde-put-project symbol current-proj value)
339         t)
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))
343              (progn
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)))
349                (if proj-iter
350                    (not (equal value
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.
354                       (or                               ;; either 
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)
361         t)
362        ;; CASE: current value same as value in the deepest project that
363        ;; holds that value - re-save it
364        ((progn
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)))
369           (if 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))
373         t)))))
374
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)))
382           (jde-symbol-list))
383     (mapc 'jde-save-close-buffer projects-reversed)))
384
385 ;;;###autoload
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
393 for that file."
394   (interactive)
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: "
401                                                       default-directory
402                                                       default-directory)))))
403     (jde-save-project-internal project-file-paths)))
404
405 ;;;###autoload
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")
427           (save-excursion
428             (set-buffer standard-output)
429             (save-buffer))
430           (kill-buffer standard-output)
431           (setq projects (nconc projects (list prj-file)))))
432     (jde-save-project-internal projects)))
433
434
435 (defvar jde-dirty-variables nil
436   "JDEE customization variables that have project-specific customizations.")
437
438 (defun jde-set-variables (&rest args)
439   "Initialize JDE customization variables.  
440
441 Takes a variable number of arguments. Each argument 
442 should be of the form:
443
444   (SYMBOL VALUE)
445
446 The value of SYMBOL is set to VALUE.
447 This function is used in JDEE project files."
448   (while args 
449     (let ((entry (car args)))
450       (if (listp entry)
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)
456                           'set-default)))
457
458             (add-to-list 'jde-dirty-variables symbol)
459
460             (if (or customized
461                     jde-loaded-project-file-version)
462                 (put symbol 'customized-value (list value)))
463             (if jde-loading-project-file
464                 (progn
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
469                                    (eval value)))
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)))))))
475
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)))
487
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
491 have been loaded)."
492   (interactive)
493   (if (or (interactive-p) msg)
494       (message "Setting customized JDE variables to startup values..."))
495   (if jde-dirty-variables
496       (mapcar 
497        'jde-set-variable-init-value
498        jde-dirty-variables)))
499
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.
503
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."
510   (condition-case err
511       (let ((project-file-path (jde-find-project-file default-directory)))
512         (if (not project-file-path) (setq project-file-path ""))
513         (if (and 
514              jde-project-context-switching-enabled-p
515              (not (jde-debugger-running-p))
516              (not (string= 
517                    (file-truename jde-current-project) 
518                    (file-truename project-file-path))))
519             (progn
520               (setq jde-current-project project-file-path)
521               (jde-load-project-file)
522               (jde-wiz-set-bsh-project))))
523     (error (message 
524             "Project file reload error: %s" 
525             (error-message-string err)))))
526
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."
533   (jde-symbol-list t)
534   (jde-load-project-file))
535
536
537 (provide 'jde-project-file)
538
539 ;; Change History
540 ;;
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.
544 ;;
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
547 ;; instead of one.
548 ;;
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.
553 ;;
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
556 ;; hooks.
557 ;;
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.
562 ;;
563 ;; Revision 1.3  2004/03/03 03:55:45  paulk
564 ;; Moved project-related stuff to this file from jde.el.
565 ;;
566 ;; Revision 1.2  2004/02/24 05:51:21  paulk
567 ;; Cosmetic change.
568 ;;
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.
573 ;;
574 ;;
575 ;;
576