com.github.steveash.typedconfig.ConfigFactoryContext.java Source code

Java tutorial

Introduction

Here is the source code for com.github.steveash.typedconfig.ConfigFactoryContext.java

Source

/*
 * Copyright (c) 2012 Jonathan Tyers, Steve Ash
 *
 * 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.
 */

package com.github.steveash.typedconfig;

import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.eventbus.EventBus;
import com.google.common.reflect.TypeToken;
import org.apache.commons.configuration.HierarchicalConfiguration;
import org.apache.commons.configuration.SubnodeConfiguration;
import org.apache.commons.configuration.event.ConfigurationEvent;
import org.apache.commons.configuration.event.ConfigurationListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.steveash.typedconfig.annotation.Config;
import com.github.steveash.typedconfig.annotation.ConfigProxy;
import com.github.steveash.typedconfig.caching.CacheStrategy;
import com.github.steveash.typedconfig.defaultvalue.DefaultValueStrategy;
import com.github.steveash.typedconfig.keycombine.KeyCombinationStrategy;
import com.github.steveash.typedconfig.resolver.ValueResolver;
import com.github.steveash.typedconfig.resolver.ValueResolverFactory;
import com.github.steveash.typedconfig.resolver.ValueResolverRegistry;
import com.github.steveash.typedconfig.resolver.ValueType;
import com.github.steveash.typedconfig.validation.ValidationStrategy;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

/**
 * This is the internal SPI that value resolvers et al use to resolve their configuration values
 * Thread safe
 *
 * @author Steve Ash
 */
public class ConfigFactoryContext implements ConfigurationListener {
    private static final Logger log = LoggerFactory.getLogger(ConfigFactoryContext.class);

    private final ValueResolverRegistry registry;
    private final ValidationStrategy validationStrategy;
    private final DefaultValueStrategy defaultStrategy;
    private final KeyCombinationStrategy keyStrategy;
    private final CacheStrategy cacheStrategy;
    private final ConfigAnnotationResolver annotationResolver;
    private final EventBus eventBus = new EventBus("config-proxy");

    public ConfigFactoryContext(ValueResolverRegistry registry, ValidationStrategy validationStrategy,
            DefaultValueStrategy defaultStrategy, KeyCombinationStrategy keyStrategy, CacheStrategy cacheStrategy) {
        this.registry = registry;
        this.keyStrategy = keyStrategy;
        this.validationStrategy = validationStrategy;
        this.defaultStrategy = defaultStrategy;
        this.cacheStrategy = cacheStrategy;
        this.annotationResolver = new ConfigAnnotationResolver();

        eventBus.register(this);
    }

    /**
     * Produces a resolver that knows how to deal with the given binding for the given config; does not
     * use any decorators or anything just the binding and the value type for the factory that is chosen
     * by the registry
     *
     *
     * @param config
     * @param newMethodBinding
     *@param parentBinding @return
     */
    public ValueResolver makeResolverForBinding(HierarchicalConfiguration config, ConfigBinding newMethodBinding,
            ConfigBinding parentBinding) {
        ValueResolverFactory factory = getRegistry().lookup(newMethodBinding);

        switch (factory.getValueType()) {
        case Container:
        case Simple:
            return factory.makeForThis(newMethodBinding, config, this);

        case Nested:
            Preconditions.checkNotNull(parentBinding);
            // config points to the location of the nested context
            SubnodeConfiguration subConfig = config.configurationAt(newMethodBinding.getConfigKeyToLookup(), true);
            ConfigBinding subBinding = newMethodBinding.withKey(subConfig.getSubnodeKey());
            return factory.makeForThis(subBinding, subConfig, this);

        default:
            throw new IllegalArgumentException("dont know how to handle value type: " + factory.getValueType());
        }
    }

    public ValueType getValueTypeForBinding(ConfigBinding binding) {
        return getRegistry().lookup(binding).getValueType();
    }

    public ValueResolverRegistry getRegistry() {
        return registry;
    }

    public ValidationStrategy getValidationStrategy() {
        return validationStrategy;
    }

    public DefaultValueStrategy getDefaultStrategy() {
        return defaultStrategy;
    }

    public CacheStrategy getCacheStrategy() {
        return cacheStrategy;
    }

    public EventBus getEventBus() {
        return eventBus;
    }

    public ConfigAnnotationResolver getAnnotationResolver() {
        return annotationResolver;
    }

    public KeyCombinationStrategy getKeyStrategy() {
        return keyStrategy;
    }

    /**
     * Given the class and method on the proxy this will create a new ConfigBinding whcih represents a particular
     * "binding" between a proxy call site and the configuration target (not the configuration instance, but the
     * location (key) within a configuration instance (w.r.t the same configuration context))
     * @param interfaze
     * @param method
     * @param config
     * @return
     */
    public ConfigBinding getBindingFor(Class<?> interfaze, Method method, HierarchicalConfiguration config) {

        ConfigProxy configProxy = getAnnotationResolver().getConfigProxy(interfaze);
        Config configValue = getAnnotationResolver().getConfigAnnotation(method);
        List<Option> options = Arrays.asList(configValue.options());

        String localKey = getLocalConfigKey(method, configValue);
        String baseKey = configProxy.basekey();
        String combinedLocalKey = getCombinedKey(baseKey, localKey, config);

        List<Annotation> anns = Lists.newArrayList(method.getDeclaredAnnotations());

        return new ConfigBinding(combinedLocalKey, TypeToken.of(method.getGenericReturnType()), options, anns);
    }

    private String getCombinedKey(String baseKey, String localKey, HierarchicalConfiguration config) {
        return keyStrategy.combineKey(baseKey, localKey, config);
    }

    private String getLocalConfigKey(Method method, Config config) {
        if (!config.value().equals(""))
            return config.value();

        if (PropertyUtil.isProperty(method)) {
            return Preconditions.checkNotNull(PropertyUtil.getPropertyName(method));
        }
        int argsCount = method.getParameterTypes().length;
        if (argsCount == 0) {
            return method.getName(); // default to the name. should we allow this?
        }
        throw new IllegalArgumentException(
                "Method " + method.toGenericString() + " takes > 0 parameters which " + "is not supported.");
    }

    @Override
    public void configurationChanged(ConfigurationEvent event) {
        if (event.isBeforeUpdate())
            return;

        //        System.out.println("Received event: " + event.getType() + " for property name: " + event.getPropertyName() +
        //                        " -> " + event.getPropertyValue() + ", is before? " + event.isBeforeUpdate());
        eventBus.post(event);
    }

}