Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

inject ObservableList #81

Open
mafixmafix opened this issue Dec 7, 2017 · 1 comment
Open

inject ObservableList #81

mafixmafix opened this issue Dec 7, 2017 · 1 comment

Comments

@mafixmafix
Copy link

mafixmafix commented Dec 7, 2017

I'm very new to afterburner and want to inject an ObservableList and try the following code:

    public class FirstPresenter implements Initializable {
        @Override
        public void initialize(URL location, ResourceBundle resources) {
        }
        public void onButClick(){
            ObservableList<XYChart.Series<Number, Number>> speedSeries = FXCollections.observableArrayList();
            SecondView secondView = new SecondView((f) -> speedSeries);
            secondView.getView();
        }
public class SecondPresenter implements Initializable {
    @Override
    public void initialize(URL location, ResourceBundle resources) {
        System.out.println(speedSeries);
    }
    @Inject
    ObservableList<XYChart.Series<Number, Number>> speedSeries;
public class SecondView extends FXMLView {
    public SecondView(Function<String, Object> injectionContext) {
        super(injectionContext);
    }
}

Exception:
java.lang.IllegalStateException: Cannot instantiate view: interface javafx.collections.ObservableList

what can i do?
Thanks.

@aeifr
Copy link

aeifr commented May 19, 2020

You can create your own presenter factory, which (i.g. via a hashmap, containing the field name and the observable list as a value) and inject this into a constructor. Alternatively, using the configuration properties. Those are used during field injection.

A possible constructor injection would look something like this:

public class ConstructorPresenterFactory implements PresenterFactory {

    private Map<Class<?>, Constructor<?>> classConstructors = new HashMap<>();

    @Override
    public <T> T instantiatePresenter(Class<T> clazz, Function<String, Object> injectionContext) {
        T instance = getInstance(clazz, injectionContext);
        Injector.registerExistingAndInject(instance);
        return instance;
    }

    /**
     * Lookup of a single point of construction.
     * In several constructors are present in the requested class <T>,
     * a constructor is going to be searched which has an injection annotation attached.
     *
     * If no scenario leads into a single point of invocation, an exception is raised.
     *
     * @param clazz requested class
     * @param <T> class type
     *
     * @return a constructor of the requested class
     */
    private <T> Constructor<T> getPrioritizedConstructor(Class<T> clazz) {
        Constructor<T>[] constructors = (Constructor<T>[]) clazz.getConstructors();
        if(constructors.length == 0) {
            throw new IllegalStateException("Not initializable.");
        }
        if(constructors.length == 1) {
            return constructors[0];
        }
        List<Constructor<T>> injectableConstructors =
                Arrays.stream(clazz.getConstructors())
                      .filter(constructor -> constructor.isAnnotationPresent(Inject.class))
                      .map(constructor -> (Constructor<T>) constructor)
                      .collect(Collectors.toList());
        int injectableConstructorCount = injectableConstructors.size();
        if(injectableConstructorCount != 1) {
            throw new IllegalStateException("No deterministic constructor is present");
        }
        return injectableConstructors.get(0);
    }

    /**
     * Resolves the arguments to their types and defined qualifiers.
     * After that those arguments are invoked into a constructor, fitting certain criteria.
     *
     * @param clazz requested class
     * @param injectionContext context for qualifier lookup
     * @param <T> class type
     *
     * @return an instance of the <T> class type
     */
    private <T> T getInstance(Class<T> clazz, Function<String, Object> injectionContext) {
        try {
            Constructor<T> constructor = getConstructor(clazz);
            Object[] arguments = constructorParamsToArgs(constructor, injectionContext);
            return constructor.newInstance(arguments);
        } catch(RuntimeException | InstantiationException | IllegalAccessException | InvocationTargetException ex) {
            return null;
        }
    }

    /**
     *
     * Note: In case a primitive type is set onto the constructor,
     * without providing a value trough the injection context,
     * no default value is passed. Therefore an exception raises.
     *
     * @param constructor looking for required arguments for invocation
     * @param injectionContext lookup context for dynamic/named args
     * @param <T> class type
     *
     * @return resolved arguments for invocation
     */
    private <T> Object[] constructorParamsToArgs(Constructor<T> constructor, Function<String, Object> injectionContext) {
        Parameter[] parameters = constructor.getParameters();
        Object[] arguments = new Object[parameters.length];
        for (int i = 0; i < arguments.length; i++) {
            Parameter parameter = parameters[i];
            Object arg = null;
            if(parameter.isAnnotationPresent(NamedArg.class)) {
                NamedArg annotation = parameters[i].getAnnotation(NamedArg.class);
                arg = injectionContext.apply(annotation.value());
            }
            Class<?> parameterType = parameter.getType();
            if(arg == null && isPrimitiveOrString(parameterType)) {
                throw new IllegalStateException("Cannot inject a primitive type (" + parameterType + ")into a constructor");
            }
            if(arg == null) {
                arg = injectionContext.apply(parameter.getClass().getName());
            }
            if(arg == null && !isPrimitiveOrString(parameterType)) {
                //Default scenario
                arg = Injector.instantiateModelOrService(parameterType);
            }
            arguments[i] = arg;
        }
        return arguments;
    }

    /**
     * Provides a constructor for a certain class.
     *
     * @param clazz requested class constructor
     * @param <T> type of the class/constructor result
     *
     * @return constructor for a class
     */
    private <T> Constructor<T> getConstructor(Class<T> clazz) {
        return (Constructor<T>) classConstructors.computeIfAbsent(clazz, this::getPrioritizedConstructor);
    }

    /**
     * Helper method for checking if a type is a primitive of a string type.
     *
     * @param type class type of an argument
     *
     * @return result if the type is primitive or a string type
     */
    private static boolean isPrimitiveOrString(Class<?> type) {
        return type.isPrimitive() || type.isAssignableFrom(String.class);
    }

    /**
     * Helper method for checking if a type is a primitive of a string type.
     *
     * @param type class type of an argument
     *
     * @return result if the type is not primitive nor string type
     */
    private static boolean isNotPrimitiveOrString(Class<?> type) {
        return !isPrimitiveOrString(type);
    }
}

Keep in mind, you still need a view which provides the observable (hashmap => map::get) or provide trough the instance supplier an object.

Hashmap approach:

public class LightView extends FXMLView {

    public LightView(@NamedArg("color") Color color) {
        super(ParameterBuilder.builder()
                              .put("red", 255)
                              .build()::get);
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants