/*
* Javassist, a Java-bytecode translator toolkit.
* Copyright (C) 2004 Bill Burke. All Rights Reserved.
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. Alternatively, the contents of this file may be used under
* the terms of the GNU Lesser General Public License Version 2.1 or later,
* or the Apache License Version 2.0.
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*/
package javassist.bytecode.annotation;
import java.io.
IOException;
import java.util.
LinkedHashMap;
import java.util.
Map;
import java.util.
Set;
import javassist.
ClassPool;
import javassist.
CtClass;
import javassist.
CtMethod;
import javassist.
NotFoundException;
import javassist.bytecode.
ConstPool;
import javassist.bytecode.
Descriptor;
/**
* The <code>annotation</code> structure.
*
* <p>An instance of this class is returned by
* <code>getAnnotations()</code> in <code>AnnotationsAttribute</code>
* or in <code>ParameterAnnotationsAttribute</code>.
*
* @see javassist.bytecode.AnnotationsAttribute#getAnnotations()
* @see javassist.bytecode.ParameterAnnotationsAttribute#getAnnotations()
* @see MemberValue
* @see MemberValueVisitor
* @see AnnotationsWriter
*
* @author <a href="mailto:bill@jboss.org">Bill Burke</a>
* @author Shigeru Chiba
* @author <a href="mailto:adrian@jboss.org">Adrian Brock</a>
*/
public class
Annotation {
static class
Pair {
int
name;
MemberValue value;
}
ConstPool pool;
int
typeIndex;
Map<
String,
Pair>
members; // this sould be LinkedHashMap
// but it is not supported by JDK 1.3.
/**
* Constructs an annotation including no members. A member can be
* later added to the created annotation by <code>addMemberValue()</code>.
*
* @param type the index into the constant pool table.
* the entry at that index must be the
* <code>CONSTANT_Utf8_Info</code> structure
* repreenting the name of the annotation interface type.
* @param cp the constant pool table.
*
* @see #addMemberValue(String, MemberValue)
*/
public
Annotation(int
type,
ConstPool cp) {
pool =
cp;
typeIndex =
type;
members = null;
}
/**
* Constructs an annotation including no members. A member can be
* later added to the created annotation by <code>addMemberValue()</code>.
*
* @param typeName the fully-qualified name of the annotation interface type.
* @param cp the constant pool table.
*
* @see #addMemberValue(String, MemberValue)
*/
public
Annotation(
String typeName,
ConstPool cp) {
this(
cp.
addUtf8Info(
Descriptor.
of(
typeName)),
cp);
}
/**
* Constructs an annotation that can be accessed through the interface
* represented by <code>clazz</code>. The values of the members are
* not specified.
*
* @param cp the constant pool table.
* @param clazz the interface.
* @throws NotFoundException when the clazz is not found
*/
public
Annotation(
ConstPool cp,
CtClass clazz)
throws
NotFoundException
{
// todo Enums are not supported right now.
this(
cp.
addUtf8Info(
Descriptor.
of(
clazz.
getName())),
cp);
if (!
clazz.
isInterface())
throw new
RuntimeException(
"Only interfaces are allowed for Annotation creation.");
CtMethod[]
methods =
clazz.
getDeclaredMethods();
if (
methods.length > 0)
members = new
LinkedHashMap<
String,
Pair>();
for (
CtMethod m:
methods)
addMemberValue(
m.
getName(),
createMemberValue(
cp,
m.
getReturnType()));
}
/**
* Makes an instance of <code>MemberValue</code>.
*
* @param cp the constant pool table.
* @param type the type of the member.
* @return the member value
* @throws NotFoundException when the type is not found
*/
public static
MemberValue createMemberValue(
ConstPool cp,
CtClass type)
throws
NotFoundException
{
if (
type ==
CtClass.
booleanType)
return new
BooleanMemberValue(
cp);
else if (
type ==
CtClass.
byteType)
return new
ByteMemberValue(
cp);
else if (
type ==
CtClass.
charType)
return new
CharMemberValue(
cp);
else if (
type ==
CtClass.
shortType)
return new
ShortMemberValue(
cp);
else if (
type ==
CtClass.
intType)
return new
IntegerMemberValue(
cp);
else if (
type ==
CtClass.
longType)
return new
LongMemberValue(
cp);
else if (
type ==
CtClass.
floatType)
return new
FloatMemberValue(
cp);
else if (
type ==
CtClass.
doubleType)
return new
DoubleMemberValue(
cp);
else if (
type.
getName().
equals("java.lang.Class"))
return new
ClassMemberValue(
cp);
else if (
type.
getName().
equals("java.lang.String"))
return new
StringMemberValue(
cp);
else if (
type.
isArray()) {
CtClass arrayType =
type.
getComponentType();
MemberValue member =
createMemberValue(
cp,
arrayType);
return new
ArrayMemberValue(
member,
cp);
}
else if (
type.
isInterface()) {
Annotation info = new
Annotation(
cp,
type);
return new
AnnotationMemberValue(
info,
cp);
}
else {
// treat as enum. I know this is not typed,
// but JBoss has an Annotation Compiler for JDK 1.4
// and I want it to work with that. - Bill Burke
EnumMemberValue emv = new
EnumMemberValue(
cp);
emv.
setType(
type.
getName());
return
emv;
}
}
/**
* Adds a new member.
*
* @param nameIndex the index into the constant pool table.
* The entry at that index must be
* a <code>CONSTANT_Utf8_info</code> structure.
* structure representing the member name.
* @param value the member value.
*/
public void
addMemberValue(int
nameIndex,
MemberValue value) {
Pair p = new
Pair();
p.
name =
nameIndex;
p.
value =
value;
addMemberValue(
p);
}
/**
* Adds a new member.
*
* @param name the member name.
* @param value the member value.
*/
public void
addMemberValue(
String name,
MemberValue value) {
Pair p = new
Pair();
p.
name =
pool.
addUtf8Info(
name);
p.
value =
value;
if (
members == null)
members = new
LinkedHashMap<
String,
Pair>();
members.
put(
name,
p);
}
private void
addMemberValue(
Pair pair) {
String name =
pool.
getUtf8Info(
pair.
name);
if (
members == null)
members = new
LinkedHashMap<
String,
Pair>();
members.
put(
name,
pair);
}
/**
* Returns a string representation of the annotation.
*/
@
Override
public
String toString() {
StringBuffer buf = new
StringBuffer("@");
buf.
append(
getTypeName());
if (
members != null) {
buf.
append("(");
for (
String name:
members.
keySet()) {
buf.
append(
name).
append("=")
.
append(
getMemberValue(
name))
.
append(", ");
}
buf.
setLength(
buf.
length()-2);
buf.
append(")");
}
return
buf.
toString();
}
/**
* Obtains the name of the annotation type.
*
* @return the type name
*/
public
String getTypeName() {
return
Descriptor.
toClassName(
pool.
getUtf8Info(
typeIndex));
}
/**
* Obtains all the member names.
*
* @return null if no members are defined.
*/
public
Set<
String>
getMemberNames() {
if (
members == null)
return null;
return
members.
keySet();
}
/**
* Obtains the member value with the given name.
*
* <p>If this annotation does not have a value for the
* specified member,
* this method returns null. It does not return a
* <code>MemberValue</code> with the default value.
* The default value can be obtained from the annotation type.
*
* @param name the member name
* @return null if the member cannot be found or if the value is
* the default value.
*
* @see javassist.bytecode.AnnotationDefaultAttribute
*/
public
MemberValue getMemberValue(
String name) {
if (
members == null||
members.
get(
name) == null)
return null;
return
members.
get(
name).
value;
}
/**
* Constructs an annotation-type object representing this annotation.
* For example, if this annotation represents <code>@Author</code>,
* this method returns an <code>Author</code> object.
*
* @param cl class loader for loading an annotation type.
* @param cp class pool for obtaining class files.
* @return the annotation
* @throws ClassNotFoundException if the class cannot found.
* @throws NoSuchClassError if the class linkage fails.
*/
public
Object toAnnotationType(
ClassLoader cl,
ClassPool cp)
throws
ClassNotFoundException,
NoSuchClassError
{
Class<?>
clazz =
MemberValue.
loadClass(
cl,
getTypeName());
try {
return
AnnotationImpl.
make(
cl,
clazz,
cp, this);
}
catch (
IllegalArgumentException e) {
/* AnnotationImpl.make() may throw this exception
* when it fails to make a proxy object for some
* reason.
*/
throw new
ClassNotFoundException(
clazz.
getName(),
e);
}
catch (
IllegalAccessError e2) {
// also IllegalAccessError
throw new
ClassNotFoundException(
clazz.
getName(),
e2);
}
}
/**
* Writes this annotation.
*
* @param writer the output.
* @throws IOException for an error during the write
*/
public void
write(
AnnotationsWriter writer) throws
IOException {
String typeName =
pool.
getUtf8Info(
typeIndex);
if (
members == null) {
writer.
annotation(
typeName, 0);
return;
}
writer.
annotation(
typeName,
members.
size());
for (
Pair pair:
members.
values()) {
writer.
memberValuePair(
pair.
name);
pair.
value.
write(
writer);
}
}
@
Override
public int
hashCode() {
return
getTypeName().
hashCode() +
(
members == null ? 0 :
members.
hashCode());
}
/**
* Returns true if the given object represents the same annotation
* as this object. The equality test checks the member values.
*/
@
Override
public boolean
equals(
Object obj) {
if (
obj == this)
return true;
if (
obj == null ||
obj instanceof
Annotation == false)
return false;
Annotation other = (
Annotation)
obj;
if (
getTypeName().
equals(
other.
getTypeName()) == false)
return false;
Map<
String,
Pair>
otherMembers =
other.
members;
if (
members ==
otherMembers)
return true;
else if (
members == null)
return
otherMembers == null;
else
if (
otherMembers == null)
return false;
else
return
members.
equals(
otherMembers);
}
}