package io.airlift.configuration; import com.google.common.reflect.TypeParameter; import com.google.common.reflect.TypeToken; import com.google.inject.Binder; import com.google.inject.Key; import com.google.inject.TypeLiteral; import com.google.inject.multibindings.Multibinder; import java.lang.annotation.Annotation; import java.util.Optional; import static com.google.inject.multibindings.Multibinder.newSetBinder; import static java.util.Objects.requireNonNull; public class ConfigBinder { public static ConfigBinder configBinder(Binder binder) { return new ConfigBinder(new GuiceConfigBinder(binder)); } static ConfigBinder configBinder(ConfigurationFactory configurationFactory, Optional<Object> bindingSource) { return new ConfigBinder(new CallbackConfigBinder(configurationFactory, bindingSource)); } public interface InternalConfigBinder { <T> void bind(ConfigurationBinding<T> configurationBinding); <T> void bindConfigDefaults(ConfigDefaultsHolder<T> configDefaultsHolder); void bindConfigurationBindingListener(ConfigurationBindingListener configurationBindingListener); } private static final class GuiceConfigBinder implements InternalConfigBinder { private final Binder binder; private Multibinder<ConfigurationBindingListenerHolder> listenerMultibinder; public GuiceConfigBinder(Binder binder) { this.binder = requireNonNull(binder, "binder is null").skipSources(getClass(), ConfigBinder.class); this.listenerMultibinder = newSetBinder(binder, ConfigurationBindingListenerHolder.class); } @Override public <T> void bind(ConfigurationBinding<T> configurationBinding) { Key<T> key = configurationBinding.getKey(); binder.bind(key).toProvider(new ConfigurationProvider<>(configurationBinding)); createConfigDefaultsBinder(key); } @Override public <T> void bindConfigDefaults(ConfigDefaultsHolder<T> configDefaultsHolder) { createConfigDefaultsBinder(configDefaultsHolder.getConfigKey()).addBinding().toInstance(configDefaultsHolder); } @Override public void bindConfigurationBindingListener(ConfigurationBindingListener configurationBindingListener) { listenerMultibinder.addBinding().toInstance(new ConfigurationBindingListenerHolder(configurationBindingListener)); } private <T> Multibinder<ConfigDefaultsHolder<T>> createConfigDefaultsBinder(Key<T> key) { TypeLiteral<ConfigDefaultsHolder<T>> typeLiteral = getTypeLiteral(key); if (key.getAnnotationType() == null) { return newSetBinder(binder, typeLiteral); } if (key.hasAttributes()) { return newSetBinder(binder, typeLiteral, key.getAnnotation()); } return newSetBinder(binder, typeLiteral, key.getAnnotationType()); } @SuppressWarnings("unchecked") private static <T> TypeLiteral<ConfigDefaultsHolder<T>> getTypeLiteral(Key<T> key) { TypeToken<T> typeToken = (TypeToken<T>) TypeToken.of(key.getTypeLiteral().getType()); return (TypeLiteral<ConfigDefaultsHolder<T>>) TypeLiteral.get(new TypeToken<ConfigDefaultsHolder<T>>() {} .where(new TypeParameter<T>() {}, typeToken) .getType()); } } private static final class CallbackConfigBinder implements InternalConfigBinder { private final ConfigurationFactory configurationFactory; private final Optional<Object> bindingSource; public CallbackConfigBinder(ConfigurationFactory configurationFactory, Optional<Object> bindingSource) { this.configurationFactory = requireNonNull(configurationFactory, "configurationFactory is null"); this.bindingSource = requireNonNull(bindingSource, "bindingSource is null"); } @Override public <T> void bind(ConfigurationBinding<T> configurationBinding) { configurationFactory.registerConfigurationProvider(new ConfigurationProvider<>(configurationBinding), bindingSource); } @Override public <T> void bindConfigDefaults(ConfigDefaultsHolder<T> configDefaultsHolder) { configurationFactory.registerConfigDefaults(configDefaultsHolder); } @Override public void bindConfigurationBindingListener(ConfigurationBindingListener configurationBindingListener) { configurationFactory.addConfigurationBindingListener(configurationBindingListener); } } private final InternalConfigBinder binder; private ConfigBinder(InternalConfigBinder binder) { this.binder = requireNonNull(binder, "binder is null"); } public <T> void bindConfig(Class<T> configClass) { requireNonNull(configClass, "configClass is null"); bindConfig(Key.get(configClass), configClass, null); } public <T> void bindConfig(Class<T> configClass, Annotation annotation) { requireNonNull(configClass, "configClass is null"); requireNonNull(annotation, "annotation is null"); bindConfig(Key.get(configClass, annotation), configClass, null); } public <T> void bindConfig(Class<T> configClass, Class<? extends Annotation> annotation) { requireNonNull(configClass, "configClass is null"); requireNonNull(annotation, "annotation is null"); bindConfig(Key.get(configClass, annotation), configClass, null); } public <T> void bindConfig(Class<T> configClass, String prefix) { requireNonNull(configClass, "configClass is null"); bindConfig(Key.get(configClass), configClass, prefix); } public <T> void bindConfig(Class<T> configClass, Annotation annotation, String prefix) { requireNonNull(configClass, "configClass is null"); requireNonNull(annotation, "annotation is null"); bindConfig(Key.get(configClass, annotation), configClass, prefix); } public <T> void bindConfig(Class<T> configClass, Class<? extends Annotation> annotation, String prefix) { requireNonNull(configClass, "configClass is null"); requireNonNull(annotation, "annotation is null"); bindConfig(Key.get(configClass, annotation), configClass, prefix); } public <T> void bindConfig(Key<T> key, Class<T> configClass, String prefix) { binder.bind(new ConfigurationBinding<>(key, configClass, Optional.ofNullable(prefix))); } public <T> void bindConfigDefaults(Class<T> configClass, ConfigDefaults<T> configDefaults) { requireNonNull(configClass, "configClass is null"); requireNonNull(configDefaults, "configDefaults is null"); bindConfigDefaults(Key.get(configClass), configDefaults); } public <T> void bindConfigDefaults(Class<T> configClass, Annotation annotation, ConfigDefaults<T> configDefaults) { requireNonNull(configClass, "configClass is null"); requireNonNull(annotation, "annotation is null"); requireNonNull(configDefaults, "configDefaults is null"); bindConfigDefaults(Key.get(configClass, annotation), configDefaults); } public <T> void bindConfigDefaults(Class<T> configClass, Class<? extends Annotation> annotation, ConfigDefaults<T> configDefaults) { requireNonNull(configClass, "configClass is null"); requireNonNull(annotation, "annotation is null"); requireNonNull(configDefaults, "configDefaults is null"); bindConfigDefaults(Key.get(configClass, annotation), configDefaults); } public <T> void bindConfigDefaults(Key<T> key, ConfigDefaults<T> configDefaults) { binder.bindConfigDefaults(new ConfigDefaultsHolder<>(key, configDefaults)); } /** * Binds default values for all the instances of given config class for the current binder */ public <T> void bindConfigGlobalDefaults(Class<T> configClass, ConfigDefaults<T> configDefaults) { Key<T> key = Key.get(configClass, GlobalDefaults.class); binder.bindConfigDefaults(new ConfigDefaultsHolder<>(key, configDefaults)); } /** * Binds a configuration binding listener that can create additional config bindings. */ public void bindConfigurationBindingListener(ConfigurationBindingListener configurationBindingListener) { binder.bindConfigurationBindingListener(configurationBindingListener); } }