From fec6f31bc2ecb877e2bb500d852eea88150dee7c Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Sun, 22 Oct 2017 10:54:05 +0800 Subject: [PATCH 1/2] Fix initialization using injection context for complex objects --- .../java/org/jabref/gui/BaseInjector.java | 236 ++++++++++++++++++ .../java/org/jabref/gui/DefaultInjector.java | 5 +- .../jabref/logic/pdf/FileAnnotationCache.java | 8 - 3 files changed, 238 insertions(+), 11 deletions(-) create mode 100644 src/main/java/org/jabref/gui/BaseInjector.java diff --git a/src/main/java/org/jabref/gui/BaseInjector.java b/src/main/java/org/jabref/gui/BaseInjector.java new file mode 100644 index 00000000000..5623cc73fe7 --- /dev/null +++ b/src/main/java/org/jabref/gui/BaseInjector.java @@ -0,0 +1,236 @@ +package org.jabref.gui; + +/* + * #%L + * afterburner.fx + * %% + * Copyright (C) 2013 Adam Bien + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import java.lang.annotation.Annotation; +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.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.function.Consumer; +import java.util.function.Function; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; + +import com.airhacks.afterburner.configuration.Configurator; + +/** + * Based on {@link com.airhacks.afterburner.injection.Injector}, but contains a few fixes + */ +public class BaseInjector { + + private static final Map, Object> modelsAndServices = new WeakHashMap<>(); + private static final Set presenters = Collections.newSetFromMap(new WeakHashMap<>()); + private static final Configurator configurator = new Configurator(); + private static Function, Object> instanceSupplier = getDefaultInstanceSupplier(); + private static Consumer LOG = getDefaultLogger(); + + public static T instantiatePresenter(Class clazz, Function injectionContext) { + @SuppressWarnings("unchecked") + T presenter = registerExistingAndInject((T) instanceSupplier.apply(clazz), injectionContext); + return presenter; + } + + public static T instantiatePresenter(Class clazz) { + return instantiatePresenter(clazz, f -> null); + } + + public static void setInstanceSupplier(Function, Object> instanceSupplier) { + BaseInjector.instanceSupplier = instanceSupplier; + } + + public static void setLogger(Consumer logger) { + LOG = logger; + } + + public static void setConfigurationSource(Function configurationSupplier) { + configurator.set(configurationSupplier); + } + + public static void resetInstanceSupplier() { + instanceSupplier = getDefaultInstanceSupplier(); + } + + public static void resetConfigurationSource() { + configurator.forgetAll(); + } + + /** + * Caches the passed presenter internally and injects all fields + * + * @param the class to initialize + * @param instance An already existing (legacy) presenter interesting in injection + * @return presenter with injected fields + */ + public static T registerExistingAndInject(T instance, Function injectionContext) { + T product = injectAndInitialize(instance, injectionContext); + presenters.add(product); + return product; + } + + @SuppressWarnings("unchecked") + public static T instantiateModelOrService(Class clazz, Function injectionContext) { + T product = (T) modelsAndServices.get(clazz); + if (product == null) { + product = injectAndInitialize((T) instanceSupplier.apply(clazz), injectionContext); + modelsAndServices.putIfAbsent(clazz, product); + } + return clazz.cast(product); + } + + public static void setModelOrService(Class clazz, T instance) { + modelsAndServices.put(clazz, instance); + } + + static T injectAndInitialize(T product, Function injectionContext) { + injectMembers(product, injectionContext); + initialize(product); + return product; + } + + static void injectMembers(final Object instance, Function injectionContext) { + Class clazz = instance.getClass(); + injectMembers(clazz, instance, injectionContext); + } + + public static void injectMembers(Class clazz, final Object instance, Function injectionContext) throws SecurityException { + LOG.accept("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); + Class type = field.getType(); + String fieldName = field.getName(); + + // First try the configurator + Object value = configurator.getProperty(clazz, fieldName); + LOG.accept("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, injectionContext); + } + + if (value != null) { + LOG.accept("Value is a primitive, injecting..."); + injectIntoField(field, instance, value); + } + } + } + Class superclass = clazz.getSuperclass(); + if (superclass != null) { + LOG.accept("Injecting members of: " + superclass); + injectMembers(superclass, instance, injectionContext); + } + } + + static void injectIntoField(final Field field, final Object instance, final Object target) { + AccessController.doPrivileged((PrivilegedAction) () -> { + boolean wasAccessible = field.isAccessible(); + try { + field.setAccessible(true); + field.set(instance, target); + return null; // return nothing... + } catch (IllegalArgumentException | IllegalAccessException ex) { + throw new IllegalStateException("Cannot set field: " + field + " with value " + target, ex); + } finally { + field.setAccessible(wasAccessible); + } + }); + } + + static void initialize(Object instance) { + Class clazz = instance.getClass(); + invokeMethodWithAnnotation(clazz, instance, PostConstruct.class + ); + } + + static void destroy(Object instance) { + Class clazz = instance.getClass(); + invokeMethodWithAnnotation(clazz, instance, PreDestroy.class + ); + } + + static void invokeMethodWithAnnotation(Class clazz, final Object instance, final Class annotationClass) throws IllegalStateException, SecurityException { + Method[] declaredMethods = clazz.getDeclaredMethods(); + for (final Method method : declaredMethods) { + if (method.isAnnotationPresent(annotationClass)) { + AccessController.doPrivileged((PrivilegedAction) () -> { + boolean wasAccessible = method.isAccessible(); + try { + method.setAccessible(true); + return method.invoke(instance); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + throw new IllegalStateException("Problem invoking " + annotationClass + " : " + method, ex); + } finally { + method.setAccessible(wasAccessible); + } + }); + } + } + Class superclass = clazz.getSuperclass(); + if (superclass != null) { + invokeMethodWithAnnotation(superclass, instance, annotationClass); + } + } + + public static void forgetAll() { + Collection values = modelsAndServices.values(); + values.forEach(BaseInjector::destroy); + presenters.forEach(BaseInjector::destroy); + presenters.clear(); + modelsAndServices.clear(); + resetInstanceSupplier(); + resetConfigurationSource(); + } + + static Function, Object> getDefaultInstanceSupplier() { + return (c) -> { + try { + return c.newInstance(); + } catch (InstantiationException | IllegalAccessException ex) { + throw new IllegalStateException("Cannot instantiate view: " + c, ex); + } + }; + } + + public static Consumer getDefaultLogger() { + return l -> { + }; + } + + private static boolean isNotPrimitiveOrString(Class type) { + return !type.isPrimitive() && !type.isAssignableFrom(String.class); + } +} diff --git a/src/main/java/org/jabref/gui/DefaultInjector.java b/src/main/java/org/jabref/gui/DefaultInjector.java index 7e430afd0d2..531768746df 100644 --- a/src/main/java/org/jabref/gui/DefaultInjector.java +++ b/src/main/java/org/jabref/gui/DefaultInjector.java @@ -9,7 +9,6 @@ import org.jabref.logic.journals.JournalAbbreviationLoader; import org.jabref.preferences.PreferencesService; -import com.airhacks.afterburner.injection.Injector; import com.airhacks.afterburner.injection.PresenterFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -53,8 +52,8 @@ public T instantiatePresenter(Class clazz, Function injec LOGGER.debug("Instantiate " + clazz.getName()); // Use our own method to construct dependencies - Injector.setInstanceSupplier(DefaultInjector::createDependency); + BaseInjector.setInstanceSupplier(DefaultInjector::createDependency); - return Injector.instantiatePresenter(clazz, injectionContext); + return BaseInjector.instantiatePresenter(clazz, injectionContext); } } diff --git a/src/main/java/org/jabref/logic/pdf/FileAnnotationCache.java b/src/main/java/org/jabref/logic/pdf/FileAnnotationCache.java index bbfdbb031ba..88d05221d8f 100644 --- a/src/main/java/org/jabref/logic/pdf/FileAnnotationCache.java +++ b/src/main/java/org/jabref/logic/pdf/FileAnnotationCache.java @@ -23,14 +23,6 @@ public class FileAnnotationCache { //the inner list holds the annotations per file, the outer collection maps this to a BibEntry. private LoadingCache>> annotationCache; - /** - * Creates an empty fil annotation cache. Required to allow the annotation cache to be injected into views without - * hitting the bug https://github.com/AdamBien/afterburner.fx/issues/71. - */ - public FileAnnotationCache() { - - } - public FileAnnotationCache(BibDatabaseContext context) { annotationCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).build(new CacheLoader>>() { @Override From 395480a542945997df076b097e02af07b9024483 Mon Sep 17 00:00:00 2001 From: Tobias Diez Date: Wed, 25 Oct 2017 21:37:08 +0800 Subject: [PATCH 2/2] Fix checkstyle --- src/main/java/org/jabref/gui/BaseInjector.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jabref/gui/BaseInjector.java b/src/main/java/org/jabref/gui/BaseInjector.java index 5623cc73fe7..b5a310c363d 100644 --- a/src/main/java/org/jabref/gui/BaseInjector.java +++ b/src/main/java/org/jabref/gui/BaseInjector.java @@ -45,9 +45,9 @@ */ public class BaseInjector { - private static final Map, Object> modelsAndServices = new WeakHashMap<>(); - private static final Set presenters = Collections.newSetFromMap(new WeakHashMap<>()); - private static final Configurator configurator = new Configurator(); + private static Map, Object> modelsAndServices = new WeakHashMap<>(); + private static Set presenters = Collections.newSetFromMap(new WeakHashMap<>()); + private static Configurator configurator = new Configurator(); private static Function, Object> instanceSupplier = getDefaultInstanceSupplier(); private static Consumer LOG = getDefaultLogger();