;; andersl-java-font-lock.el -- Font lock support for Java.

;; Copyright (C) 1996 Anders Lindgren

;; Author: Anders Lindgren <andersl@csd.uu.se>
;; Maintainer: Anders Lindgren <andersl@csd.uu.se>
;; Created:  5 Aug 1996
;; Version: 0.0
;; Keywords: font-lock, java, languages
;; Date:  6 Aug 1996

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.

;; This program is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.

;;; Commentary:

;; This package supplies high-performance font-lock support for the
;; language Java.
;;
;; In particular, the following non-trivial features are highlighted:
;;   - Declared variables
;;   - Keywords and types
;;   - Name of classes, constructors, and methods
;;   - Labels and corresponding break/continue statements
;;   - Javadoc tags (including name associated with `param' tag)
;;
;; Note that all declared variables are fontified, even if the variable
;; is defined inside a complex expression, e.g:
;;
;;     for (int i = 0; i < 10; i++)
;;     ^^^  ^^^ ^
;;      |    |  |-- Variable face
;;      |    |----- Type face
;;      |---------- Keyword face
;;
;; Identifiers containing Latin-1 characters are handled.  (Although,
;; they would work better if java-mode would define the proper syntax
;; code for letters like `å'.)
;;
;; I have decided not to highlight the special identifiers: `true',
;; `false', `null', `this', and `super'.  (In case you really want
;; them to be highlighted, I have written a font-lock entry, just
;; uncomment it and add water.)
;;
;; This package only works correctly under the assumption that every
;; capitalized word is the name of a class or an interface.  However,
;; should this requirement not be fulfilled, only minor mis-coloring
;; problems will occur.
;;
;; A modern version of font-lock is required.  The definition of
;; "modern" is that it should contain the variable
;; `font-lock-defaults-alist'. 
;;
;; Note that this package has been inspired by, although not based on,
;; other packages for font-locking Java.

;; Home, sweet home:
;;
;; The latest version of this package can be found at:
;;    http:\\www.csd.uu.se\~andersl\emacs.shtml
;;    ftp:\\ftp.csd.uu.se\pub\users\andersl\emacs\
;;
;; While you're visiting, take a look at some of my other packages.

;; Installation:
;;
;; Please place the following lines in the appropriate init file, for
;; example your ~/.emacs file.
;;
;; (add-hook 'java-mode-hook 'my-java-mode-hook)
;;
;; (defun my-java-mode-hook ()
;;   (cond (window-system
;;          (require 'andersl-java-font-lock)
;;          (turn-on-font-lock))))

;;; Code:

(defvar java-font-lock-keywords nil
  "Default font lock keywords for the Java language.")
(defvar java-font-lock-keywords-1 nil
  "Font lock keywords for the Java language.")
(defvar java-font-lock-keywords-2 nil
  "Font lock, additional support for types and variables.")
(defvar java-font-lock-keywords-3 nil
  "Font lock, additional support for javadoc tags.")


(defvar java-primitive-type-regexp
  (concat "\\<\\(boolean\\|byte\\|char\\|double\\|float\\|int"
	  "\\|long\\|short\\|void\\)\\>")
  "Regexp which should match a primitive type.")
(defvar java-primitive-type-regexp-count 1
  "Number of paris of parentheses in `java-primitive-type-regexp'.")


(defvar java-identifier-regexp nil
  "Regexp which should match all Java identifiers.")
(defvar java-identifier-regexp-count nil)


(defvar java-class-type-regexp nil
  "Regexp which should match a class or an interface name.
The name is assumed to begin with a capital letter.")
(defvar java-class-type-regexp-count nil)


(let ((capital-letter "A-Z\300-\326\330-\337")
      (letter "a-zA-Z_$\300-\326\330-\366\370-\377")
      (digit  "0-9"))

  (setq java-identifier-regexp
	(concat "\\<\\([" letter "][" letter digit "]*\\)\\>"))
  (setq java-identifier-regexp-count 1)
  
  (setq java-class-type-regexp
	(concat "\\<\\([" capital-letter "][" letter digit "]*\\)\\>"))
  (setq java-class-type-regexp-count 1))


(let ((java-modifier-regexp
       (concat "\\<\\(abstract\\|const\\|final\\|native\\|"
	       "p\\(r\\(ivate\\|otected\\)\\|ublic\\)\\|"
	       "s\\(tatic\\|ynchronized\\)\\|transient\\|volatile\\)\\>"))
      (java-modifier-regexp-count 4)

      (java-type-regexp
       (concat "\\(" java-class-type-regexp "\\|" 
               java-primitive-type-regexp "\\)"))
      (java-type-regexp-count 
       (+ 1 java-class-type-regexp-count java-primitive-type-regexp-count)))

  ;; "Normal" font-lock support:
  (setq java-font-lock-keywords-1
	(list

	 ;; Keywords:
	 (list        
	  (concat
	   "\\<\\("
	   "b\\(reak\\|yvalue\\)\\|"
	   "c\\(a\\(s[et]\\|tch\\)\\|lass\\|ontinue\\)\\|"
	   "d\\(efault\\|o\\)\\|e\\(lse\\|xtends\\)\\|"
	   "f\\(inally\\|or\\|uture\\)\\|"
	   "g\\(eneric\\|oto\\)\\|"
	   "i\\(f\\|mp\\(lements\\|ort\\)"
	   "\\|n\\(ner\\|stanceof\\|terface\\)\\)\\|"
	   "new\\|o\\(perator\\|uter\\)\\|"
	   "package\\|re\\(st\\|turn\\)\\|switch\\|"
	   "t\\(hrows?\\|ry\\)\\|var\\|while\\)\\>")
	  1 'font-lock-keyword-face)

	 ;; Modifiers:
	 (list java-modifier-regexp 1 'font-lock-type-face)

	 ;; Special "variables":
	 ;; Uncomment the following lines to fontify `true', `this' etc.
	 (list "\\<\\(false\\|null\\|super\\|t\\(his\\|rue\\)\\)\\>"
	       1 'font-lock-keyword-face)
	 
	 ;; Class name:
	 (list (concat "\\<class\\s +" java-identifier-regexp)
	       1 'font-lock-function-name-face)
	 
	 ;; Constructors:
	 (list (concat
		"^\\s *\\(" java-modifier-regexp "\\s +\\)*"
		java-class-type-regexp "\\s *(")
	       (+ 2 java-modifier-regexp-count)
	       'font-lock-function-name-face)

	 ;; Methods:
	 (list (concat java-type-regexp
		       "\\s *\\(\\[\\s *\\]\\s *\\)*"
		       java-identifier-regexp "\\s *(")
	       (+ 2 java-type-regexp-count)
	       'font-lock-function-name-face)
	 
	 ;; Labels, with corresponding break/continue statements.
	 (list (concat "^\\s *" java-identifier-regexp "\\s *:")
	       1 'font-lock-reference-face)
	 (list (concat "\\(break\\|continue\\)\\s *" java-identifier-regexp)
	       2 'font-lock-reference-face)))

  ;; Types and declared variables:
  (setq java-font-lock-keywords-2
	(append 
	 java-font-lock-keywords-1
	 (list
	  (list (concat "\\(" java-type-regexp "\\)"
			"\\s *\\(\\[\\s *\\]\\s *\\)*"
			"\\()\\|\\sw\\|$\\)")
		;; Fontify each declaration item.
		'(1 font-lock-type-face)
		'(font-lock-match-java-style-declaration-item-and-skip-to-next
		  (goto-char (match-end 1))
		  (goto-char (match-end 1))
		  (1 font-lock-variable-name-face))))))
  
  ;; Javadoc tags:
  (setq java-font-lock-keywords-3
	(append
	 java-font-lock-keywords-2
	 (list
	  '("\\(@\\(author\\|exception\\|param\\|return\\|see\\|version\\)\\)"
	    1 font-lock-reference-face t)
	  (list (concat "@\\(param\\)\\s *" java-identifier-regexp)
		2 'font-lock-variable-name-face t))))

  (setq java-font-lock-keywords java-font-lock-keywords-3))


;; Match, and move over, any declaration/definition item after
;; point.  Does not match items which looks like a type declaration
;; (primitive types and class names, i.e. capitalized words.)
;; Should the variable name be followed by a comma, we reposition
;; the cursor to fontify more identifiers.
(defun font-lock-match-java-style-declaration-item-and-skip-to-next (limit)
  (if (looking-at "\\s *\\(\\[\\s *\\]\\s *\\)*")
      (goto-char (match-end 0)))
  (and 
   (looking-at (concat "\\s *" java-identifier-regexp))
   (save-match-data
     (not (string-match java-primitive-type-regexp 
			(buffer-substring (match-beginning 1)
					  (match-end 1)))))
   (save-match-data
     (condition-case nil
	 (save-restriction
	   (narrow-to-region (point-min) limit)
	   (goto-char (match-end 0))
	   (if (looking-at "\\s *\\(\\[\\s *\\]\\s *\\)*")
	       (goto-char (match-end 0)))
	   (cond ((eq (following-char) ?,)
		  ;; Match, and continue.
		  (forward-char 1)
		  (point))
		 ((memq (following-char) '(?= ?\; ?\) ?\n 0))
		  ;; Match, the next search will fail.
		  (point))
		 (t nil))) ;; No match.
       (error t)))))


;;; Inform font-lock that we're around.
;; This code only works on modern versions of Emacs, on older systems
;; you have to manually set the variables `font-lock-keywords',
;; `font-lock-syntax-table', or upgrade your Emacs.

(require 'font-lock)  

(if (not (assq 'java-mode font-lock-defaults-alist))
    (setq font-lock-defaults-alist
	  (cons 
	   (cons 'java-mode

		 ;; java-mode-defaults
		 '((java-font-lock-keywords java-font-lock-keywords-1 
		    java-font-lock-keywords-2 java-font-lock-keywords-3)
		   nil nil ((?_ . "w")) beginning-of-defun
		   (font-lock-mark-block-function . mark-defun)))

	   font-lock-defaults-alist)))


(provide 'andersl-java-font-lock)

;; andersl-java-font-lock.el ends here.
