package br.com.caelum.iogi.reflection;
import br.com.caelum.iogi.DependenciesInjector;
import br.com.caelum.iogi.spi.ParameterNamesProvider;
import com.google.common.collect.ImmutableMap;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Map;
public class Target<T> {
public static <T> Target<T> create(final Class<T> type, final String name) {
return new Target<T>(type, name);
}
private final Type type;
private final String name;
public Target(final Type type, final String name) {
this.type = type;
this.name = name;
}
public Class<T> getClassType() {
// This could be pre-computed if needed for performance
return findRawClassType(type);
}
@SuppressWarnings("unchecked")
private Class<T> findRawClassType(final Type type) {
if (type instanceof ParameterizedType)
return findRawClassType(((ParameterizedType)type).getRawType());
else
return (Class<T>)type;
}
public String getName() {
return name;
}
public T cast(final Object object) {
if (getClassType().isPrimitive())
return Primitives.primitiveCast(object, getClassType());
return getClassType().cast(object);
}
public boolean isInstantiable() {
return !getClassType().isInterface() && !Modifier.isAbstract(getClassType().getModifiers()) && getClassType() != Void.class;
}
public Type getType() {
return type;
}
public Target<Object> typeArgument(final int index) {
final ParameterizedType thisAsParameterizedType = (ParameterizedType)this.getType();
final Type[] typeArguments = thisAsParameterizedType.getActualTypeArguments();
final Type typeArgument = typeArguments[index];
return new Target<Object>(typeArgument, this.getName());
}
public Class<?> arrayElementType() {
return getClassType().getComponentType();
}
public Target<?> arrayElementTarget() {
return Target.create(arrayElementType(), getName());
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((type == null) ? 0 : type.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final Target<?> other = (Target<?>) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (type == null) {
if (other.type != null)
return false;
} else if (!type.equals(other.type))
return false;
return true;
}
@Override
public String toString() {
return String.format("Target(name=%s, type=%s)", name, getClassType());
}
public Constructors constructors(ParameterNamesProvider parameterNamesProvider, DependenciesInjector dependenciesInjector) {
final HashSet<ClassConstructor> classConstructors = new HashSet<ClassConstructor>();
for (final Constructor<?> constructor : getClassType().getDeclaredConstructors()) {
if (!constructor.isSynthetic())
classConstructors.add(new ClassConstructor(constructor, parameterNamesProvider));
}
return new Constructors(classConstructors, dependenciesInjector);
}
public boolean isParameterized() {
return this.getType() instanceof ParameterizedType;
}
private static class Primitives {
private static final Map<Class<?>, Class<?>> primitiveToObject = ImmutableMap.<Class<?>, Class<?>>builder()
.put(Boolean.TYPE, Boolean.class)
.put(Character.TYPE, Character.class)
.put(Byte.TYPE, Byte.class)
.put(Short.TYPE, Short.class)
.put(Integer.TYPE, Integer.class)
.put(Long.TYPE, Long.class)
.put(Float.TYPE, Float.class)
.put(Double.TYPE, Double.class)
.put(Void.TYPE, Void.class)
.build();
private Primitives() {}
@SuppressWarnings("unchecked")
public static <T> T primitiveCast(final Object object, final Class<T> type) {
return (T) primitiveToObject.get(type).cast(object);
}
}
}