com.github.rinde.rinsim.core.model.DependencyResolver.java Source code

Java tutorial

Introduction

Here is the source code for com.github.rinde.rinsim.core.model.DependencyResolver.java

Source

/*
 * Copyright (C) 2011-2016 Rinde van Lon, iMinds-DistriNet, KU Leuven
 *
 * 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.rinde.rinsim.core.model;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Verify.verify;
import static com.google.common.base.Verify.verifyNotNull;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.rinde.rinsim.util.LinkedHashBiMap;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;

class DependencyResolver extends DependencyProvider {
    private static final Logger LOGGER = LoggerFactory.getLogger(DependencyResolver.class);
    final Map<Class<?>, Dependency> providerMap;
    final Multimap<Dependency, Class<?>> dependencyMap;
    final BiMap<Class<?>, Dependency> modelTypeMap;
    final Set<Dependency> builders;
    final Set<ModelBuilder<?, ?>> defaultModels;

    DependencyResolver() {
        providerMap = new LinkedHashMap<>();
        dependencyMap = LinkedHashMultimap.create();
        modelTypeMap = LinkedHashBiMap.create();
        builders = new LinkedHashSet<>();
        defaultModels = new LinkedHashSet<>();
    }

    void addDefault(ModelBuilder<?, ?> mb) {
        checkArgument(!defaultModels.contains(mb));
        defaultModels.add(mb);
    }

    void add(ModelBuilder<?, ?> mb) {
        final ImmutableSet<Class<?>> deps = mb.getDependencies();
        final Dependency dep = new Dependency(this, mb, deps);
        modelTypeMap.put(mb.getAssociatedType(), dep);

        for (final Class<?> clz : mb.getProvidingTypes()) {
            checkArgument(!providerMap.containsKey(clz), "A provider for %s already exists: %s.", clz,
                    providerMap.get(clz));
            providerMap.put(clz, dep);
        }
        dependencyMap.putAll(dep, deps);
        verify(!builders.contains(dep));
        builders.add(dep);
    }

    Multimap<Dependency, Dependency> constructDependencyGraph() {
        final Multimap<Dependency, Dependency> dependencyGraph = LinkedHashMultimap.create();
        for (final Entry<Dependency, Class<?>> entry : dependencyMap.entries()) {
            checkArgument(providerMap.containsKey(entry.getValue()),
                    "Could not resolve dependency for implementations of %s, as requested" + " by %s.",
                    entry.getValue(), entry.getKey().modelBuilder);
            dependencyGraph.put(entry.getKey(), providerMap.get(entry.getValue()));
        }
        return dependencyGraph;
    }

    void addDefaultModels() {
        for (final ModelBuilder<?, ?> b : defaultModels) {
            final ImmutableSet<Class<?>> providingTypes = b.getProvidingTypes();
            if (providingTypes.isEmpty() || !providerMap.keySet().containsAll(providingTypes)) {
                checkArgument(Sets.intersection(providerMap.keySet(), providingTypes).isEmpty());
                add(b);
            }
        }
    }

    ImmutableSet<Model<?>> resolve() {
        addDefaultModels();
        final Multimap<Dependency, Dependency> dependencyGraph = constructDependencyGraph();

        if (LOGGER.isTraceEnabled()) {
            for (final Dependency dep : dependencyGraph.keySet()) {
                final StringBuilder sb = new StringBuilder();
                for (final Dependency d : dependencyGraph.get(dep)) {
                    sb.append(d.modelBuilder).append(" ");
                }
                LOGGER.trace("{} requires: {}.", dep.modelBuilder.toString(), sb);
            }
        }

        while (!dependencyGraph.isEmpty()) {
            final List<Dependency> toRemove = new ArrayList<>();
            for (final Dependency dep : dependencyGraph.keys()) {
                final Collection<Dependency> dependencies = dependencyGraph.get(dep);
                boolean allResolved = true;
                for (final Dependency dependency : dependencies) {
                    allResolved &= dependency.isResolved();
                }
                if (allResolved) {
                    dep.build();
                    toRemove.add(dep);
                }
            }

            for (final Dependency mb : toRemove) {
                dependencyGraph.removeAll(mb);
            }
            if (toRemove.isEmpty()) {
                throw new IllegalArgumentException("Could not resolve dependencies for " + dependencyGraph.keySet()
                        + ", most likely a circular dependency was declared.");
            }
        }

        final ImmutableSet.Builder<Model<?>> builder = ImmutableSet.builder();
        for (final Dependency cmb : builders) {
            builder.add(cmb.build());
        }
        return builder.build();
    }

    @Override
    @Nonnull
    public <T> T get(Class<T> type) {
        return providerMap.get(type).build().get(type);
    }

    static class DependencyProviderAccessDecorator extends DependencyProvider {
        final DependencyProvider delegate;
        final ImmutableSet<Class<?>> knownDependencies;
        final Set<Class<?>> requestedDependencies;
        final ModelBuilder<?, ?> modelBuilder;

        DependencyProviderAccessDecorator(DependencyProvider dp, ImmutableSet<Class<?>> allowed,
                ModelBuilder<?, ?> mb) {
            delegate = dp;
            knownDependencies = allowed;
            modelBuilder = mb;
            requestedDependencies = new HashSet<>();
        }

        boolean areAllDependenciesRequested() {
            return requestedDependencies.equals(knownDependencies);
        }

        Set<Class<?>> getUnusedDependencies() {
            return Sets.difference(knownDependencies, requestedDependencies);
        }

        @Override
        @Nonnull
        public <T> T get(Class<T> type) {
            checkArgument(!knownDependencies.isEmpty(), "%s did not declare any dependencies.", modelBuilder);
            checkArgument(knownDependencies.contains(type),
                    "%s is not a type that %s declared as a dependency, " + "known dependencies: %s.", type,
                    modelBuilder, knownDependencies);
            checkArgument(!requestedDependencies.contains(type),
                    "%s is already requested by %s, each type must be requested " + "exactly once.", type,
                    modelBuilder);
            requestedDependencies.add(type);
            return delegate.get(type);
        }
    }

    static class Dependency {
        final ModelBuilder<?, ?> modelBuilder;
        private final DependencyProviderAccessDecorator dependencyProvider;
        @Nullable
        private Model<?> value;

        Dependency(DependencyProvider dp, ModelBuilder<?, ?> mb, ImmutableSet<Class<?>> deps) {
            modelBuilder = mb;
            dependencyProvider = new DependencyProviderAccessDecorator(dp, deps, mb);
            if (deps.isEmpty()) {
                build();
            }
        }

        final Model<?> build() {
            if (value == null) {
                value = modelBuilder.build(dependencyProvider);
                checkNotNull(value, "%s returned null where a Model was expected.", modelBuilder);
                checkState(dependencyProvider.areAllDependenciesRequested(),
                        "All declared dependencies MUST be requested from the dependency "
                                + "provider, %s has unused dependencies: %s.",
                        modelBuilder, dependencyProvider.getUnusedDependencies());
            }
            return verifyNotNull(value);
        }

        public boolean isResolved() {
            return value != null;
        }

        @Override
        public String toString() {
            return "Dependency-" + modelBuilder.toString();
        }
    }

}