All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.microsoft.windowsazure.core.DefaultBuilder Maven / Gradle / Ivy

/**
 * Copyright Microsoft Corporation
 * 
 * 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.microsoft.windowsazure.core;

import com.microsoft.windowsazure.ConfigurationException;

import javax.inject.Inject;
import javax.inject.Named;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;

public class DefaultBuilder implements Builder, Builder.Registry {
    private Map, Factory> factories;
    private Map, Map, List>>> alterations;

    public DefaultBuilder() {
        factories = new HashMap, Factory>();
        alterations = new HashMap, Map, List>>>();
    }

    public static DefaultBuilder create() {
        DefaultBuilder builder = new DefaultBuilder();

        for (Builder.Exports exports : ServiceLoader
                .load(Builder.Exports.class)) {
            exports.register(builder);
        }

        return builder;
    }

    void addFactory(Class service, Factory factory) {
        factories.put(service, factory);
    }

    @Override
    public  Builder.Registry add(Class service) {
        return add(service, service);
    }

    Constructor findInjectConstructor(Class implementation) {

        Constructor withInject = null;
        Constructor withoutInject = null;
        int count = 0;

        for (Constructor ctor : implementation.getConstructors()) {
            if (ctor.getAnnotation(Inject.class) != null) {
                if (withInject != null) {
                    throw new RuntimeException(
                            "Class must not have multple @Inject annotations: "
                                    + implementation.getName());
                }
                withInject = ctor;
            } else {
                ++count;
                withoutInject = ctor;
            }
        }
        if (withInject != null) {
            return withInject;
        }
        if (count != 1) {
            throw new RuntimeException(
                    "Class without @Inject annotation must have one constructor: "
                            + implementation.getName());
        }
        return withoutInject;
    }

    @Override
    public  Builder.Registry add(Class service,
            final Class implementation) {
        final Constructor ctor = findInjectConstructor(implementation);
        final Class[] parameterTypes = ctor.getParameterTypes();
        final Annotation[][] parameterAnnotations = ctor
                .getParameterAnnotations();

        addFactory(service, new Builder.Factory() {
            @Override
            @SuppressWarnings("unchecked")
            public  T create(String profile, Class service,
                    Builder builder, Map properties) {
                Object[] initializationArguments = new Object[parameterTypes.length];
                for (int i = 0; i != parameterTypes.length; ++i) {

                    boolean located = false;

                    String named = findNamedAnnotation(parameterAnnotations[i]);
                    String fullName = dotCombine(profile, named);

                    boolean probeProperties = fullName != null
                            && fullName != "";
                    int startingIndex = 0;
                    while (!located && probeProperties) {
                        String nameProbe = fullName.substring(startingIndex);
                        if (!located && named != null
                                && properties.containsKey(nameProbe)) {
                            located = true;
                            if (parameterTypes[i] == String.class) {
                                initializationArguments[i] = properties
                                    .get(nameProbe);
                            } else {
                                initializationArguments[i] = builder.build(fullName,
                                        service, parameterTypes[i], properties);
                            }
                        } else {
                            startingIndex = fullName
                                    .indexOf('.', startingIndex) + 1;
                            if (startingIndex == 0) {
                                probeProperties = false;
                            }
                        }
                    }

                    if (!located) {
                        located = true;
                        initializationArguments[i] = builder.build(fullName,
                                service, parameterTypes[i], properties);
                    }
                }

                try {
                    return (T) ctor.newInstance(initializationArguments);
                } catch (InstantiationException e) {
                    throw new ConfigurationException(e);
                } catch (IllegalAccessException e) {
                    throw new ConfigurationException(e);
                } catch (InvocationTargetException e) {
                    throw new ConfigurationException(e);
                }
            }
        });
        return this;
    }

    protected String dotCombine(String profile, String named) {
        boolean noProfile = profile == null || profile.isEmpty();
        boolean noName = named == null || named.isEmpty();
        if (noName) {
            return profile;
        }
        if (noProfile) {
            return named;
        }

        return profile + "." + named;
    }

    protected String findNamedAnnotation(Annotation[] annotations) {
        for (Annotation annotation : annotations) {
            if (Named.class.isAssignableFrom(annotation.getClass())) {
                return ((Named) annotation).value();
            }
        }
        return null;
    }

    @Override
    public  Registry add(Factory factory) {
        for (Type genericInterface : factory.getClass().getGenericInterfaces()) {
            ParameterizedType parameterizedType = (ParameterizedType) genericInterface;
            if (parameterizedType.getRawType().equals(Builder.Factory.class)) {
                Type typeArgument = parameterizedType.getActualTypeArguments()[0];
                addFactory((Class) typeArgument, factory);
            }
        }
        return this;
    }

    @Override
    @SuppressWarnings("unchecked")
    public  T build(String profile, Class service,
            Class instanceClass, Map properties) {
        Factory factory = (Factory) factories.get(instanceClass);
        if (factory == null) {
            throw new RuntimeException("Service or property not registered: "
                    + profile + " " + service.getName() + " " + instanceClass);
        }
        T instance = factory.create(profile, service, this, properties);
        Map, List>> alterationMap = alterations
                .get(service);
        if (alterationMap != null) {
            List> alterationList = alterationMap
                    .get(instanceClass);
            if (alterationList != null) {
                for (Alteration alteration : alterationList) {
                    instance = ((Alteration) alteration).alter(profile,
                            instance, this, properties);
                }
            }
        }
        return instance;
    }

    @Override
    public  void alter(Class service, Class instance,
            Alteration alteration) {
        if (!this.alterations.containsKey(service)) {
            this.alterations.put(service,
                    new HashMap, List>>());
        }
        if (!this.alterations.get(service).containsKey(instance)) {
            this.alterations.get(service).put(instance,
                    new ArrayList>());
        }
        this.alterations.get(service).get(instance).add(alteration);
    }

}