package com.google.gwt.reflect.test.annotations;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.reflect.shared.GwtReflect;
import java.lang.annotation.Annotation;
import java.util.Arrays;
/**
* A somewhat ugly, but functional implementation of an annotation;
* it should never be used in production, but it exposes a relatively simple
* and correct api for "how an annotation should behave".
* <p>
*
* @author "james@wetheinter.net"
*
*/
public abstract class AbstractAnnotation extends SourceVisitor{
public static enum MemberType {
Boolean, Byte, Short, Int, Long, Float, Double, Class, Enum, String, Annotation,
Boolean_Array, Byte_Array, Short_Array, Int_Array, Long_Array, Float_Array,
Double_Array, Class_Array, Enum_Array, String_Array, Annotation_Array
}
private final JavaScriptObject memberMap;
public AbstractAnnotation() {
this(JavaScriptObject.createObject());
}
public AbstractAnnotation(final JavaScriptObject memberMap) {
this.memberMap = memberMap;
}
public abstract Class<? extends Annotation> annotationType();
protected abstract String[] members();
protected abstract MemberType[] memberTypes();
protected final native <T> T getValue(String member)
/*-{
return this.@com.google.gwt.reflect.test.annotations.AbstractAnnotation::memberMap[member];
}-*/;
protected final native void setValue(String member, Object value)
/*-{
this.@com.google.gwt.reflect.test.annotations.AbstractAnnotation::memberMap[member] = value;
}-*/;
@Override
public final int hashCode() {
final String[] members = members();
final MemberType[] types = memberTypes();
int hash = 0, i = members.length;
for (; i-- > 0;) {
final String member = members[i];
switch (types[i]) {
case Annotation:
case Class:
case Enum:
case String:
hash += ((127) * member.hashCode()) ^ getObjectHash(member);
break;
case Boolean:
hash += ((127) * member.hashCode()) ^ getBoolInt(member);
break;
case Byte:
case Int:
case Short:
case Float:
case Double:
hash += ((127) * member.hashCode()) ^ getInt(member);
break;
case Long:
hash += ((127) * member.hashCode()) ^ (int)getLong(memberMap, member);
break;
case Annotation_Array:
case Class_Array:
case Enum_Array:
case String_Array:
hash += ((127) * member.hashCode()) ^ Arrays.hashCode(this.<Object[]>getValue(member));
break;
case Boolean_Array:
case Byte_Array:
case Int_Array:
case Short_Array:
case Float_Array:
case Double_Array:
hash += ((127) * member.hashCode()) ^ getPrimitiveArrayHash(member);
break;
case Long_Array:
hash += ((127) * member.hashCode()) ^ Arrays.hashCode(this.<long[]>getValue(member));
}
}
return hash;
}
@Override
public String toString() {
final StringBuilder b = new StringBuilder("@");
b.append(annotationType().getName());
b.append(" (");
final String[] members = members();
if (members.length == 0) {
b.append(")");
return b.toString();
}
final MemberType[] types = memberTypes();
b.append(members[0]);
b.append(" = ");
b.append(toString(members[0], types[0]));
for (int i = 1, m = members.length; i < m; i++) {
b.append(", ");
b.append(members[i]);
b.append(" = ");
b.append(toString(members[i], types[i]));
}
b.append(")");
return b.toString();
}
/**
* From java.lang.annotation:
* Returns true if the specified object represents an annotation that is
* logically equivalent to this one. In other words, returns true if the specified object is an instance of
* the same annotation type as this instance, all of whose members are equal to the corresponding member of
* this annotation, as defined below:
* <ul>
* <li>Two corresponding primitive typed members whose values are <tt>x</tt> and <tt>y</tt> are considered
* equal if <tt>x == y</tt>, unless their type is <tt>float</tt> or <tt>double</tt>.
* <li>Two corresponding <tt>float</tt> members whose values are <tt>x</tt> and <tt>y</tt> are considered
* equal if <tt>Float.valueOf(x).equals(Float.valueOf(y))</tt>. (Unlike the <tt>==</tt> operator, NaN is
* considered equal to itself, and <tt>0.0f</tt> unequal to <tt>-0.0f</tt>.)
* <li>Two corresponding <tt>double</tt> members whose values are <tt>x</tt> and <tt>y</tt> are considered
* equal if <tt>Double.valueOf(x).equals(Double.valueOf(y))</tt>. (Unlike the <tt>==</tt> operator, NaN is
* considered equal to itself, and <tt>0.0</tt> unequal to <tt>-0.0</tt>.)
* <li>Two corresponding <tt>String</tt>, <tt>Class</tt>, enum, or annotation typed members whose values are
* <tt>x</tt> and <tt>y</tt> are considered equal if <tt>x.equals(y)</tt>. (Note that this definition is
* recursive for annotation typed members.)
* <li>Two corresponding array typed members <tt>x</tt> and <tt>y</tt> are considered equal if
* <tt>Arrays.equals(x, y)</tt>, for the appropriate overloading of {@link java.util.Arrays#equals}.
* </ul>
*
* @return true if the specified object represents an annotation that is logically equivalent to this one,
* otherwise false
*/
@Override
public final boolean equals(final Object o) {
if (o == this) {
return true;
}
if (o instanceof AbstractAnnotation) {
final AbstractAnnotation you = (AbstractAnnotation)o;
final MemberType[] myTypes = memberTypes();
if (!Arrays.equals(myTypes, you.memberTypes())) {
return false;
}
final String[] myMembers = members();
final String[] yourMembers = you.members();
// These member lists are generated, so they will always be in the same order for the same type
if (myMembers.length == yourMembers.length) {
for (int i = myMembers.length; i-- > 0;) {
final String key = myMembers[i];
if (!key.equals(yourMembers[i])) {
return false;
}
final MemberType type = myTypes[i];
assert !isNull(key);
assert !you.isNull(key);
switch (type) {
case Annotation:
if (getValue(key).equals(you.getValue(key))) {
continue;
}
return false;
case Class:
case Enum:
case String:
case Boolean:
case Byte:
case Int:
case Short:
case Float:
case Double:
if (quickEquals(key, you.memberMap)) {
continue;
}
return false;
case Long:
if (getLong(memberMap, key) == getLong(you.memberMap, key)) {
continue;
}
return false;
case Annotation_Array:
case Class_Array:
case Enum_Array:
case String_Array:
if (arraysEqualObject(getValue(key), you.getValue(key))) {
continue;
}
return false;
case Boolean_Array:
case Byte_Array:
case Int_Array:
case Short_Array:
case Float_Array:
case Double_Array:
if (arraysEqualPrimitive(getValue(key), you.getValue(key))) {
continue;
}
return false;
case Long_Array:
if (arraysEqualLong(getValue(key), you.getValue(key))) {
continue;
}
return false;
}
}// end for loop
return true;
}
}
return false;
}
private String toString(final String key, final MemberType memberType) {
switch (memberType) {
case Class:
final Class<?> c = getValue(key);
return c.getName() + ".class";
case Enum:
final Enum<?> e = getValue(key);
return e.getDeclaringClass().getName() + "." + e.name();
case Boolean:
case Byte:
case Int:
case Short:
case Float:
case Double:
return getNativeString(key);
case Long:
return String.valueOf(getLong(memberMap, key));
case Class_Array:
StringBuilder b = new StringBuilder("{ ");
final Class<?>[] classes = this.<Class<?>[]>getValue(key);
if (classes.length > 0) {
b.append(classes[0].getName()).append(".class");
for (int i = 1, m = classes.length; i < m; i++) {
b.append(", ").append(classes[i].getName()).append(".class");
}
}
b.append(" }");
return b.toString();
case Enum_Array:
b = new StringBuilder("{ ");
final Enum<?>[] enums = this.<Enum<?>[]>getValue(key);
if (enums.length > 0) {
b.append(enums[0].getDeclaringClass().getName()).append(".").append(enums[0].name());
for (int i = 1, m = enums.length; i < m; i++) {
b.append(", ").append(enums[i].getDeclaringClass().getName()).append(".").append(enums[i].name());
}
}
b.append(" }");
return b.toString();
case String_Array:
b = new StringBuilder("{ ");
final String[] strings = this.<String[]>getValue(key);
if (strings.length > 0) {
b.append('"').append(strings[0]).append('"');
for (int i = 1, m = strings.length; i < m; i++) {
b.append(", \"").append(strings[i]).append('"');
}
}
b.append(" }");
return b.toString();
case Long_Array:
b = new StringBuilder("{ ");
final long[] longs = this.<long[]>getValue(key);
if (longs.length > 0) {
b.append(longs[0]);
if (longs[0] > Integer.MAX_VALUE) {
b.append('L');
}
for (int i = 1, m = longs.length; i < m; i++) {
b.append(", ").append(longs[i]);
if (longs[i] > Integer.MAX_VALUE) {
b.append('L');
}
}
}
b.append(" }");
return b.toString();
case Annotation_Array:
case Boolean_Array:
case Byte_Array:
case Int_Array:
case Short_Array:
case Float_Array:
case Double_Array:
b = new StringBuilder("{ ");
arrayToString(b, key);
b.append(" }");
return b.toString();
case String:
return "\"" + GwtReflect.escape(this.<String>getValue(key)) + "\"";
case Annotation:
default:
final Object o = getValue(key);
return String.valueOf(o);
}
}
private native void arrayToString(StringBuilder b, String key)
/*-{
var i = 0, o = this.@com.google.gwt.reflect.test.annotations.AbstractAnnotation::memberMap[key], m = o.length;
if (m > 0) {
b.@java.lang.StringBuilder::append(Ljava/lang/String;)(""+o[i++]);
for (;i < m; i++) {
b.@java.lang.StringBuilder::append(Ljava/lang/String;)(", ");
b.@java.lang.StringBuilder::append(Ljava/lang/String;)(""+o[i]);
}
}
}-*/;
private final native String getNativeString(String key)
/*-{
return ''+this.@com.google.gwt.reflect.test.annotations.AbstractAnnotation::memberMap[key];
}-*/;
private native int getPrimitiveArrayHash(String member)
/*-{
var hash = 1;
var arr = this.@com.google.gwt.reflect.test.annotations.AbstractAnnotation::memberMap[member];
for ( var i in arr) {
hash += 31*(~~arr[i]);// GWT's number hash codes just cast to int anyway
}
return hash;
}-*/;
private native int getInt(String member)
/*-{
return this.@com.google.gwt.reflect.test.annotations.AbstractAnnotation::memberMap[member];
}-*/;
private native int getBoolInt(String member)
/*-{
return this.@com.google.gwt.reflect.test.annotations.AbstractAnnotation::memberMap[member] ? 1 : 0;
}-*/;
private final int getObjectHash(final String member) {
final Object value = getValue(member);
assert value != null : "Annotations can never have null values. No member " + member + " in " + this;
return value.hashCode();
}
private final native boolean isNull(String key)
/*-{
return this.@com.google.gwt.reflect.test.annotations.AbstractAnnotation::memberMap[key] == null;
}-*/;
private final native boolean quickEquals(String key, JavaScriptObject you)
/*-{
return you[key] === this.@com.google.gwt.reflect.test.annotations.AbstractAnnotation::memberMap[key];
}-*/;
}