Search in sources :

Example 1 with RecursionGuard

use of org.eclipse.n4js.utils.RecursionGuard in project n4js by eclipse.

the class RunnerHelper method getProjectExtendedDepsAndApiImplMapping.

/**
 * Returns a mapping from all API projects among <code>dependencies</code> to their corresponding implementation
 * project for implementation ID <code>implementationId</code>.
 * <p>
 * Special cases: if there are no API projects among the dependencies, this method will return an empty map in
 * {@link ApiUsage#concreteApiImplProjectMapping} and {@link ApiUsage#implementationIdRequired}<code>==false</code>;
 * otherwise, if <code>implementationId</code> is <code>null</code>, then this method will assert that there exists
 * exactly one implementation for the API projects among the dependencies and use that (stored in
 * {@link ApiUsage#implementationId}).
 * <p>
 * Throws exception in case of error given <code>throwOnError==true</code>, never returns null.
 *
 * @param runtimeEnvironment
 *            active runtime environment.
 * @param moduleToRun
 *            what to run.
 * @param implementationId
 *            might be <code>null</code>
 * @param throwOnError
 *            if true fast fail in erroneous situations. Otherwise tries to proceed in a meaningful way. State can
 *            then be queried on the ApiUsage instance.
 *
 * @return result wrapped in an {@link ApiUsage} instance.
 */
// TODO this methods could require some cleanup after the concepts of API-Implementation mappings stabilized...
public ApiUsage getProjectExtendedDepsAndApiImplMapping(RuntimeEnvironment runtimeEnvironment, URI moduleToRun, String implementationId, boolean throwOnError) {
    final LinkedHashSet<IN4JSProject> deps = new LinkedHashSet<>();
    // 1) add project containing the moduleToRun and its direct AND indirect dependencies
    final Optional<? extends IN4JSProject> project = n4jsCore.findProject(moduleToRun);
    if (project.isPresent() == false) {
        throw new RuntimeException("can't obtain containing project for moduleToRun: " + moduleToRun);
    }
    recursiveDependencyCollector(project.get(), deps, new RecursionGuard<>());
    // TODO need to add not only REs but also RLs they provide
    // 2) add the runtime environment project, REs it extends and RLs provided
    final Optional<IN4JSProject> reProject = getCustomRuntimeEnvironmentProject(runtimeEnvironment);
    if (reProject.isPresent()) {
        IN4JSProject re = reProject.get();
        recursiveExtendedREsCollector(re, deps);
    } else {
    // IDE-1359: don't throw exception to make runners work without user-defined runtime environment
    // (will be changed later when library manager is available!)
    // throw new RuntimeException("can't obtain runtime environment project for " + moduleToRun2);
    }
    // TODO actually we would like to return the dependencies in load order:
    // - RuntimeEnvironment boot
    // - all RuntimeLibrary boots (take deps2 between RLs into account)
    // - all other deps2 (order does not matter they just needs to be on path later)
    // - project to be called
    // maybe some in order DFS or something above?
    // manually transform to list, or is this one ok?
    final ApiImplMapping apiImplMapping = ApiImplMapping.of(deps, n4jsCore.findAllProjects());
    if (apiImplMapping.hasErrors()) {
        if (throwOnError)
            throw new IllegalStateException("the workspace setup contains errors related to API / implementation projects (check manifests of related projects):\n    " + Joiner.on("\n    ").join(apiImplMapping.getErrorMessages()));
    }
    if (apiImplMapping.isEmpty()) {
        return ApiUsage.of(new ArrayList<>(deps), Collections.<IN4JSProject, IN4JSProject>emptyMap(), apiImplMapping);
    }
    // there must be exactly one implementation for the API projects in the workspace
    if (implementationId == null) {
        final List<String> allImplIds = apiImplMapping.getAllImplIds();
        if (allImplIds.size() != 1) {
            if (throwOnError) {
                throw new IllegalStateException("no implementationId specified while several are available in the workspace: " + allImplIds);
            } else {
                // back out, further processing not possible without implementations id.
                return ApiUsage.of(new ArrayList<>(deps), Collections.emptyMap(), apiImplMapping, true);
            }
        } else {
            implementationId = allImplIds.get(0);
        }
    }
    final Map<IN4JSProject, IN4JSProject> apiImplProjectMapping = new LinkedHashMap<>();
    // projectIds of projects without an implementation
    final List<String> missing = new ArrayList<>();
    for (IN4JSProject dep : deps) {
        if (dep != null) {
            final String depId = dep.getProjectId();
            if (depId != null && apiImplMapping.isApi(depId)) {
                // so: dep is an API project ...
                final IN4JSProject impl = apiImplMapping.getImpl(depId, implementationId);
                if (impl != null) {
                    // so: impl is the implementation project for dep for implementation ID 'implementationId'
                    apiImplProjectMapping.put(dep, impl);
                } else {
                    // bad: no implementation for dep for implementation ID 'implementationId'
                    missing.add(depId);
                }
            }
        }
    }
    if (!missing.isEmpty()) {
        if (throwOnError) {
            throw new IllegalStateException("no implementation for implementation ID \"" + implementationId + "\" found for the following projects: " + Joiner.on(", ").join(missing));
        }
    }
    // / #-#-#-#-#-#-#-#-#-#-#-#-#-#-#
    // IDEBUG-506 look for additional dependencies, pulled in from api-impl project not yet processed:
    HashSet<IN4JSProject> processedDepProjects = deps;
    LinkedHashSet<IN4JSProject> tobeInspectedApiImplProjects = new LinkedHashSet<>();
    apiImplProjectMapping.entrySet().forEach(p -> {
        IN4JSProject v = p.getValue();
        if (!processedDepProjects.contains(v))
            tobeInspectedApiImplProjects.add(v);
    });
    // Collect transitive mappings. Populate with original ones.
    final Map<IN4JSProject, IN4JSProject> joinedApiImplProjectMapping = new LinkedHashMap<>(apiImplProjectMapping);
    while (!tobeInspectedApiImplProjects.isEmpty()) {
        // compute dependencies if necessary
        LinkedHashSet<IN4JSProject> batchedPivotDependencies = new LinkedHashSet<>();
        RecursionGuard<URI> guard = new RecursionGuard<>();
        for (IN4JSProject pivotProject : tobeInspectedApiImplProjects) {
            recursiveDependencyCollector(pivotProject, batchedPivotDependencies, guard);
        }
        tobeInspectedApiImplProjects.clear();
        List<IN4JSProject> batchedPivotNewDepList = batchedPivotDependencies.stream().filter(p -> null != p && !processedDepProjects.contains(p)).collect(Collectors.toList());
        // new Api-mapping
        apiImplMapping.enhance(batchedPivotNewDepList, n4jsCore.findAllProjects());
        // go over new dependencies and decide:
        for (IN4JSProject pivNewDep : batchedPivotNewDepList) {
            final String depId = pivNewDep.getProjectId();
            if (apiImplMapping.isApi(depId)) {
                // API-mapping
                if (joinedApiImplProjectMapping.containsKey(pivNewDep)) {
                // already done.
                } else {
                    // put new entry
                    IN4JSProject pivImpl = apiImplMapping.getImpl(depId, implementationId);
                    if (null != pivImpl) {
                        joinedApiImplProjectMapping.put(pivNewDep, pivImpl);
                        tobeInspectedApiImplProjects.add(pivImpl);
                    } else {
                        missing.add(depId);
                    }
                }
            } else {
                // no API.
                if (!processedDepProjects.contains(pivNewDep)) {
                    // add to deps
                    processedDepProjects.add(pivNewDep);
                }
            }
        }
    }
    if (!missing.isEmpty()) {
        if (throwOnError) {
            throw new IllegalStateException("no implementation for implementation ID \"" + implementationId + "\" found for the following projects: " + Joiner.on(", ").join(missing));
        }
    }
    return ApiUsage.of(implementationId, new ArrayList<>(deps), apiImplProjectMapping, apiImplMapping, missing);
}
Also used : LinkedHashSet(java.util.LinkedHashSet) BootstrapModule(org.eclipse.n4js.n4mf.BootstrapModule) URI(org.eclipse.emf.common.util.URI) Inject(com.google.inject.Inject) ResourceNameComputer(org.eclipse.n4js.utils.ResourceNameComputer) IN4JSCore(org.eclipse.n4js.projectModel.IN4JSCore) IN4JSSourceContainerAware(org.eclipse.n4js.projectModel.IN4JSSourceContainerAware) ArrayList(java.util.ArrayList) HashSet(java.util.HashSet) LinkedHashMap(java.util.LinkedHashMap) Strings(com.google.common.base.Strings) RunnerRegistry(org.eclipse.n4js.runner.extension.RunnerRegistry) N4JSGlobals(org.eclipse.n4js.N4JSGlobals) Optional(com.google.common.base.Optional) Map(java.util.Map) RecursionGuard(org.eclipse.n4js.utils.RecursionGuard) IRunnerDescriptor(org.eclipse.n4js.runner.extension.IRunnerDescriptor) RuntimeEnvironment(org.eclipse.n4js.runner.extension.RuntimeEnvironment) ApiImplMapping(org.eclipse.n4js.compare.ApiImplMapping) Path(java.nio.file.Path) LinkedHashSet(java.util.LinkedHashSet) Collections.emptyList(java.util.Collections.emptyList) Collection(java.util.Collection) IN4JSProject(org.eclipse.n4js.projectModel.IN4JSProject) Set(java.util.Set) Collectors(java.util.stream.Collectors) File(java.io.File) ProjectType(org.eclipse.n4js.n4mf.ProjectType) List(java.util.List) Lists.newArrayList(com.google.common.collect.Lists.newArrayList) IN4JSArchive(org.eclipse.n4js.projectModel.IN4JSArchive) AbstractSubGenerator(org.eclipse.n4js.generator.AbstractSubGenerator) Collections(java.util.Collections) FindArtifactHelper(org.eclipse.n4js.utils.FindArtifactHelper) Joiner(com.google.common.base.Joiner) ApiImplMapping(org.eclipse.n4js.compare.ApiImplMapping) ArrayList(java.util.ArrayList) Lists.newArrayList(com.google.common.collect.Lists.newArrayList) RecursionGuard(org.eclipse.n4js.utils.RecursionGuard) URI(org.eclipse.emf.common.util.URI) LinkedHashMap(java.util.LinkedHashMap) IN4JSProject(org.eclipse.n4js.projectModel.IN4JSProject)

Aggregations

Joiner (com.google.common.base.Joiner)1 Optional (com.google.common.base.Optional)1 Strings (com.google.common.base.Strings)1 Lists.newArrayList (com.google.common.collect.Lists.newArrayList)1 Inject (com.google.inject.Inject)1 File (java.io.File)1 Path (java.nio.file.Path)1 ArrayList (java.util.ArrayList)1 Collection (java.util.Collection)1 Collections (java.util.Collections)1 Collections.emptyList (java.util.Collections.emptyList)1 HashSet (java.util.HashSet)1 LinkedHashMap (java.util.LinkedHashMap)1 LinkedHashSet (java.util.LinkedHashSet)1 List (java.util.List)1 Map (java.util.Map)1 Set (java.util.Set)1 Collectors (java.util.stream.Collectors)1 URI (org.eclipse.emf.common.util.URI)1 N4JSGlobals (org.eclipse.n4js.N4JSGlobals)1