package act.util;
/*-
* #%L
* ACT Framework
* %%
* Copyright (C) 2014 - 2017 ActFramework
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import act.asm.AnnotationVisitor;
import act.asm.Opcodes;
import act.asm.Type;
import org.osgl.$;
import org.osgl.util.C;
import org.osgl.util.E;
import org.osgl.util.S;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Map;
public class GeneralAnnoInfo {
public static class EnumInfo {
private Type type;
private String value;
EnumInfo(String desc, String value) {
this.type = Type.getType(desc);
this.value = value;
}
public Type type() {
return type;
}
public String value() {
return value;
}
}
private Type type;
private Map<String, Object> attributes = C.newMap();
private Map<String, List<Object>> listAttributes = C.newMap();
public GeneralAnnoInfo(Type type) {
E.NPE(type);
this.type = type;
}
public Type type() {
return type;
}
public Class annotationClass(ClassLoader classLoader) {
return $.classForName(type.getClassName(), classLoader);
}
public Map<String, Object> attributes() {
return C.map(attributes);
}
public Map<String, List<Object>> listAttributes() {
return C.map(listAttributes);
}
public GeneralAnnoInfo addAnnotation(String name, Type type) {
GeneralAnnoInfo anno = new GeneralAnnoInfo(type);
attributes.put(name, anno);
return anno;
}
public void putAttribute(String name, Object val) {
attributes.put(name, val);
}
public void putListAttribute(String name, Object val) {
List<Object> vals = listAttributes.get(name);
if (null == vals) {
vals = C.newList(val);
listAttributes.put(name, vals);
} else {
vals.add(val);
}
}
public Object getAttribute(String name) {
return attributes.get(name);
}
public List<Object> getListAttributes(String name) {
return listAttributes.get(name);
}
@Override
public int hashCode() {
return $.hc(type, attributes, listAttributes);
}
@Override
public String toString() {
C.List<String> keys = C.newList(attributes.keySet()).append(listAttributes.keySet()).sorted();
S.Buffer sb = S.newBuffer("@").append(type.getClassName()).append("(");
for (String key: keys) {
Object v = attributes.get(key);
if (null == v) {
v = listAttributes.get(v);
}
sb.append(key).append("=").append(v).append(", ");
}
if (!keys.isEmpty()) {
sb.delete(sb.length() - 2, sb.length());
}
sb.append(")");
return sb.toString();
}
public <T extends Annotation> T toAnnotation() {
return AnnotationInvocationHandler.proxy(this);
}
public static class Visitor extends AnnotationVisitor implements Opcodes {
private GeneralAnnoInfo anno;
public Visitor(AnnotationVisitor av, GeneralAnnoInfo anno) {
super(ASM5, av);
E.NPE(anno);
this.anno = anno;
}
@Override
public AnnotationVisitor visitAnnotation(String name, String desc) {
AnnotationVisitor av = super.visitAnnotation(name, desc);
GeneralAnnoInfo annoAnno = anno.addAnnotation(name, Type.getType(desc));
return av;
}
@Override
public void visit(String name, Object value) {
anno.putAttribute(name, value);
super.visit(name, value);
}
@Override
public void visitEnum(String name, String desc, String value) {
anno.putAttribute(name, new EnumInfo(desc, value));
super.visitEnum(name, desc, value);
}
@Override
public AnnotationVisitor visitArray(final String name) {
AnnotationVisitor av = super.visitArray(name);
return new AnnotationVisitor(ASM5, av) {
@Override
public void visitEnum(String ignore, String desc, String value) {
anno.putListAttribute(name, new EnumInfo(desc, value));
}
@Override
public void visit(String ignore, Object value) {
anno.putListAttribute(name, value);
}
};
}
}
public static class AnnotationInvocationHandler<T extends Annotation> implements Annotation, InvocationHandler, Serializable {
private static final long serialVersionUID = 8157022630814320170L;
private final GeneralAnnoInfo annoInfo;
private final Class<T> annotationType;
private final int hashCode;
private final Map<String, Object> values;
AnnotationInvocationHandler(GeneralAnnoInfo annoInfo, ClassLoader classLoader) {
this.annotationType = $.classForName(annoInfo.type().getClassName(), classLoader);
this.annoInfo = annoInfo;
this.hashCode = annoInfo.hashCode();
this.values = retrieveAnnotationValues(annoInfo, annotationType);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String key = method.getName();
Object val = values.get(key);
return null == val ? method.invoke(this, args) : val;
}
private <T> Object toArray(List<Object> list) {
Class<T> c = (Class<T>)list.get(0).getClass();
int size = list.size();
if (c == String.class) {
return list.toArray(new String[size]);
} else if (c == Class.class) {
return list.toArray(new Class[size]);
} else if (c == Boolean.class) {
return $.asPrimitive(list.toArray(new Boolean[size]));
} else if (c == Byte.class) {
return $.asPrimitive(list.toArray(new Byte[size]));
} else if (c == Short.class) {
return $.asPrimitive(list.toArray(new Short[size]));
} else if (c == Character.class) {
return $.asPrimitive(list.toArray(new Character[size]));
} else if (c == Integer.class) {
return $.asPrimitive(list.toArray(new Integer[size]));
} else if (c == Float.class) {
return $.asPrimitive(list.toArray(new Float[size]));
} else if (c == Long.class) {
return $.asPrimitive(list.toArray(new Long[size]));
} else if (c == Double.class) {
return $.asPrimitive(list.toArray(new Double[size]));
} else {
return list.toArray((T[])Array.newInstance(c, size));
}
}
@Override
public Class<? extends Annotation> annotationType() {
return annotationType;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!annotationType.isInstance(obj)) {
return false;
}
Annotation other = annotationType.cast(obj);
//compare annotation member values
for (Map.Entry<String, Object> member : annoInfo.attributes.entrySet()) {
Object value = member.getValue();
Object otherValue = getAnnotationMemberValue(other, member.getKey());
if (!$.eq2(value, otherValue)) {
return false;
}
}
for (Map.Entry<String, List<Object>> member : annoInfo.listAttributes.entrySet()) {
Object value = toArray(member.getValue());
Object otherValue = getAnnotationMemberValue(other, member.getKey());
if (!$.eq2(value, otherValue)) {
return false;
}
}
return true;
}
/**
* Calculates the hash code of this annotation proxy as described in
* {@link Annotation#hashCode()}.
*
* @return The hash code of this proxy.
* @see Annotation#hashCode()
*/
@Override
public int hashCode() {
return hashCode;
}
@Override
public String toString() {
return annoInfo.toString();
}
private Map<String, Object> retrieveAnnotationValues(GeneralAnnoInfo info, Class<T> type) {
Map<String, Object> bag = C.newMap();
for (String key : info.attributes.keySet()) {
bag.put(key, info.getAttribute(key));
}
for (String key : info.listAttributes.keySet()) {
bag.put(key, toArray(info.getListAttributes(key)));
}
Method[] ma = type.getDeclaredMethods();
for (Method m: ma) {
String mn = m.getName();
if (!bag.containsKey(mn)) {
bag.put(mn, m.getDefaultValue());
}
}
return bag;
}
private Object getAnnotationMemberValue(Annotation annotation, String name) {
try {
Method method = annotation.getClass().getMethod(name, new Class<?>[]{});
return method.invoke(annotation);
} catch (Exception e) {
throw E.unexpected(e);
}
}
public static <T extends Annotation> T proxy(GeneralAnnoInfo info) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return proxy(info, cl);
}
public static <T extends Annotation> T proxy(GeneralAnnoInfo info, ClassLoader cl) {
AnnotationInvocationHandler handler = new AnnotationInvocationHandler(info, cl);
return (T) Proxy.newProxyInstance(cl, new Class[]{handler.annotationType()}, handler);
}
}
}