(require 'jde-gen)
(defcustom jde-gen-equals-trailing-and-operators nil
"Specifies whether the '&&' operators in a generated equals
method are added at the end of the line or at the beginning. If
this variable is t, the operator will be added at the end of the
line, else on the next line before the comparison. With
`jde-gen-equals-trailing-and-operators' set to nil:
return (a == o.a)
&& (b == o.b)
&& (s == null ? o.s == null : s.equals(o.s));
Or, with `jde-gen-equals-trailing-and-operators' set to t:
return (a == o.a) &&
(b == o.b) &&
(s == null ? o.s == null : s.equals(o.s));
"
:group 'jde-gen
:type 'boolean)
(defcustom jde-gen-equals-parens-around-expression nil
"Specifies whether the generated equals expression should be
surrounded by parentheses.
With `jde-gen-equals-trailing-and-operators' set to nil:
return ((a == o.a)
&& (b == o.b)
&& (s == null ? o.s == null : s.equals(o.s)));
Or, with `jde-gen-equals-trailing-and-operators' set to t:
return ((a == o.a) &&
(b == o.b) &&
(s == null ? o.s == null : s.equals(o.s)));
"
:group 'jde-gen
:type 'boolean)
(defcustom jde-gen-equals-method-template
'("'>"
"\"/**\" '> 'n"
"\" * Check if this object is equal to another object.\" '> 'n"
"\" * \" '> 'n"
"\" * <p>For the definition of the object equivalence relation\" '> 'n"
"\" * see {@link java.lang.Object#equals(Object)}.</p>\" '> 'n"
"\" * \" '> 'n"
"\" * @param obj another, possibly equal object.\" '> 'n"
"\" * \" '> 'n"
"\" * @return true if the objects are equal, false otherwise.\" '> 'n"
"\" * \" '> 'n"
"\" * @see java.lang.Object#equals(Object)\" '> 'n"
"\" */\" '> 'n"
"(jde-gen-method-signature \"public\" \"boolean\" \"equals\" \"Object obj\")"
"(jde-gen-electric-brace)"
"\"if (obj == this)\" '> 'n"
"\"return true;\" '> 'n '> 'n"
"\"if (obj == null || getClass() != obj.getClass())\" '> 'n"
"\"return false;\" '> 'n '> 'n"
"(jde-gen-equals-return \"obj\" \"o\") '> 'n"
"\"}\" '> 'n '>")
"*Template for creating an equals method.
Setting this variable defines a template instantiation command
`jde-gen-equals-method', as a side-effect."
:group 'jde-gen
:type '(repeat string)
:set '(lambda (sym val)
(defalias 'jde-gen-equals-method
(tempo-define-template
"java-equals-method"
(jde-gen-read-template val)
nil
"Create an equals method at the current point."))
(set-default sym val)))
(defun jde-gen-equals-return (&optional parm-name var-name class)
"Generate a body of an appropriate override for the
java.lang.Object#equals(Object) function. This function gets the
list of member variables from`jde-gen-get-serializable-members'.
The first optional parameter `parm-name' is the parameter name of
the Object argument of the equals function. Default is \"obj\".
The second optional parameter `var-name' denotes the variable
name used to cast the \"obj\" argument to. The default is \"o\".
The third optional parameter `class' can be a semantic tag, which
is then used instead of the result of `semantic-current-tag'.
Example:
class Bean {
int a;
long b;
String s;
}
Result:
Bean o = (Bean) obj;
return (a == o.a)
&& (b == o.b)
&& (s == null ? o.s == null : s.equals(o.s));
Or, with `jde-gen-equals-trailing-and-operators' set to t:
Bean o = (Bean) obj;
return (a == o.a) &&
(b == o.b) &&
(s == null ? o.s == null : s.equals(o.s));
"
(interactive)
(let* ((parm (or parm-name "obj"))
(var (or var-name "o"))
(class-tag (or class (semantic-current-tag)))
(class-name (semantic-tag-name class-tag))
(members (sort (jde-gen-get-serializable-members class-tag)
'jde-gen-get-primitives-first))
(super (car (semantic-tag-type-superclasses class-tag)))
(extends (and super (not (string= "Object" super)))))
(list 'l '>
class-name " " var " = (" class-name ") " parm ";" '>'n '>'n
"return "
(if jde-gen-equals-parens-around-expression "(")
(if extends (list 'l "super.equals(" var ")")) '>
(cons 'l (mapcar
(lambda (tag)
(let ((name (semantic-tag-name tag))
(type (semantic-tag-type tag)))
(list 'l (if extends (jde-gen-equals-add-and-operator)
(setq extends t) nil)
(cond
((and (string-match "\\`\\(byte\\|char\\|short\\|int\\|long\\|float\\|double\\|boolean\\)" type)
(or (string-match "\\[.*\\]" name) (string-match "\\[.*\\]" type)))
(let ((array (replace-regexp-in-string "\\[.*\\]" "" name)))
(concat "java.util.Arrays.equals(" array ", " var "." array ")")))
((or (string-match "\\[.*\\]" name) (string-match "\\[.*\\]" type))
(let ((array (replace-regexp-in-string "\\[.*\\]" "" name)))
(concat "java.util.Arrays.deepEquals(" array ", " var "." array ")")))
((or (semantic-tag-of-type-p tag "byte")
(semantic-tag-of-type-p tag "char")
(semantic-tag-of-type-p tag "short")
(semantic-tag-of-type-p tag "int")
(semantic-tag-of-type-p tag "long")
(semantic-tag-of-type-p tag "boolean"))
(concat "(" name " == " var "." name ")"))
((or (semantic-tag-of-type-p tag "float")
(semantic-tag-of-type-p tag "double"))
(concat "(" name " == " var "." name ")"))
(t (concat "(" name " == null ? " var "." name " == null : "
name ".equals(" var "." name "))" )))
'>))) members))
(if jde-gen-equals-parens-around-expression ")") ";")))
(defun jde-gen-equals-add-and-operator ()
(if jde-gen-equals-trailing-and-operators
(list 'l " &&" '> 'n '>)
(list 'l '> 'n '> "&& ")))
(defcustom jde-gen-hashcode-method-template
'("'>"
"\"/**\" '> 'n"
"\" * Calculate the hash code for this object.\" '> 'n"
"\" * \" '> 'n"
"\" * <p>The rules laid out in J. Blosh's Effective Java are used\" '> 'n"
"\" * for the hash code calculation.</p>\" '> 'n"
"\" * \" '> 'n"
"\" * @return the hash code.\" '> 'n"
"\" * \" '> 'n"
"\" * @see java.lang.Object#hashCode\" '> 'n"
"\" */\" '> 'n"
"(jde-gen-method-signature \"public\"\ \"int\" \"hashCode\" nil)"
"(jde-gen-electric-brace)"
"(jde-gen-hashcode-body) '> 'n"
"\"}\" '> 'n '>" )
"*Template for creating a hashCode method.
Setting this variable defines a template instantiation command
`jde-gen-hashcode-method', as a side-effect."
:group 'jde-gen
:type '(repeat string)
:set '(lambda (sym val)
(defalias 'jde-gen-hashcode-method
(tempo-define-template
"java-hashcode-method"
(jde-gen-read-template val)
nil
"Create a hashCode method at the current point."))
(set-default sym val)))
(defvar jde-gen-hashcode-primes
'(11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97)
"a list of all two digit prime numbers")
(defvar jde-gen-hashcode-current-prime 0
"a prime number")
(defun jde-gen-hashcode-next-prime ()
"Get the next prime number"
(let ((last jde-gen-hashcode-current-prime))
(setq jde-gen-hashcode-current-prime (% (+ 1 last)
(length jde-gen-hashcode-primes)))
(int-to-string (nth last jde-gen-hashcode-primes))))
(defun jde-gen-hashcode-body (&optional var-name class)
"Generate a body of a hashCode function.
This function gets the list of member variables of the current
class from `jde-gen-get-serializable-members'.
The first optional parameter `var-name' denotes the variable name used
to calculate the hash code, the default is \"code\".
The second optional parameter `class' can be a semantic tag, which is
then used instead of the result of `semantic-current-tag'.
"
(interactive)
(let* ((var (or var-name "code"))
(class-tag (or class (semantic-current-tag)))
(members (sort (jde-gen-get-serializable-members class-tag)
'jde-gen-get-primitives-first))
(super (car (semantic-tag-type-superclasses class-tag)))
(extends (and super (not (string= "Object" super)))))
(list 'l "int " var " = "
(if extends "super.hashCode()" (jde-gen-hashcode-next-prime)) ";"
'> 'n '> 'n
(cons
'l
(mapcar
(lambda (tag)
(let ((name (semantic-tag-name tag))
(type (semantic-tag-type tag)))
(list 'l var " = " var " * 37 + "
(cond
((and (string-match "\\`\\(byte\\|char\\|short\\|int\\|long\\|float\\|double\\|boolean\\)" type)
(or (string-match "\\[.*\\]" name) (string-match "\\[.*\\]" type)))
(let ((array (replace-regexp-in-string "\\[.*\\]" "" name)))
(concat "java.util.Arrays.hashCode(" array ")")))
((or (string-match "\\[.*\\]" name) (string-match "\\[\\]" type))
(let ((array (replace-regexp-in-string "\\[.*\\]" "" name)))
(concat "java.util.Arrays.deepHashCode(" array ")")))
((or (semantic-tag-of-type-p tag "byte")
(semantic-tag-of-type-p tag "char")
(semantic-tag-of-type-p tag "short"))
(concat "(int) " name))
((semantic-tag-of-type-p tag "int") name)
((semantic-tag-of-type-p tag "long")
(concat "(int) (" name " ^ (" name " >> 32))" ))
((semantic-tag-of-type-p tag "boolean")
(concat "(" name " ? 1 : 0)"))
((semantic-tag-of-type-p tag "float")
(concat "Float.floatToIntBits(" name ")" ))
((semantic-tag-of-type-p tag "double")
(concat "(int) (Double.doubleToLongBits(" name ") ^ (Double.doubleToLongBits(" name ") >> 32))" ))
(t
(concat "(" name " == null ? " (jde-gen-hashcode-next-prime)
" : " name ".hashCode())"))) ";"
'> 'n))) members))
'> 'n
"return " var ";")))
(defcustom jde-gen-tostring-method-template
'("'>"
"\"/**\" '> 'n"
"\" * Get a string representation of this object.\" '> 'n"
"\" * \" '> 'n"
"\" * @return a string representation of this object.\" '> 'n"
"\" * \" '> 'n"
"\" * @see java.lang.Object#toString\" '> 'n"
"\" */\" '> 'n"
"(jde-gen-method-signature \"public\" \"String\" \"toString\" \"\")"
"(jde-gen-electric-brace)"
"(jde-gen-tostring-return) '> 'n"
"\"}\" '> 'n '>"
"(jde-import-one-class \"org.apache.commons.lang.builder.ToStringBuilder\")")
"*Template for creating an toString method.
Setting this variable defines a template instantiation
command `jde-gen-tostring-method', as a side-effect."
:group 'jde-gen
:type '(repeat string)
:set '(lambda (sym val)
(defalias 'jde-gen-tostring-method
(tempo-define-template
"java-tostring-method"
(jde-gen-read-template val)
nil
"Create an toString method at the current point."))
(set-default sym val)))
(defun jde-gen-tostring-return (&optional class)
"Generate a body of an appropriate override for the
java.lang.Object#toString function. This gets the member variables
of the current class from semantic via `semantic-current-tag'.
This uses the ToStringBuilder class from the jakarta commons lang project.
"
(interactive)
(let* ((class-tag (or class (semantic-current-tag)))
(class-name (semantic-tag-name class-tag))
(members (jde-gen-get-member-variables class-tag))
(super (car (semantic-tag-type-superclasses class-tag)))
(extends (and super (not (string= "Object" super)))))
(list 'l '>
"return new ToStringBuilder(this)" ' > 'n
(if extends (list 'l ".appendSuper(super.toString())" '> 'n))
(cons 'l (mapcar
(lambda (tag)
(let ((name (semantic-tag-name tag)))
(list 'l ".append(\"" name "\", " name ")" '> 'n))) members))
".toString();")))
(defun jde-gen-object-methods ()
"Generates an equals(), a hashCode() and a toString method."
(interactive)
(jde-gen-equals-method)
(newline-and-indent)
(jde-gen-hashcode-method)
(newline-and-indent)
(jde-gen-tostring-method))
(if (not (fboundp 'replace-regexp-in-string))
(defsubst replace-regexp-in-string (REGEXP REP STRING &optional FIXEDCASE LITERAL SUBEXP START)
"XEmacs does not have `replace-regexp-in-string'"
(replace-in-string STRING REGEXP REP &optional LITERAL)))
(defun jde-gen-get-member-variables (&optional tag)
"Get all member variables of the current class as a semantic tag list.
The optional parameter `tag' can be a semantic tag, which is
then used as the current class instead of the result of `semantic-current-tag'."
(let ((curtag (or tag (semantic-current-tag))))
(remove nil
(mapcar
(lambda (member)
(if (semantic-tag-of-class-p member 'variable)
member
nil))
(semantic-tag-type-members curtag)))))
(defun jde-gen-get-member-functions (&optional tag)
"Get all member functions of the current class as a semantic tag list.
The optional parameter `tag' can be a semantic tag, which is
then used as the current class instead of the result of `semantic-current-tag'."
(let ((curtag (or tag (semantic-current-tag))))
(remove nil
(mapcar
(lambda (member) (if (semantic-tag-of-class-p member 'function) member nil))
(semantic-tag-type-members curtag)))))
(defun jde-gen-get-serializable-members (&optional tag)
"Get all serializable member variables of the current class as a semantic tag list.
The optional parameter `tag' can be a semantic tag, which is
then used as the current class instead of the result of `semantic-current-tag'."
(let ((curtag (or tag (semantic-current-tag))))
(remove nil
(mapcar
(lambda (member)
(let ((is-variable (semantic-tag-of-class-p member 'variable))
(modifiers (semantic-tag-modifiers member)))
(if (and is-variable (not (or (member "static" modifiers)
(member "transient" modifiers))))
member
nil)))
(semantic-tag-type-members curtag)))))
(defun jde-gen-equals-member-is-primitive (tag)
"Check if tag is of primitive type"
(and (or (semantic-tag-of-type-p tag "byte")
(semantic-tag-of-type-p tag "char")
(semantic-tag-of-type-p tag "short")
(semantic-tag-of-type-p tag "boolean")
(semantic-tag-of-type-p tag "int")
(semantic-tag-of-type-p tag "long"))
(jde-gen-equals-member-isnt-array tag)))
(defun jde-gen-equals-member-is-float (tag)
"Check if tag is of a floating point type"
(and (or (semantic-tag-of-type-p tag "float")
(semantic-tag-of-type-p tag "double")))
(jde-gen-equals-member-isnt-array tag))
(defun jde-gen-equals-member-isnt-array (tag)
"Check if tag is of an array type"
(not (or (string-match "\\[.*\\]" (semantic-tag-name tag))
(string-match "\\[.*\\]" (semantic-tag-type tag)))))
(defun jde-gen-get-primitives-first (a b)
"List sorter for a class' member list. Primitive types will be considered
lower, so that they are returned at the head of the list. This ensures they will be
processed first and the more costly complex types later."
(or (and (jde-gen-equals-member-is-primitive a)
(not (jde-gen-equals-member-is-primitive b)))
(and (jde-gen-equals-member-is-float a)
(not (or (jde-gen-equals-member-is-primitive b)
(jde-gen-equals-member-is-float b))))
(and (jde-gen-equals-member-isnt-array a)
(not (or (jde-gen-equals-member-is-primitive b)
(jde-gen-equals-member-is-float b)
(jde-gen-equals-member-isnt-array b))))
))
(defun jde-import-one-class (class)
"Insert an import into the buffer if nor already there."
(interactive "s")
(if (not (jde-import-already-imports-class class (jde-import-get-imports)))
(jde-import-insert-imports-into-buffer (list class))))
(defun jde-gen-get-current-class-name ()
"Return the name of the class around point."
(semantic-tag-name (semantic-current-tag)))
(defun jde-gen-get-class-buffer-name ()
"Get the class name from the buffer filename"
(file-name-sans-extension (file-name-nondirectory
(or (buffer-file-name) "Object.java"))))
(defcustom jde-gen-exception-buffer-template
(list
"(open-line 1) (funcall jde-gen-boilerplate-function)"
"(jde-gen-get-package-statement)"
"(progn (require 'jde-javadoc) (jde-javadoc-insert-start-block))"
"\" * Exception <code>\" (jde-gen-get-class-buffer-name) \"</code>.\" '> 'n"
"\" \" (jde-javadoc-insert-empty-line)"
"\" * Created: \" (current-time-string) '> 'n"
"\" \" (jde-javadoc-insert-empty-line)"
"\" \" (jde-gen-save-excursion (jde-javadoc-insert 'tempo-template-jde-javadoc-author-tag))"
"\" \" (jde-gen-save-excursion (jde-javadoc-insert 'tempo-template-jde-javadoc-version-tag))"
"\" \" (jde-javadoc-insert-end-block)"
"\"public class \""
"(jde-gen-get-class-buffer-name)" "\" \" (jde-gen-get-extend-class)"
"(jde-gen-electric-brace)"
"'p'n"
"'> (jde-javadoc-insert-start-block)"
"\"* Constructs a new <code>\" (jde-gen-get-class-buffer-name) \"</code> with\" '>'n"
"\"* <code>null</code> as its detail message.\" '>'n"
"'> (jde-javadoc-insert-end-block)"
"(jde-gen-method-signature \"public\" nil (jde-gen-get-class-buffer-name) nil)"
"(jde-gen-electric-brace)"
"\"}\"'>'n"
"'n"
"'> (jde-javadoc-insert-start-block)"
"\"* Constructs a new <code>\" (jde-gen-get-class-buffer-name) \"</code> with\" '>'n"
"\"* the specified detail message.\" '>'n"
"'> (jde-javadoc-insert-empty-line)"
"\"* @param message the detail message string.\" '> 'n"
"'> (jde-javadoc-insert-end-block)"
"(jde-gen-method-signature \"public\" nil (jde-gen-get-class-buffer-name) \"String message\")"
"(jde-gen-electric-brace)"
"\"super(message);\" '> 'n"
"\"}\" '> 'n"
"'n"
"'> (jde-javadoc-insert-start-block)"
"\"* Constructs a new <code>\" (jde-gen-get-class-buffer-name) \"</code> with\" '>'n"
"\"* the specified cause and a detail message of\" '> 'n"
"\"* <code>(cause == null ? null : cause.toString())</code>\" '> 'n"
"\"* (which typically contains the class and detail message of cause).\" '> 'n"
"'> (jde-javadoc-insert-empty-line)"
"\"* @param cause the causing throwable. A null value is permitted\" '> 'n"
"\"* and indicates that the cause is nonexistent or unknown.\" '> 'n"
"'> (jde-javadoc-insert-end-block)"
"(jde-gen-method-signature \"public\" nil (jde-gen-get-class-buffer-name) \"Throwable cause\")"
"(jde-gen-electric-brace)"
"\"super(cause == null ? (String) null : cause.toString());\" '> 'n"
"\"initCause(cause);\" '> 'n"
"\"}\" '> 'n"
"'n"
"'> (jde-javadoc-insert-start-block)"
"\"* Constructs a new <code>\" (jde-gen-get-class-buffer-name) \"</code> with\" '>'n"
"\"* the specified cause and message.\" '> 'n"
"'> (jde-javadoc-insert-empty-line)"
"\"* @param message the detail message string.\" '> 'n"
"\"* @param cause the causing throwable. A null value is permitted\" '> 'n"
"\"* and indicates that the cause is nonexistent or unknown.\" '> 'n"
"'> (jde-javadoc-insert-end-block)"
"(jde-gen-method-signature \"public\" nil (jde-gen-get-class-buffer-name) \"String message,Throwable cause\")"
"(jde-gen-electric-brace)"
"\"super(message);\" '> 'n"
"\"initCause(cause);\" '> 'n"
"\"}\" '> 'n"
"\"}\" '>" "(if jde-gen-comments (concat \" // \" (jde-gen-get-class-buffer-name)))"
"'>'n")
"*Template for a new exception class.
Setting this variable defines a template instantiation
command `jde-gen-exception', as a side-effect."
:group 'jde-gen
:type '(repeat string)
:set '(lambda (sym val)
(tempo-define-template "java-exception-buffer-template"
(jde-gen-read-template val)
nil
"Insert a generic Java exception buffer skeleton.")
(defalias 'jde-gen-exception
(list 'lambda (list)
(list 'interactive)
(list 'tempo-template-java-exception-buffer-template)))
(set-default sym val)))
(defun jde-gen-exception-buffer (file)
"Create a new Java buffer containing an exception class of the same name.
This command inserts the template generated by `jde-gen-exception'.
It then moves the point to the location of the first method."
(interactive "F")
(find-file file)
(jde-gen-exception))
(provide 'jde-gen-extra)