package jalse.attributes;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Objects;
/**
* AttributeTypes and their values can be considered the core data of the JALSE model. AttributeType
* defines what type the data must be. {@link AttributeContainer} is an attribute container where
* attributes can be stored and {@link AttributeListener} can be set to trigger on value updates.
* <br>
* <br>
* For simple value types (no type arguments) {@link Attributes#newTypeOf(Class)} can be used:
*
* <pre>
* <code>
* AttributeType{@code <String>} stringType = Attributes.newTypeOf(String.class);
* </code>
* </pre>
*
* For more complex types an anonymous (or declared) subclasses should be created supplying the
* intended value type as the type argument. This is needed so full generic type information can be
* retrieved then raw and parameterised types can be differentiated:
*
* <pre>
* <code>
* AttributeType{@code <Collection<String>>} colStringType = new AttributeType{@code <Collection<String>>}(){};
* </code>
* </pre>
*
* When using reflection or where type erasure is not possible
* {@link Attributes#newUnknownType(Type)} can be used. This will be unique to the type not to
* {@code Object.class}.<br>
* <br>
* AttributeTypes are meant to be easily distinguishable but for full generic information a
* Anonymous class is needed. To ensure this behaviour and so that AttributeType can be used as a
* unique key {@link #equals(Object)}, {@link #hashCode()} and {@link #getValueType()} are all
* {@code final}.
*
* @author Elliot Ford
* @param <T>
* Value type.
*
* @see AttributeContainer
* @see AttributeListener
* @see DefaultAttributeContainer
* @see Attributes
*
*/
public abstract class AttributeType<T> {
/**
* Attribute value type.
*/
protected final Type valueType;
/**
* Creates a new instance of Attribute type (using generic type information).
*/
public AttributeType() throws IllegalStateException {
/*
* Get direct superclass of AttributeType.
*/
Class<?> superType = getClass();
while (!superType.getSuperclass().equals(AttributeType.class)) {
superType = superType.getSuperclass();
}
/*
* Get generic parameter type.
*/
final ParameterizedType genericSuperType = (ParameterizedType) getClass().getGenericSuperclass();
final Type typeArg = genericSuperType.getActualTypeArguments()[0]; // T
if (typeArg instanceof TypeVariable<?>) { // Could not get generic type argument.
throw new IllegalStateException(
"AttributeType must be initialised in a way that provides generic type information: new AttributeType<String>{}");
}
this.valueType = typeArg;
}
AttributeType(final Type valueType) {
this.valueType = Objects.requireNonNull(valueType);
}
@Override
public final boolean equals(final Object obj) {
return obj == this || obj instanceof AttributeType<?> && valueType.equals(((AttributeType<?>) obj).valueType);
}
/**
* Gets attribute value type.
*
* @return Value type.
*/
public final Type getValueType() {
return valueType;
}
@Override
public final int hashCode() {
return 31 * 1 + valueType.hashCode();
}
@Override
public String toString() {
return "AttributeType [" + valueType.getTypeName() + "]";
}
}