package com.github.czyzby.autumn.mvc.component.preferences.dto; import com.badlogic.gdx.Preferences; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.ui.Button; import com.badlogic.gdx.scenes.scene2d.ui.Label; import com.badlogic.gdx.scenes.scene2d.ui.List; import com.badlogic.gdx.scenes.scene2d.ui.SelectBox; import com.badlogic.gdx.scenes.scene2d.ui.TextButton; import com.badlogic.gdx.scenes.scene2d.ui.TextField; import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.reflect.Field; import com.badlogic.gdx.utils.reflect.ReflectionException; import com.github.czyzby.kiwi.util.common.Strings; import com.github.czyzby.kiwi.util.gdx.reflection.Reflection; /** Reflected field wrapper. Treats a field as a preference. * * @author MJ */ public class ReflectionPreference implements Preference<Object> { private final Object owner; private final Field field; private final Type type; public ReflectionPreference(final Object owner, final Field field) throws GdxRuntimeException { this.owner = owner; this.field = field; type = Type.getType(field); if (type == null) { throw new GdxRuntimeException("Invalid field annotated with @Property: unable to handle: " + field.getType() + " in field: " + field + " of component: " + owner); } } @Override public void read(final String name, final Preferences preferences) throws Exception { Reflection.setFieldValue(field, owner, type.convert(preferences.getString(name))); } @Override public Object getDefault() { return type.getDefault(); } @Override public Object extractFromActor(final Actor actor) { return type.extractFromActor(actor); } @Override public Object get() { try { return Reflection.getFieldValue(field, owner); } catch (final ReflectionException exception) { throw new GdxRuntimeException("Unable to extract field value from component: " + owner, exception); } } @Override public void set(final Object preference) { try { Reflection.setFieldValue(field, owner, preference); } catch (final ReflectionException exception) { throw new GdxRuntimeException("Unable to set field value in component: " + owner, exception); } } @Override public void save(final String name, final Preferences preferences) { preferences.putString(name, String.valueOf(get())); } /** Contains all supported types. * * @author MJ */ private static enum Type { BOOLEAN(boolean.class, Boolean.class) { @Override public Object convert(final String raw) { return Boolean.valueOf(raw); } @Override public Object getDefault() { return Boolean.TRUE; } @Override public Object extractFromActor(final Actor actor) { if (actor instanceof Button) { return ((Button) actor).isChecked(); } throw new GdxRuntimeException("Cannot use default setter of boolean preference with actor: " + actor); } }, INT(int.class, Integer.class) { @Override public Object convert(final String raw) { return Integer.valueOf(raw); } @Override public Object getDefault() { return Integer.valueOf(0); } @Override public Object extractFromActor(final Actor actor) { if (actor instanceof TextField) { return Integer.valueOf(((TextField) actor).getText()); } else if (actor instanceof List<?>) { return ((List<?>) actor).getSelectedIndex(); } else if (actor instanceof SelectBox<?>) { return ((SelectBox<?>) actor).getSelectedIndex(); } throw new GdxRuntimeException("Cannot use default setter of int preference with actor: " + actor); } }, FLOAT(float.class, Float.class) { @Override public Object convert(final String raw) { return Float.valueOf(raw); } @Override public Object getDefault() { return Float.valueOf(0f); } @Override public Object extractFromActor(final Actor actor) { if (actor instanceof TextField) { return Float.valueOf(((TextField) actor).getText()); } throw new GdxRuntimeException("Cannot use default setter of float preference with actor: " + actor); } }, LONG(long.class, Long.class) { @Override public Object convert(final String raw) { return Long.valueOf(raw); } @Override public Object getDefault() { return Long.valueOf(0L); } @Override public Object extractFromActor(final Actor actor) { if (actor instanceof TextField) { return Long.valueOf(((TextField) actor).getText()); } throw new GdxRuntimeException("Cannot use default setter of long preference with actor: " + actor); } }, STRING(String.class, Object.class) { @Override public Object convert(final String raw) { return raw; } @Override public Object getDefault() { return Strings.EMPTY_STRING; } @Override public Object extractFromActor(final Actor actor) { if (actor instanceof Label) { return ((Label) actor).getText().toString(); } else if (actor instanceof TextButton) { return ((TextButton) actor).getText(); } else if (actor instanceof TextField) { return ((TextField) actor).getText(); } else if (actor instanceof List<?>) { return Strings.toString(((List<?>) actor).getSelected(), Strings.EMPTY_STRING); } else if (actor instanceof SelectBox<?>) { return Strings.toString(((SelectBox<?>) actor).getSelected(), Strings.EMPTY_STRING); } return Strings.toString(actor); } }; private final Class<?>[] supportedClasses; private Type(final Class<?>... supportedClasses) { this.supportedClasses = supportedClasses; } /** @return default field value for the selected field type. */ public abstract Object getDefault(); /** @param field contains a preference. * @return type ready to handle field's value or null. */ public static Type getType(final Field field) { for (final Type type : values()) { if (type.matches(field)) { return type; } } return null; } /** @param field contains the preference. * @return true if this type can handle the field. */ public boolean matches(final Field field) { for (final Class<?> supportedClass : supportedClasses) { if (supportedClass.equals(field.getType())) { return true; } } return false; } /** @param raw preference value in the map. * @return converted preference value, ready to be stored in the field. */ public abstract Object convert(String raw); /** @param actor used to set up the preference. * @return value extracted from the actor. */ public abstract Object extractFromActor(final Actor actor); } }