/* * Copyright 2015 MovingBlocks * * 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 org.terasology.module; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Optional; import org.terasology.module.dependencyResolution.OptionalResolutionStrategy; import org.terasology.naming.Name; import org.terasology.naming.Version; import org.terasology.naming.VersionRange; /** * Dependency Resolver determines a working set of modules for a given set of desired modules. Where multiple versions are compatible, they are resolved in favour of the * latest available. In particular, the latest version of the desired modules is prioritised, and in the order requested (so if using the latest version of the first * desired module prevents the use of latest version of the second desired module, then that is what will happen). * <p> * The algorithm used is based on Arc Consistency Algorithm #3. * </p> * * @author Immortius */ public class DependencyResolver { private final OptionalResolutionStrategy optionalStrategy; private final ModuleRegistry registry; /** * Creates a DependencyResolver using the {@link org.terasology.module.dependencyResolution.OptionalResolutionStrategy#INCLUDE_IF_REQUIRED}. * * @param registry The registry to resolve modules from */ public DependencyResolver(ModuleRegistry registry) { this(registry, OptionalResolutionStrategy.INCLUDE_IF_REQUIRED); } /** * @param registry The registry to resolve modules from * @param optionalResolutionStrategy The strategy for handling optional dependencies */ public DependencyResolver(ModuleRegistry registry, OptionalResolutionStrategy optionalResolutionStrategy) { this.registry = registry; this.optionalStrategy = optionalResolutionStrategy; } /** * @param rootModule The first root module * @param additionalModules Any further root modules * @return A set of compatible modules based on the required modules. */ public ResolutionResult resolve(Name rootModule, Name... additionalModules) { return builder().require(rootModule).requireAll(additionalModules).build(); } /** * @param moduleIds The set of module ids to build a set of compatible modules from * @return A set of compatible modules based on the required modules. */ public ResolutionResult resolve(Iterable<Name> moduleIds) { return builder().requireAll(moduleIds).build(); } /** * @return A builder to resolve a set of compatible modules based on the required modules. */ public ResolutionBuilder builder() { return new ResolutionBuilder(); } /** * Prepares and performs the process of resolving dependencies. */ public class ResolutionBuilder { private final Map<Name, Optional<VersionRange>> validVersions = new HashMap<>(); /** * Adds a module to the set of requirements. * Previously defined requirements on a module are overwritten. * @param moduleId the id of the module that must be resolved. Any version matches; * later versions are preferred, if multiple versions are available. * @return this instance */ public ResolutionBuilder require(Name moduleId) { validVersions.put(moduleId, Optional.empty()); return this; } /** * Adds a module to the set of requirements. * Previously defined requirements on a module are overwritten. * @param moduleId the id of the module that must be resolved. Only the specified version matches. * @param version the version of the module that must be matched * @return this instance */ public ResolutionBuilder requireVersion(Name moduleId, Version version) { validVersions.put(moduleId, Optional.of(new VersionRange(version, version.getNextPatchVersion()))); return this; } /** * Adds a module to the set of requirements. * Previously defined requirements on a module are overwritten. * @param moduleId the id of the module that must be resolved. Only the specified version range matches. * @param range the version range of the module that must be matched * @return this instance */ public ResolutionBuilder requireVersionRange(Name moduleId, VersionRange range) { validVersions.put(moduleId, Optional.of(range)); return this; } /** * Adds multiple modules to the set of requirements. * Previously defined requirements on a module are overwritten. * @param moduleIds an array of module IDs that must be resolved. Any version matches; * later versions are preferred, if multiple versions of a module are available. * @return this instance */ public ResolutionBuilder requireAll(Name[] moduleIds) { for (Name name : moduleIds) { validVersions.put(name, Optional.empty()); } return this; } /** * Adds multiple modules to the set of requirements. * Previously defined requirements on a module are overwritten. * @param moduleIds a group of module IDs that must be resolved. Any version matches; * later versions are preferred, if multiple versions of a module are available. * @return this instance */ public ResolutionBuilder requireAll(Iterable<Name> moduleIds) { Iterator<Name> iterator = moduleIds.iterator(); while (iterator.hasNext()) { validVersions.put(iterator.next(), Optional.empty()); } return this; } /** * Performs the actual dependency resolution. * @return the result of the process. */ public ResolutionResult build() { ResolutionAttempt attempt = new ResolutionAttempt(registry, optionalStrategy); return attempt.resolve(validVersions); } } }