/*
* Copyright 2003-2015 JetBrains s.r.o.
*
* 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 jetbrains.mps.project.dependency;
import jetbrains.mps.smodel.Language;
import jetbrains.mps.util.annotation.ToRemove;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.language.SLanguage;
import org.jetbrains.mps.openapi.module.SDependency;
import org.jetbrains.mps.openapi.module.SModule;
import org.jetbrains.mps.openapi.module.SModuleReference;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* This class helps extracting all dependencies of a given type for a given set of modules.
* E.g. we can give it a set of modules and ask, which modules are needed to compile the given set:
* new GlobalModuleDependenciesManager(startSet).getModules(Deptype.COMPILE)
* Note that if we have M modules and N dependencies, and want to know something about a set of S modules,
* this will work O(M+N) in the worst case, regardless of S
*/
public class GlobalModuleDependenciesManager {
final static Logger LOG = LogManager.getLogger(GlobalModuleDependenciesManager.class);
private final Set<SModule> myModules;
@NotNull private final ErrorHandler myHandler;
private final UsedModulesCollector myUsedModulesCollector = new UsedModulesCollector();
public GlobalModuleDependenciesManager(Collection<? extends SModule> modules, @NotNull ErrorHandler handler) {
myModules = new HashSet<>(modules);
myHandler = handler;
}
public GlobalModuleDependenciesManager(Collection<? extends SModule> modules) {
this(modules, DEFAULT_HANDLER);
}
public GlobalModuleDependenciesManager(@NotNull SModule module, ErrorHandler handler) {
this(Collections.singletonList(module), handler);
}
public GlobalModuleDependenciesManager(@NotNull SModule module) {
this(module, DEFAULT_HANDLER);
}
/**
* @return all languages used by the given modules
* @deprecated Use {@link org.jetbrains.mps.openapi.module.SModule#getUsedLanguages()} directly.
*/
@Deprecated
@ToRemove(version = 3.3)
public Collection<Language> getUsedLanguages() {
Set<Language> result = new HashSet<Language>();
for (SModule module : myModules) {
result.addAll(directlyUsedLanguages(module));
}
return result;
}
/**
* Return only modules with 'reexport' mark in the dependents subtree
* @deprecated one usage does not justify method's existence
*/
@Deprecated
@ToRemove(version = 3.4)
public Collection<SModule> getOnlyReexportModules() {
Set<SModule> result = new HashSet<>();
for (SModule module : myModules) {
collect(module, result, Deptype.VISIBLE);
}
return result;
}
/**
* Return all modules of a given dependency type in scope of given
* <p/>
* RUNTIMES:
* If we need runtimes, this only adds additional edges to our graph. M -uses> L -runtime> R is equivalent
* to M -non-reexp> R in this case
* <p/>
* REEXPORT:
* If we need dependencies with respect to reexport flag, we should first collect all neighbours of the
* given nodes in graph, and then, considering the graph with "reexport" edges only, collect all nodes
* accessible from (start set+neighbours) in this graph
* If we don't respect reexport flag, we should collect all accessible nodes from the given set in a
* dependencies graph. The "neighbours scheme" works in this case, too.
*
* @param depType determines the type of dependencies we want to get
* @return all modules in scope of given
*/
@NotNull
public Collection<SModule> getModules(Deptype depType) {
Set<SModule> neighbours = collectNeighbours(depType);
Set<SModule> result = new HashSet<>();
for (SModule neighbour : neighbours) {
collect(neighbour, result, depType);
}
return result;
}
private Set<SModule> collectNeighbours(Deptype depType) {
HashSet<SModule> result = new HashSet<>();
for (SModule module : myModules) {
result.addAll(myUsedModulesCollector.directlyUsedModules(module, myHandler, true, depType.runtimes));
}
result.addAll(myModules);
return result;
}
private void collect(SModule current, Set<SModule> result, Deptype depType) {
if (!result.contains(current)) {
result.add(current);
for (SModule m : myUsedModulesCollector.directlyUsedModules(current, myHandler, depType.reexportAll, depType.runtimes)) {
collect(m, result, depType);
}
}
}
private static Collection<Language> directlyUsedLanguages(@NotNull SModule module) {
Set<Language> result = new HashSet<>();
for (SLanguage language : module.getUsedLanguages()) {
final SModule sourceModule = language.getSourceModule();
// respect sourceModule may be null
if (sourceModule instanceof Language) {
result.add((Language) sourceModule);
}
}
return result;
}
public enum Deptype {
/**
* All modules visible from given modules
* This includes modules from dependencies, transitive, respecting reexports
* Including initial modules
*/
VISIBLE(false, false),
/**
* All modules required for compilation of given modules
* This includes visible modules and used language runtimes, respecting reexports
* Including languages with runtime stub paths
* Including initial modules
*/
COMPILE(true, false),
/**
* All modules required for execution of given modules
* This includes transitive closure of visible modules, with no respect for reexports,
* and runtimes of used languages, not respecting reexports
* Including languages with runtime stub paths
* Including initial modules
*/
EXECUTE(true, true);
public boolean runtimes;
public boolean reexportAll;
Deptype(boolean runtimes, boolean reexportAll) {
this.runtimes = runtimes;
this.reexportAll = reexportAll;
}
}
public interface ErrorHandler {
void depCannotBeResolved(@NotNull SDependency unresolvableDep);
void langSourceModuleCannotBeResolved(@NotNull SLanguage languageWithoutSource);
void runtimeDependencyCannotBeFound(@NotNull SLanguage usedLang, @NotNull SModuleReference runtimeRef);
void runtimeDependencyCannotBeFound(@NotNull SModuleReference runtimeRef);
}
public final static ErrorHandler DEFAULT_HANDLER = new PostingWarningsErrorHandler();
}