eu.numberfour.n4js.internal.N4JSModel.java Source code

Java tutorial

Introduction

Here is the source code for eu.numberfour.n4js.internal.N4JSModel.java

Source

/**
 * Copyright (c) 2016 NumberFour AG.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   NumberFour AG - Initial API and implementation
 */
package eu.numberfour.n4js.internal;

import static com.google.common.base.Optional.absent;
import static com.google.common.base.Optional.fromNullable;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.FluentIterable.from;
import static com.google.common.collect.Iterables.isEmpty;
import static com.google.common.collect.Lists.newArrayList;
import static eu.numberfour.n4js.internal.N4JSSourceContainerType.ARCHIVE;
import static eu.numberfour.n4js.internal.N4JSSourceContainerType.PROJECT;
import static eu.numberfour.n4js.n4mf.ProjectType.TEST;
import static java.util.Collections.emptyList;

import java.io.File;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.apache.log4j.Logger;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.common.util.URI;
import org.eclipse.xtext.naming.QualifiedName;

import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.inject.Inject;
import com.google.inject.Singleton;

import eu.numberfour.n4js.external.ExternalLibraryWorkspace;
import eu.numberfour.n4js.external.TargetPlatformInstallLocationProvider;
import eu.numberfour.n4js.projectModel.IN4JSArchive;
import eu.numberfour.n4js.projectModel.IN4JSProject;
import eu.numberfour.n4js.projectModel.IN4JSSourceContainer;
import eu.numberfour.n4js.projectModel.IN4JSSourceContainerAware;
import eu.numberfour.n4js.n4mf.ExtendedRuntimeEnvironment;
import eu.numberfour.n4js.n4mf.ImplementedProjects;
import eu.numberfour.n4js.n4mf.ProjectDescription;
import eu.numberfour.n4js.n4mf.ProjectReference;
import eu.numberfour.n4js.n4mf.ProvidedRuntimeLibraryDependency;
import eu.numberfour.n4js.n4mf.SimpleProjectDependency;
import eu.numberfour.n4js.n4mf.SimpleProjectDescription;
import eu.numberfour.n4js.n4mf.SourceFragment;
import eu.numberfour.n4js.n4mf.SourceFragmentType;
import eu.numberfour.n4js.n4mf.TestedProject;

/**
 */
@SuppressWarnings({ "javadoc" })
@Singleton
public class N4JSModel {

    private static final Logger LOGGER = Logger.getLogger(N4JSModel.class);

    /**
     * Segment count indicating that a resource is directly a sub resource of a particular project. Namely if the
     * resource has the following URI: platform:/resource/P/someResource.extension
     *
     * Then the first segment (at 0 index) will be the project P, and the second segment is the someResource.extension
     * that is directly contained in the project.
     *
     */
    public static final int DIRECT_RESOURCE_IN_PROJECT_SEGMENTCOUNT = 2;

    private final InternalN4JSWorkspace workspace;

    @Inject
    protected ExternalLibraryWorkspace externalLibraryWorkspace;

    @Inject
    protected FileBasedExternalPackageManager packageManager;

    @Inject
    private TargetPlatformInstallLocationProvider installLocationProvider;

    @Inject
    public N4JSModel(InternalN4JSWorkspace workspace) {
        this.workspace = workspace;
    }

    public N4JSProject getN4JSProject(URI location) {
        checkArgument(location.isFile(), "Expecting file URI. Was: " + location);
        boolean external = false;
        if (null != installLocationProvider.getTargetPlatformInstallLocation()) {
            Path projectPath = new File(location.toFileString()).toPath();
            Path nodeModulesPath = new File(installLocationProvider.getTargetPlatformNodeModulesLocation())
                    .toPath();
            try {

                final Path projectRoot = projectPath.getRoot();
                final Path nodeModulesRoot = nodeModulesPath.getRoot();

                if (Objects.equal(projectRoot, nodeModulesRoot)) {
                    final String relativePath = nodeModulesPath.relativize(projectPath).toString();
                    external = location.lastSegment().equals(relativePath);
                }

            } catch (final IllegalArgumentException e) {
                final String message = "Error while trying to relativize paths. Project path was: " + projectPath
                        + " target platform node modules location was: " + nodeModulesPath + ".";
                LOGGER.error(message, e);
                throw new RuntimeException(message, e);
            }
        }
        return new N4JSProject(location, external, this);
    }

    public N4JSProject findProjectWith(URI nestedLocation) {
        URI location = workspace.findProjectWith(nestedLocation);
        if (location != null) {
            return getN4JSProject(location);
        }

        location = externalLibraryWorkspace.findProjectWith(nestedLocation);
        if (null != location) {
            return getN4JSProject(location);
        }

        return null;
    }

    public Optional<? extends IN4JSSourceContainer> findN4JSSourceContainer(URI nestedLocation) {
        Optional<? extends IN4JSSourceContainer> foundN4JSSourceContainer = Optional.absent();
        if (!nestedLocation.isArchive()) {
            N4JSProject project = findProjectWith(nestedLocation);
            if (project != null) {
                for (IN4JSSourceContainer n4jsSourceContainer : project.getSourceContainers()) {
                    if (matchPaths(nestedLocation, n4jsSourceContainer)) {
                        return Optional.of(n4jsSourceContainer);
                    }
                }
            }
        } else {
            String pathToArchive = nestedLocation.authority();
            URI archiveURI = URI.createURI(pathToArchive.substring(0, pathToArchive.length() - 1));
            N4JSProject project = findProjectWith(archiveURI);
            N4JSArchive archive = getN4JSArchive(project, archiveURI);
            foundN4JSSourceContainer = findN4JSSourceContainerInArchive(archiveURI, archive);
        }
        return foundN4JSSourceContainer;
    }

    public Optional<? extends IN4JSSourceContainer> findN4JSExternalSourceContainer(IN4JSProject extPackage,
            URI nestedLocation) {
        Optional<? extends IN4JSSourceContainer> foundN4JSSourceContainer = Optional.absent();
        for (IN4JSSourceContainer n4jsSourceContainer : extPackage.getSourceContainers()) {
            if (matchPaths(nestedLocation, n4jsSourceContainer)) {
                return Optional.of(n4jsSourceContainer);
            }
        }
        return foundN4JSSourceContainer;
    }

    private boolean matchPaths(URI nestedLocation, IN4JSSourceContainer container) {
        URI location = container.getLocation();
        return pathStartsWithFolder(nestedLocation, location);
    }

    private boolean pathStartsWithFolder(URI nestedLocation, URI containerLocation) {
        int maxSegments = containerLocation.segmentCount();
        if (nestedLocation.segmentCount() >= maxSegments) {
            for (int i = 0; i < maxSegments; i++) {
                if (!nestedLocation.segment(i).equals(containerLocation.segment(i))) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    protected Optional<? extends IN4JSSourceContainer> findN4JSSourceContainerInArchive(URI location,
            N4JSArchive archive) {
        int maxSegments = location.segmentCount();
        OUTER: for (IN4JSSourceContainer sourceContainer : archive.getSourceContainers()) {
            URI sourceContainerLocation = sourceContainer.getLocation();
            if (sourceContainerLocation.segmentCount() <= maxSegments) {
                for (int i = 0; i < sourceContainerLocation.segmentCount(); i++) {
                    if (!sourceContainerLocation.segment(i).equals(location.segment(i))) {
                        continue OUTER;
                    }
                }
                return Optional.of(sourceContainer);
            }
        }
        return Optional.absent();
    }

    public N4JSArchive getN4JSArchive(N4JSProject project, URI archiveLocation) {
        return new N4JSArchive(project, archiveLocation);
    }

    public ImmutableList<? extends IN4JSArchive> getLibraries(N4JSProject project) {
        URI location = project.getLocation();
        return doGetLibraries(project, location);
    }

    protected InternalN4JSWorkspace getInternalWorkspace() {
        return workspace;
    }

    protected ImmutableList<? extends IN4JSArchive> doGetLibraries(N4JSProject project, URI location) {
        ImmutableList.Builder<IN4JSArchive> result = ImmutableList.builder();
        ProjectDescription description = getProjectDescription(location);
        if (description != null) {
            description.getAllRequiredRuntimeLibraries()
                    .forEach(lib -> addArchiveFromDependency(project, location, lib, result));
            description.getAllProjectDependencies()
                    .forEach(lib -> addArchiveFromDependency(project, location, lib, result));
        }
        return result.build();
    }

    private void addArchiveFromDependency(final N4JSProject project, final URI location,
            final SimpleProjectDependency dependency, final ImmutableList.Builder<IN4JSArchive> result) {

        if (null != dependency && null != dependency.getProject()) {
            final URI dependencyLocation = workspace.getLocation(location, dependency,
                    N4JSSourceContainerType.ARCHIVE);
            if (dependencyLocation != null) {
                result.add(getN4JSArchive(project, dependencyLocation));
            }
        }
    }

    public ImmutableList<? extends IN4JSArchive> getLibraries(N4JSArchive archive) {
        URI location = archive.getLocation();
        return doGetLibraries(archive.getProject(), location);
    }

    /**
     * This delegates to {@link InternalN4JSWorkspace#getProjectDescription(URI)} to allow caching.
     */
    protected ProjectDescription getProjectDescription(URI location) {
        ProjectDescription description = workspace.getProjectDescription(location);
        if (null == description) {
            description = externalLibraryWorkspace.getProjectDescription(location);
        }
        return description;
    }

    public ImmutableList<? extends IN4JSSourceContainer> getN4JSSourceContainers(N4JSProject project) {
        ImmutableList.Builder<IN4JSSourceContainer> result = ImmutableList.builder();
        URI location = project.getLocation();
        ProjectDescription description = getProjectDescription(location);
        if (description != null) {
            List<SourceFragment> sourceFragments = newArrayList(from(description.getSourceFragment()));
            sourceFragments.sort((f1, fDIRECT_RESOURCE_IN_PROJECT_SEGMENTCOUNT) -> f1
                    .compareByFragmentType(fDIRECT_RESOURCE_IN_PROJECT_SEGMENTCOUNT));
            for (SourceFragment sourceFragment : sourceFragments) {
                List<String> paths = sourceFragment.getPaths();
                for (String path : paths) {
                    // XXX poor man's canonical path conversion. Consider headless compiler with npm projects.
                    final String relativeLocation = ".".equals(path) ? "" : path;
                    IN4JSSourceContainer sourceContainer = this.createProjectN4JSSourceContainer(project,
                            sourceFragment.getSourceFragmentType(), relativeLocation);
                    result.add(sourceContainer);
                }
            }
        }
        return result.build();
    }

    protected String getLocationPath(URI location) {
        return location.toFileString();
    }

    protected IN4JSSourceContainer createArchiveN4JSSourceContainer(N4JSArchive archive, SourceFragmentType type,
            String relativeLocation) {
        return new N4JSArchiveSourceContainer(archive, type, relativeLocation);
    }

    protected IN4JSSourceContainer createProjectN4JSSourceContainer(N4JSProject project, SourceFragmentType type,
            String relativeLocation) {
        return new N4JSProjectSourceContainer(project, type, relativeLocation);
    }

    public ImmutableList<IN4JSSourceContainer> getSourceContainers(N4JSArchive archive) {
        ImmutableList.Builder<IN4JSSourceContainer> result = ImmutableList.builder();
        URI location = archive.getLocation();
        ProjectDescription description = getProjectDescription(location);
        if (description != null) {
            List<SourceFragment> sourceFragments = newArrayList(from(description.getSourceFragment()));
            sourceFragments.sort((f1, fDIRECT_RESOURCE_IN_PROJECT_SEGMENTCOUNT) -> f1
                    .compareByFragmentType(fDIRECT_RESOURCE_IN_PROJECT_SEGMENTCOUNT));
            for (SourceFragment sourceFragment : sourceFragments) {
                List<String> paths = sourceFragment.getPaths();
                for (String path : paths) {
                    result.add(createArchiveN4JSSourceContainer(archive, sourceFragment.getSourceFragmentType(),
                            path));
                }
            }
        }
        return result.build();
    }

    public ImmutableList<? extends IN4JSProject> getDependencies(N4JSProject project) {
        return getDependencies(project, false);
    }

    public ImmutableList<? extends IN4JSProject> getDependenciesAndImplementedApis(N4JSProject project) {
        return getDependencies(project, true);
    }

    private ImmutableList<? extends IN4JSProject> getDependencies(N4JSProject project, boolean includeApis) {
        ImmutableList.Builder<IN4JSProject> result = ImmutableList.builder();
        URI location = project.getLocation();
        ProjectDescription description = getProjectDescription(location);
        if (description != null) {
            result.addAll(resolveProjectReferences(project, description.getAllRequiredRuntimeLibraries()));
            result.addAll(resolveProjectReferences(project, description.getAllProjectDependencies()));
            result.addAll(getTestedProjects(project));
            if (includeApis) {
                result.addAll(resolveProjectReferences(project, description.getImplementedProjects()));
            }
        }
        return result.build();
    }

    public Optional<IN4JSProject> getExtendedRuntimeEnvironment(N4JSProject project) {
        final URI location = project.getLocation();
        final ProjectDescription description = getProjectDescription(location);
        if (null == description) {
            return absent();
        }
        final ExtendedRuntimeEnvironment re = description.getExtendedRuntimeEnvironment();
        if (null == re) {
            return absent();
        }
        final ProjectReference ref = re.getExtendedRuntimeEnvironment();
        return resolveProjectReference(project, ref);
    }

    public ImmutableList<? extends IN4JSProject> getImplementedProjects(N4JSProject project) {
        ImmutableList.Builder<IN4JSProject> result = ImmutableList.builder();
        URI location = project.getLocation();
        ProjectDescription description = getProjectDescription(location);
        if (description != null) {
            result.addAll(resolveProjectReferences(project, description.getAllImplementedProjects()));
        }
        return result.build();
    }

    public ImmutableList<? extends IN4JSSourceContainerAware> getProvidedRuntimeLibraries(N4JSProject project) {

        ImmutableList.Builder<IN4JSSourceContainerAware> providedRuntimes = ImmutableList.builder();
        URI projectLocation = project.getLocation();
        ProjectDescription description = getProjectDescription(projectLocation);

        if (projectLocation != null) {

            for (ProvidedRuntimeLibraryDependency runtimeLibrary : description.getAllProvidedRuntimeLibraries()) {
                if (null != runtimeLibrary.getProject()) {
                    URI location = workspace.getLocation(projectLocation, runtimeLibrary, PROJECT);
                    if (null == location) {
                        location = externalLibraryWorkspace.getLocation(projectLocation, runtimeLibrary, PROJECT);
                    }

                    if (null != location) {
                        providedRuntimes.add(getN4JSProject(location));
                    } else {

                        // Assuming archive (NFAR)
                        location = workspace.getLocation(projectLocation, runtimeLibrary, ARCHIVE);
                        if (null != location) {
                            providedRuntimes.add(getN4JSArchive(project, location));
                        }
                    }
                }
            }

        }

        return providedRuntimes.build();
    }

    public Iterator<URI> iterator(IN4JSSourceContainer sourceContainer) {
        if (sourceContainer.isLibrary()) {
            return workspace.getArchiveIterator(sourceContainer.getLibrary().getLocation(),
                    sourceContainer.getRelativeLocation());
        } else {
            if (sourceContainer.getProject().isExternal() && Platform.isRunning()) {
                return externalLibraryWorkspace.getFolderIterator(sourceContainer.getLocation());
            }
            return workspace.getFolderIterator(sourceContainer.getLocation());
        }
    }

    /**
     * @see IN4JSSourceContainer#findArtifact(QualifiedName, Optional)
     */
    public URI findArtifact(IN4JSSourceContainer sourceContainer, QualifiedName name,
            Optional<String> fileExtension) {
        final String ext = fileExtension.or("").trim();
        final String extWithDot = !ext.isEmpty() && !ext.startsWith(".") ? "." + ext : ext;
        final String pathStr = name.toString("/") + extWithDot; // no need for IQualifiedNameConverter here!
        if (sourceContainer.isLibrary()) {
            return null; // TODO support for finding artifacts in libraries
        } else {
            URI artifactLocation = workspace.findArtifactInFolder(sourceContainer.getLocation(), pathStr);
            if (null == artifactLocation) {
                artifactLocation = externalLibraryWorkspace.findArtifactInFolder(sourceContainer.getLocation(),
                        pathStr);
            }
            return artifactLocation;
        }
    }

    public Optional<String> getExtendedRuntimeEnvironmentName(URI location) {
        if (null == location) {
            return absent();
        }
        final ProjectDescription description = getProjectDescription(location);
        if (null == description) {
            return absent();
        }
        final ExtendedRuntimeEnvironment extendedRe = description.getExtendedRuntimeEnvironment();
        if (null == extendedRe) {
            return absent();
        }
        final ProjectReference reRef = extendedRe.getExtendedRuntimeEnvironment();
        if (null == reRef) {
            return absent();
        }
        final SimpleProjectDescription project = reRef.getProject();
        if (null == project) {
            return absent();
        }
        return fromNullable(project.getArtifactId());
    }

    public Collection<IN4JSProject> getTestedProjects(final N4JSProject project) {
        if (null == project || !project.exists()) {
            return emptyList();
        }

        // Shortcut to avoid reading the project description at all.
        if (!TEST.equals(project.getProjectType())) {
            return emptyList();
        }

        final Builder<IN4JSProject> builder = ImmutableList.builder();
        final URI location = project.getLocation();
        final ProjectDescription description = getProjectDescription(location);

        if (null != description) {
            for (TestedProject testedProject : description.getAllTestedProjects()) {
                if (null != testedProject.getProject()) {
                    URI hostLocation = workspace.getLocation(location, testedProject, PROJECT);

                    if (null == hostLocation) {
                        hostLocation = externalLibraryWorkspace.getLocation(location, testedProject, PROJECT);
                    }

                    if (hostLocation != null) {
                        final N4JSProject tested = getN4JSProject(hostLocation);
                        if (null != tested && tested.exists()) {
                            builder.add(tested);
                        }
                    }
                }
            }
        }

        return builder.build();
    }

    /**
     * Resolves the project reference argument for based on the given N4JS project. May return with an absent referenced
     * project if any of the arguments are {@code null} or the reference cannot be resolved.
     *
     * @param project
     *            the relative project to resolve the reference for. Optional, could be {@code null}.
     * @param reference
     *            the reference to resolve. Optional, could be {@code null}.
     * @return the referenced project as an N4JS project instance. Could be missing if resolution failed but never
     *         {@code null}.
     */
    public Optional<IN4JSProject> resolveProjectReference(final IN4JSProject project,
            final ProjectReference reference) {

        if (null == project || null == reference || null == reference.getProject()) {
            return absent();
        }

        final URI location = project.getLocation();
        if (null == location) {
            return absent();
        }

        URI dependencyLocation = workspace.getLocation(location, reference, PROJECT);
        if (null != dependencyLocation) {
            return fromNullable(getN4JSProject(dependencyLocation));
        }

        dependencyLocation = externalLibraryWorkspace.getLocation(location, reference, PROJECT);
        if (null != dependencyLocation) {
            return fromNullable(getN4JSProject(dependencyLocation));
        }

        return absent();
    }

    /**
     * Resolves an iterable of project references described at
     * {@link #resolveProjectReference(IN4JSProject, ProjectReference)}, and returns with an collection of resolved N4JS
     * projects. All unresolved references will be filtered out from the returning collection.
     *
     * @param project
     *            the relative project.
     * @param references
     *            the references to resolve.
     * @return a collection of resolved project references. Could be empty but never {@code null}.
     */
    public Collection<IN4JSProject> resolveProjectReferences(final IN4JSProject project,
            final Iterable<? extends ProjectReference> references) {

        if (null == project || null == references || isEmpty(references)) {
            return emptyList();
        }
        return from(references).transform(ref -> resolveProjectReference(project, ref))
                .filter(opt -> opt.isPresent()).transform(opt -> opt.get()).toList();
    }

    /**
     *
     * @param project
     * @param implementedProjects
     * @return
     */
    private Iterable<? extends IN4JSProject> resolveProjectReferences(N4JSProject project,
            ImplementedProjects implementedProjects) {
        if (implementedProjects == null || implementedProjects.getImplementedProjects() == null) {
            return emptyList();
        }
        return resolveProjectReferences(project, implementedProjects.getImplementedProjects());
    }

}