Skip to content

Commit

Permalink
Integrated PR from original repository: Fix for AdamBien#71 and AdamB…
Browse files Browse the repository at this point in the history
…ien#78: Use injection context already on inital presenter initialization AdamBien#80
  • Loading branch information
dlemmermann committed Aug 20, 2020
1 parent a9d10f8 commit d195f7c
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 120 deletions.
13 changes: 13 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<javafx.version>11.0.2</javafx.version>
<slf4j.version>1.7.29</slf4j.version>
</properties>

<distributionManagement>
Expand Down Expand Up @@ -86,6 +87,18 @@
<version>${javafx.version}</version>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>${slf4j.version}</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
139 changes: 53 additions & 86 deletions src/main/java/com/airhacks/afterburner/injection/Injector.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,23 @@
*/

import com.airhacks.afterburner.configuration.Configurator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.function.Consumer;
import java.util.function.Function;

/**
Expand All @@ -53,24 +51,13 @@ public class Injector {

private static Function<Class<?>, Object> instanceSupplier = getDefaultInstanceSupplier();

private static Consumer<String> LOG = getDefaultLogger();
private static Logger LOGGER = LoggerFactory.getLogger(Injector.class);

private static final Configurator configurator = new Configurator();

public static <T> T instantiatePresenter(Class<T> clazz, Function<String, Object> injectionContext) {
@SuppressWarnings("unchecked")
T presenter = registerExistingAndInject((T) instanceSupplier.apply(clazz));
//after the regular, conventional initialization and injection, perform postinjection
Field[] fields = clazz.getDeclaredFields();
for (final Field field : fields) {
if (field.isAnnotationPresent(Inject.class)) {
final String fieldName = field.getName();
final Object value = injectionContext.apply(fieldName);
if (value != null) {
injectIntoField(field, presenter, value);
}
}
}
T presenter = registerExistingAndInject((T) instanceSupplier.apply(clazz), injectionContext);
return presenter;
}

Expand All @@ -82,10 +69,6 @@ public static void setInstanceSupplier(Function<Class<?>, Object> instanceSuppli
Injector.instanceSupplier = instanceSupplier;
}

public static void setLogger(Consumer<String> logger) {
LOG = logger;
}

public static void setConfigurationSource(Function<Object, Object> configurationSupplier) {
configurator.set(configurationSupplier);
}
Expand All @@ -98,25 +81,34 @@ public static void resetConfigurationSource() {
configurator.forgetAll();
}

public static <T> T registerExistingAndInject(T instance) {
return registerExistingAndInject(instance, x -> null);
}

/**
* Caches the passed presenter internally and injects all fields
*
* @param <T> the class to initialize
* @param instance An already existing (legacy) presenter interesting in
* injection
* @param injectionContext
* @return presenter with injected fields
*/
public static <T> T registerExistingAndInject(T instance) {
T product = injectAndInitialize(instance);
public static <T> T registerExistingAndInject(T instance, Function<String, Object> injectionContext) {
T product = injectAndInitialize(instance, injectionContext);
presenters.add(product);
return product;
}

@SuppressWarnings("unchecked")
public static <T> T instantiateModelOrService(Class<T> clazz) {
return instantiateModelOrService(clazz, x -> null);
}

@SuppressWarnings("unchecked")
public static <T> T instantiateModelOrService(Class<T> clazz, Function<String, Object> injectionContext) {
T product = (T) modelsAndServices.get(clazz);
if (product == null) {
product = injectAndInitialize((T) instanceSupplier.apply(clazz));
product = injectAndInitialize((T) instanceSupplier.apply(clazz), injectionContext);
modelsAndServices.putIfAbsent(clazz, product);
}
return clazz.cast(product);
Expand All @@ -127,40 +119,53 @@ public static <T> void setModelOrService(Class<T> clazz, T instance) {
}

static <T> T injectAndInitialize(T product) {
injectMembers(product);
return injectAndInitialize(product, x -> null);
}

static <T> T injectAndInitialize(T product, Function<String, Object> injectionContext) {
injectMembers(product, injectionContext);
initialize(product);
return product;
}

static void injectMembers(final Object instance) {
public static void injectMembers(final Object instance, Function<String, Object> injectionContext) {
Class<? extends Object> clazz = instance.getClass();
injectMembers(clazz, instance);
injectMembers(clazz, instance, injectionContext);
}

public static void injectMembers(Class<? extends Object> clazz, final Object instance) throws SecurityException {
LOG.accept("Injecting members for class " + clazz + " and instance " + instance);
public static void injectMembers(Class<? extends Object> clazz, final Object instance, Function<String, Object> injectionContext) throws SecurityException {
LOGGER.debug("Injecting members for class " + clazz + " and instance " + instance);
Field[] fields = clazz.getDeclaredFields();
for (final Field field : fields) {
if (field.isAnnotationPresent(Inject.class)) {
LOG.accept("Field annotated with @Inject found: " + field);
LOGGER.trace("Field annotated with @Inject found: " + field);
Class<?> type = field.getType();
String key = field.getName();
Object value = configurator.getProperty(clazz, key);
LOG.accept("Value returned by configurator is: " + value);
String fieldName = field.getName();

// First try the configurator
Object value = configurator.getProperty(clazz, fieldName);
LOGGER.trace("Value returned by configurator is: " + value);

// Next try injection context
if (value == null) {
value = injectionContext.apply(fieldName);
}

if (value == null && isNotPrimitiveOrString(type)) {
LOG.accept("Field is not a JDK class");
value = instantiateModelOrService(type);
LOGGER.trace("Field is not a JDK class");
value = instantiateModelOrService(type, injectionContext);
}

if (value != null) {
LOG.accept("Value is a primitive, injecting...");
LOGGER.trace("Value is a primitive, injecting...");
injectIntoField(field, instance, value);
}
}
}
Class<? extends Object> superclass = clazz.getSuperclass();
if (superclass != null) {
LOG.accept("Injecting members of: " + superclass);
injectMembers(superclass, instance);
LOGGER.trace("Injecting members of: " + superclass);
injectMembers(superclass, instance, injectionContext);
}
}

Expand Down Expand Up @@ -199,7 +204,7 @@ static void invokeMethodWithAnnotation(Class<?> clazz, final Object instance, fi
boolean wasAccessible = method.isAccessible();
try {
method.setAccessible(true);
return method.invoke(instance, new Object[]{});
return method.invoke(instance);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
throw new IllegalStateException("Problem invoking " + annotationClass + " : " + method, ex);
} finally {
Expand All @@ -216,63 +221,25 @@ static void invokeMethodWithAnnotation(Class<?> clazz, final Object instance, fi

public static void forgetAll() {
Collection<Object> values = modelsAndServices.values();
values.stream().forEach((object) -> {
destroy(object);
});
presenters.stream().forEach((object) -> {
destroy(object);
});
values.forEach(Injector::destroy);
presenters.forEach(Injector::destroy);
presenters.clear();
modelsAndServices.clear();
resetInstanceSupplier();
resetConfigurationSource();
}

static Function<Class<?>, Object> getDefaultInstanceSupplier() {
return new DefaultInstanceProvider();
}

public static Consumer<String> getDefaultLogger() {
return l -> {
return (c) -> {
try {
return c.newInstance();
} catch (InstantiationException | IllegalAccessException ex) {
throw new IllegalStateException("Cannot instantiate view: " + c, ex);
}
};
}

private static boolean isNotPrimitiveOrString(Class<?> type) {
return !type.isPrimitive() && !type.isAssignableFrom(String.class);
}

private static class DefaultInstanceProvider implements Function<Class<?>, Object> {

private Map<Class<?>,Object> instanceCache = new HashMap<>();

@Override
public Object apply(Class<?> cls) {

boolean singleton = cls.isAnnotationPresent(Singleton.class);
try {

if ( singleton ) {
// 1.8 API is not used intentionally so everything works in mobile environments
Object instance = instanceCache.get(cls);
if ( instance != null ) return instance;
}

// instantiate using default constructor even if it is private
Constructor<?> defaultConstructor = cls.getDeclaredConstructor();
defaultConstructor.setAccessible(true);
Object instance = defaultConstructor.newInstance();

if ( singleton ) {
instanceCache.put(cls, instance);
}
return instance;

} catch (Throwable ex) {
throw new IllegalStateException("Cannot instantiate a view: " + cls, ex);
}

}


}
}
19 changes: 10 additions & 9 deletions src/main/java/com/airhacks/afterburner/views/FXMLView.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
import javafx.scene.Parent;
import javafx.scene.layout.StackPane;
import javafx.util.Callback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URL;
Expand All @@ -45,8 +47,6 @@
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

Expand All @@ -57,7 +57,7 @@
* @author adam-bien.com
*/
public abstract class FXMLView extends StackPane {
private static final Logger LOGGER = Logger.getLogger(FXMLView.class.getName());
private static final Logger LOGGER = LoggerFactory.getLogger(FXMLView.class);

public static final String DEFAULT_ENDING = "View";
public static final String CSS_FILE_ENDING = ".css";
Expand Down Expand Up @@ -150,7 +150,7 @@ PresenterFactory discover() {
if (factories.size() == 1) {
return factories.get(0);
} else {
factories.forEach(s->LOGGER.severe(s.toString()));
factories.forEach(s -> LOGGER.error(s.toString()));
throw new IllegalStateException("More than one PresenterFactories discovered");
}

Expand Down Expand Up @@ -227,7 +227,7 @@ void addCSSIfAvailable(Parent parent) {
return;
}
String uriToCss = uri.toExternalForm();
if (!parent.getStylesheets().contains(uriToCss)){
if (!parent.getStylesheets().contains(uriToCss)) {
parent.getStylesheets().add(uriToCss);
}
}
Expand All @@ -238,6 +238,7 @@ String getStyleSheetName() {

/**
* .bss files are binary encoded css files which javafx produces.
*
* @return the conventional name of the bss file expected.
*/
String getBinaryStyleSheetName() {
Expand All @@ -258,13 +259,13 @@ String getResourceCamelOrLowerCase(boolean mandatory, String ending) {
if (found != null) {
return name;
}
LOGGER.config("File: " + name + " not found, attempting with camel case");
LOGGER.info("File: " + name + " not found, attempting with camel case");
name = getConventionalName(false, ending);
found = getClass().getResource(name);
if (mandatory && found == null) {
final String message = "Cannot load file " + name;
LOGGER.severe(message);
LOGGER.severe("Stopping initialization phase...");
LOGGER.error(message);
LOGGER.error("Stopping initialization phase...");
throw new IllegalStateException(message);
}
return name;
Expand Down Expand Up @@ -363,7 +364,7 @@ static ExecutorService getExecutorService() {
* @return nothing
*/
public Void exceptionReporter(Throwable t) {
LOGGER.log(Level.SEVERE,"Exception thrown in afterburner.fx",t);
LOGGER.error("Exception thrown in afterburner.fx", t);
return null;
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
requires javax.inject;
requires java.annotation;

requires java.logging;
requires org.slf4j;

exports com.airhacks.afterburner.configuration;
exports com.airhacks.afterburner.injection;
Expand Down
Loading

0 comments on commit d195f7c

Please sign in to comment.