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 7.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. /*
  2. * Copyright (c) 2006-2017 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  5. * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  6. * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7. * permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8. *
  9. * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  10. * Software.
  11. *
  12. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  13. * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
  14. * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  15. * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  16. */
  17. package com.dmdirc.config;
  18. import com.dmdirc.config.binding.ConfigBinder;
  19. import com.dmdirc.config.binding.ConfigBinding;
  20. import com.dmdirc.config.provider.AggregateConfigProvider;
  21. import com.dmdirc.config.provider.ConfigChangeListener;
  22. import com.google.common.collect.ArrayListMultimap;
  23. import com.google.common.collect.Multimap;
  24. import java.lang.reflect.AccessibleObject;
  25. import java.lang.reflect.Executable;
  26. import java.lang.reflect.Field;
  27. import java.util.ArrayList;
  28. import java.util.Arrays;
  29. import java.util.Collection;
  30. import java.util.Optional;
  31. import javax.annotation.Nonnull;
  32. import org.slf4j.Logger;
  33. import org.slf4j.LoggerFactory;
  34. import static com.dmdirc.util.LogUtils.APP_ERROR;
  35. /**
  36. * Facilitates automatically binding fields or methods annotated with a {@link ConfigBinding}
  37. * element to a configuration value.
  38. */
  39. class ConfigBinderImpl implements ConfigBinder {
  40. private static final Logger LOG = LoggerFactory.getLogger(ConfigBinder.class);
  41. /** A map of instances to created listeners. */
  42. private final Multimap<Object, ConfigChangeListener> listeners = ArrayListMultimap.create();
  43. /** The default domain to use. */
  44. private final Optional<String> defaultDomain;
  45. /** The configuration manager to use to retrieve settings. */
  46. private final AggregateConfigProvider manager;
  47. /** Retriever to use to get typed config values. */
  48. private final ConfigValueRetriever valueRetriever;
  49. ConfigBinderImpl(final AggregateConfigProvider manager) {
  50. this.manager = manager;
  51. this.valueRetriever = new ConfigValueRetriever(manager);
  52. this.defaultDomain = Optional.empty();
  53. }
  54. ConfigBinderImpl(final AggregateConfigProvider manager, @Nonnull final String domain) {
  55. this.manager = manager;
  56. this.valueRetriever = new ConfigValueRetriever(manager);
  57. this.defaultDomain = Optional.of(domain);
  58. }
  59. @Override
  60. public void bind(final Object instance, final Class<?> clazz) {
  61. final Collection<ConfigChangeListener> newListeners = new ArrayList<>();
  62. final Collection<AccessibleObject> elements = new ArrayList<>();
  63. elements.addAll(Arrays.asList(clazz.getDeclaredMethods()));
  64. elements.addAll(Arrays.asList(clazz.getDeclaredFields()));
  65. for (AccessibleObject element : elements) {
  66. final ConfigBinding binding = element.getAnnotation(ConfigBinding.class);
  67. if (binding != null) {
  68. final ConfigChangeListener listener = getListener(instance, element, binding);
  69. newListeners.add(listener);
  70. manager.addChangeListener(getDomain(binding.domain()), binding.key(), listener);
  71. for (int i = 0; i < binding.fallbacks().length - 1; i += 2) {
  72. manager.addChangeListener(getDomain(binding.fallbacks()[i]),
  73. binding.fallbacks()[i + 1], listener);
  74. }
  75. if (binding.applyInitially()) {
  76. updateBoundMember(instance, element, binding);
  77. }
  78. }
  79. }
  80. addListeners(instance, newListeners);
  81. }
  82. /**
  83. * Returns the default domain for this binder if the given annotation-specified domain is empty.
  84. */
  85. private String getDomain(final String annotationDomain) {
  86. return annotationDomain.isEmpty() ? defaultDomain.get() : annotationDomain;
  87. }
  88. /**
  89. * Creates a new listener which will call
  90. * {@link #updateBoundMember(Object, AccessibleObject, ConfigBinding)} with the given arguments.
  91. *
  92. * @param instance The instance to create a listener for
  93. * @param element The element to create a listener for
  94. * @param binding The binding annotation on the above element
  95. *
  96. * @return An appropriate config change listener
  97. */
  98. private ConfigChangeListener getListener(final Object instance,
  99. final AccessibleObject element, final ConfigBinding binding) {
  100. return (domain, key) -> updateBoundMember(instance, element, binding);
  101. }
  102. /**
  103. * Updates the specified element of the given instance with the current value of the
  104. * configuration key(s) specified by its binding.
  105. *
  106. * @param instance The instance to be updated
  107. * @param element The element to be updated
  108. * @param binding The binding which defines the configuration properties
  109. */
  110. private void updateBoundMember(final Object instance,
  111. final AccessibleObject element, final ConfigBinding binding) {
  112. if (!element.isAccessible()) {
  113. element.setAccessible(true);
  114. }
  115. final Object value = valueRetriever.getValue(
  116. getTargetClass(element),
  117. getDomain(binding.domain()),
  118. binding.key(),
  119. binding.required(),
  120. binding.fallbacks());
  121. try {
  122. binding.invocation().newInstance().invoke(element, instance, value);
  123. } catch (ReflectiveOperationException ex) {
  124. LOG.error(APP_ERROR, "Exception when updating bound setting", ex);
  125. }
  126. }
  127. /**
  128. * Gets the type required for setting the given element.
  129. *
  130. * @param element The element to determine a type for
  131. *
  132. * @return If the given element is a field, then the type of that field; if the element is a
  133. * method then the type of the first parameter; otherwise, <code>String.class</code>.
  134. */
  135. private Class<?> getTargetClass(final AccessibleObject element) {
  136. if (element instanceof Field) {
  137. return ((Field) element).getType();
  138. }
  139. if (element instanceof Executable) {
  140. return ((Executable) element).getParameterTypes()[0];
  141. }
  142. return String.class;
  143. }
  144. /**
  145. * Adds the given listeners to the given instance's collection.
  146. *
  147. * @param instance The instance to add listeners for
  148. * @param newListeners The listeners to be added
  149. */
  150. private void addListeners(final Object instance,
  151. final Iterable<ConfigChangeListener> newListeners) {
  152. synchronized (listeners) {
  153. listeners.putAll(instance, newListeners);
  154. }
  155. }
  156. @Override
  157. public void unbind(final Object instance) {
  158. synchronized (listeners) {
  159. listeners.get(instance).forEach(manager::removeListener);
  160. listeners.removeAll(instance);
  161. }
  162. }
  163. @Override
  164. public ConfigBinder withDefaultDomain(@Nonnull final String domain) {
  165. return new ConfigBinderImpl(manager, domain);
  166. }
  167. }