package pluginbase.config.field; import com.googlecode.gentyref.GenericTypeReflector; import pluginbase.config.annotation.Comment; import pluginbase.config.annotation.Description; import pluginbase.config.annotation.HandlePropertyWith; import pluginbase.config.annotation.Immutable; import pluginbase.config.annotation.Name; import pluginbase.config.annotation.SerializeWith; import pluginbase.config.annotation.ValidateWith; import pluginbase.config.properties.PropertyAliases; import pluginbase.config.properties.PropertyHandler; import pluginbase.config.properties.PropertyHandlers; import pluginbase.config.serializers.Serializer; import pluginbase.config.serializers.SerializerSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import pluginbase.config.util.PrimitivesUtil; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.WildcardType; import java.util.Collection; import java.util.Map; public class Field extends FieldMap { @NotNull private final java.lang.reflect.Field field; private final boolean persistable; private final boolean immutable; private final String name; private final Type type; private final Class typeClass; private final Class collectionType; private final Class mapType; @Nullable private final Class<? extends PropertyHandler> propertyHandlerClass; @Nullable private final Class<? extends Serializer> serializerClass; @Nullable private final Class<? extends Validator> validatorClass; @Nullable private final String description; @Nullable private final String[] comments; @Nullable public static FieldInstance getInstance(@NotNull Object object, @NotNull String... name) { if (name.length == 0) { throw new IllegalArgumentException("name cannot be 0 length array."); } if (name.length == 1) { String[] actualName = PropertyAliases.getPropertyName(object.getClass(), name[0]); if (actualName != null) { name = actualName; } } return new FieldInstance(object, name).locateField(); } Field(@NotNull java.lang.reflect.Field field) { this(field, null); } Field(@NotNull java.lang.reflect.Field field, @Nullable FieldMap children) { super(children == null ? null : children.fieldMap); this.field = field; this.persistable = !Modifier.isTransient(field.getModifiers()); this.name = getName(field); this.immutable = field.getAnnotation(Immutable.class) != null; this.type = determineActualType(field); this.typeClass = determineTypeClass(type); this.collectionType = determineCollectionType(type); this.mapType = determineMapType(type); this.propertyHandlerClass = getPropertyHandlerClass(field); this.serializerClass = getSerializerClass(field); this.validatorClass = getValidatorClass(field); this.description = getDescription(field); this.comments = getComments(field); } private String getName(@NotNull java.lang.reflect.Field field) { Name name = field.getAnnotation(Name.class); if (name == null) { name = field.getType().getAnnotation(Name.class); } if (name != null) { return name.value(); } else { return field.getName(); } } @NotNull private Type determineActualType(@NotNull java.lang.reflect.Field field) { Type type = field.getGenericType(); Class clazz = GenericTypeReflector.erase(type); if (VirtualField.class.isAssignableFrom(clazz)) { type = GenericTypeReflector.getExactSuperType(type, VirtualField.class); if (type instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) type; type = parameterizedType.getActualTypeArguments()[0]; } } return type; } @NotNull private Class determineTypeClass(@NotNull Type type) { if (type instanceof Class) { return PrimitivesUtil.switchForWrapper((Class) type); } else { if (type instanceof WildcardType) { return Object.class; } else { return GenericTypeReflector.erase(type); } } } @Nullable private Class determineCollectionType(@NotNull Type type) { if (Collection.class.isAssignableFrom(getType())) { Type collectionType = GenericTypeReflector.getExactSuperType(type, Collection.class); if (collectionType instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) collectionType; collectionType = parameterizedType.getActualTypeArguments()[0]; if (collectionType instanceof Class) { return (Class) collectionType; } } if (collectionType instanceof WildcardType) { return Object.class; } return GenericTypeReflector.erase(collectionType); } return null; } @Nullable private Class determineMapType(@NotNull Type type) { if (Map.class.isAssignableFrom(getType())) { Type mapType = GenericTypeReflector.getExactSuperType(type, Map.class); if (mapType instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) mapType; mapType = parameterizedType.getActualTypeArguments()[1]; if (mapType instanceof Class) { return (Class) mapType; } } if (mapType instanceof WildcardType) { return Object.class; } return GenericTypeReflector.erase(mapType); } return null; } @Nullable private Class<? extends PropertyHandler> getPropertyHandlerClass(@NotNull java.lang.reflect.Field field) { HandlePropertyWith handlePropertyWith = field.getAnnotation(HandlePropertyWith.class); if (handlePropertyWith != null) { return handlePropertyWith.value(); } else { return null; } } @Nullable private Class<? extends Serializer> getSerializerClass(@NotNull java.lang.reflect.Field field) { SerializeWith serializeWith = field.getAnnotation(SerializeWith.class); if (serializeWith != null) { return serializeWith.value(); } else { serializeWith = field.getType().getAnnotation(SerializeWith.class); if (serializeWith != null) { return serializeWith.value(); } } return null; } @Nullable private Class<? extends Validator> getValidatorClass(@NotNull java.lang.reflect.Field field) { ValidateWith validateWith = field.getAnnotation(ValidateWith.class); if (validateWith != null) { return validateWith.value(); } return null; } @Nullable private String getDescription(@NotNull java.lang.reflect.Field field) { Description description = field.getAnnotation(Description.class); if (description != null) { return description.value(); } else { description = field.getType().getAnnotation(Description.class); if (description != null) { return description.value(); } } return null; } @Nullable private String[] getComments(@NotNull java.lang.reflect.Field field) { Comment comment = field.getAnnotation(Comment.class); if (comment != null) { return comment.value(); } else { comment = field.getType().getAnnotation(Comment.class); if (comment != null) { return comment.value(); } } return null; } public String getName() { return name; } public boolean hasChildFields() { return fieldMap != null; } public boolean isPersistable() { return persistable; } public boolean isImmutable() { return immutable; } @Nullable public Class getSerializerClass() { return serializerClass; } @NotNull public Serializer getSerializer() { return getSerializer(SerializerSet.defaultSet()); } @NotNull public Serializer getSerializer(@NotNull SerializerSet serializerSet) { /* TODO: decide if override serializers are higher priority than @SerializeWith on a field. For now, they are lower priority. Serializer serializer = serializerSet.getOverrideSerializer(field.getType()); if (serializer != null) { return serializer; } */ if (getSerializerClass() != null) { return serializerSet.getSerializerInstance(getSerializerClass()); } return serializerSet.getClassSerializer(getType()); } @Nullable public Validator getValidator() { return validatorClass != null ? Validators.getValidator(validatorClass) : null; } @Nullable public Object getValue(@NotNull Object object) { boolean accessible = field.isAccessible(); if (!accessible) { field.setAccessible(true); } try { if (VirtualField.class.isAssignableFrom(field.getType())) { VirtualField vProp = (VirtualField) field.get(object); return vProp != null ? vProp.get() : null; } else { return field.get(object); } } catch (IllegalAccessException e) { throw new IllegalAccessError("This should never happen."); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("The specified object does not contain this field.", e); } finally { if (!accessible) { field.setAccessible(false); } } } public void setValue(@NotNull Object object, @Nullable Object value) throws PropertyVetoException { if (isImmutable()) { return; } forceSet(object, value); } public void forceSet(@NotNull Object object, @Nullable Object value) throws PropertyVetoException { boolean accessible = field.isAccessible(); if (!accessible) { field.setAccessible(true); } try { if (VirtualField.class.isAssignableFrom(field.getType())) { setVirtualProperty((VirtualField) field.get(object), value); } else { setProperty(object, value); } } catch (IllegalAccessException e) { throw new IllegalAccessError("This should never happen."); } catch (IllegalArgumentException e) { try { FieldMap fieldMap = FieldMapper.getFieldMap(object.getClass()); if (fieldMap.hasField(getName())) { throw new IllegalArgumentException("The specified value is not an instance of type " + getType(), e); } } catch (IllegalArgumentException ignore) { } throw new IllegalArgumentException("The specified object does not contain " + this, e); } finally { if (!accessible) { field.setAccessible(false); } } } private void setVirtualProperty(@Nullable VirtualField vProp, @Nullable Object value) throws PropertyVetoException { if (vProp == null) { return; } Validator validator = getValidator(); if (validator != null) { vProp.set(validator.validateChange(value, vProp.get())); } else { vProp.set(value); } } private void setProperty(@NotNull Object object, @Nullable Object value) throws IllegalAccessException, PropertyVetoException { Validator validator = getValidator(); if (validator != null) { field.set(object, validator.validateChange(value, field.get(object))); } else { field.set(object, value); } } @NotNull public Class getType() { return typeClass; } @Nullable public Class getCollectionType() { return collectionType; } @Nullable public Class getMapType() { return mapType; } @NotNull public PropertyHandler getPropertyHandler() { return propertyHandlerClass != null ? PropertyHandlers.getHandler(propertyHandlerClass) : PropertyHandlers.getDefaultHandler(); } @Nullable public String getDescription() { return description; } @Nullable public String[] getComments() { if (this.comments == null) { return null; } String[] comments = new String[this.comments.length]; System.arraycopy(this.comments, 0, comments, 0, this.comments.length); return comments; } @Override public String toString() { return "Field{" + "field=" + field + '}'; } }