You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ConfigBinderImpl.java 6.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. package com.dmdirc.config;
  2. import com.dmdirc.interfaces.config.AggregateConfigProvider;
  3. import com.dmdirc.interfaces.config.ConfigChangeListener;
  4. import com.google.common.collect.ArrayListMultimap;
  5. import com.google.common.collect.Multimap;
  6. import java.lang.reflect.AccessibleObject;
  7. import java.lang.reflect.Executable;
  8. import java.lang.reflect.Field;
  9. import java.util.ArrayList;
  10. import java.util.Arrays;
  11. import java.util.Collection;
  12. import java.util.Optional;
  13. import javax.annotation.Nonnull;
  14. import org.slf4j.Logger;
  15. import org.slf4j.LoggerFactory;
  16. import static com.dmdirc.util.LogUtils.APP_ERROR;
  17. /**
  18. * Facilitates automatically binding fields or methods annotated with a {@link ConfigBinding}
  19. * element to a configuration value.
  20. */
  21. class ConfigBinderImpl implements ConfigBinder {
  22. private static final Logger LOG = LoggerFactory.getLogger(ConfigBinder.class);
  23. /** A map of instances to created listeners. */
  24. private final Multimap<Object, ConfigChangeListener> listeners = ArrayListMultimap.create();
  25. /** The default domain to use. */
  26. private final Optional<String> defaultDomain;
  27. /** The configuration manager to use to retrieve settings. */
  28. private final AggregateConfigProvider manager;
  29. /** Retriever to use to get typed config values. */
  30. private final ConfigValueRetriever valueRetriever;
  31. ConfigBinderImpl(final AggregateConfigProvider manager) {
  32. this.manager = manager;
  33. this.valueRetriever = new ConfigValueRetriever(manager);
  34. this.defaultDomain = Optional.empty();
  35. }
  36. ConfigBinderImpl(final AggregateConfigProvider manager, @Nonnull final String domain) {
  37. this.manager = manager;
  38. this.valueRetriever = new ConfigValueRetriever(manager);
  39. this.defaultDomain = Optional.of(domain);
  40. }
  41. @Override
  42. public void bind(final Object instance, final Class<?> clazz) {
  43. final Collection<ConfigChangeListener> newListeners = new ArrayList<>();
  44. final Collection<AccessibleObject> elements = new ArrayList<>();
  45. elements.addAll(Arrays.asList(clazz.getDeclaredMethods()));
  46. elements.addAll(Arrays.asList(clazz.getDeclaredFields()));
  47. for (AccessibleObject element : elements) {
  48. final ConfigBinding binding = element.getAnnotation(ConfigBinding.class);
  49. if (binding != null) {
  50. final ConfigChangeListener listener = getListener(instance, element, binding);
  51. newListeners.add(listener);
  52. manager.addChangeListener(getDomain(binding.domain()), binding.key(), listener);
  53. for (int i = 0; i < binding.fallbacks().length - 1; i += 2) {
  54. manager.addChangeListener(getDomain(binding.fallbacks()[i]),
  55. binding.fallbacks()[i + 1], listener);
  56. }
  57. if (binding.applyInitially()) {
  58. updateBoundMember(instance, element, binding);
  59. }
  60. }
  61. }
  62. addListeners(instance, newListeners);
  63. }
  64. /**
  65. * Returns the default domain for this binder if the given annotation-specified domain is empty.
  66. */
  67. private String getDomain(final String annotationDomain) {
  68. return annotationDomain.isEmpty() ? defaultDomain.get() : annotationDomain;
  69. }
  70. /**
  71. * Creates a new listener which will call
  72. * {@link #updateBoundMember(Object, AccessibleObject, ConfigBinding)} with the given arguments.
  73. *
  74. * @param instance The instance to create a listener for
  75. * @param element The element to create a listener for
  76. * @param binding The binding annotation on the above element
  77. *
  78. * @return An appropriate config change listener
  79. */
  80. private ConfigChangeListener getListener(final Object instance,
  81. final AccessibleObject element, final ConfigBinding binding) {
  82. return (domain, key) -> updateBoundMember(instance, element, binding);
  83. }
  84. /**
  85. * Updates the specified element of the given instance with the current value of the
  86. * configuration key(s) specified by its binding.
  87. *
  88. * @param instance The instance to be updated
  89. * @param element The element to be updated
  90. * @param binding The binding which defines the configuration properties
  91. */
  92. private void updateBoundMember(final Object instance,
  93. final AccessibleObject element, final ConfigBinding binding) {
  94. if (!element.isAccessible()) {
  95. element.setAccessible(true);
  96. }
  97. final Object value = valueRetriever.getValue(
  98. getTargetClass(element),
  99. getDomain(binding.domain()),
  100. binding.key(),
  101. binding.required(),
  102. binding.fallbacks());
  103. try {
  104. binding.invocation().newInstance().invoke(element, instance, value);
  105. } catch (ReflectiveOperationException ex) {
  106. LOG.error(APP_ERROR, "Exception when updating bound setting", ex);
  107. }
  108. }
  109. /**
  110. * Gets the type required for setting the given element.
  111. *
  112. * @param element The element to determine a type for
  113. *
  114. * @return If the given element is a field, then the type of that field; if the element is a
  115. * method then the type of the first parameter; otherwise, <code>String.class</code>.
  116. */
  117. private Class<?> getTargetClass(final AccessibleObject element) {
  118. if (element instanceof Field) {
  119. return ((Field) element).getType();
  120. }
  121. if (element instanceof Executable) {
  122. return ((Executable) element).getParameterTypes()[0];
  123. }
  124. return String.class;
  125. }
  126. /**
  127. * Adds the given listeners to the given instance's collection.
  128. *
  129. * @param instance The instance to add listeners for
  130. * @param newListeners The listeners to be added
  131. */
  132. private void addListeners(final Object instance,
  133. final Iterable<ConfigChangeListener> newListeners) {
  134. synchronized (listeners) {
  135. listeners.putAll(instance, newListeners);
  136. }
  137. }
  138. @Override
  139. public void unbind(final Object instance) {
  140. synchronized (listeners) {
  141. listeners.get(instance).forEach(manager::removeListener);
  142. listeners.removeAll(instance);
  143. }
  144. }
  145. @Override
  146. public ConfigBinder withDefaultDomain(@Nonnull final String domain) {
  147. return new ConfigBinderImpl(manager, domain);
  148. }
  149. }