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.4KB

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