package org.rascalmpl.eclipse.util;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.osgi.framework.Bundle;
import org.rascalmpl.eclipse.Activator;
import org.rascalmpl.eclipse.IRascalResources;
import org.rascalmpl.library.util.PathConfig;
import org.rascalmpl.uri.ProjectURIResolver;
import org.rascalmpl.uri.URIUtil;
import io.usethesource.vallang.IListWriter;
import io.usethesource.vallang.ISourceLocation;
import io.usethesource.vallang.IValue;
import io.usethesource.vallang.IValueFactory;
/**
* ProjectConfig is a builder to produce a proper Rascal PathConfig for an Eclipse project.
* This is not yet complete.
*/
public class ProjectConfig {
public static final String BIN_FOLDER = "bin";
private final IValueFactory vf;
public ProjectConfig(IValueFactory vf) {
this.vf = vf;
}
public PathConfig getPathConfig(IProject project) {
ISourceLocation projectLoc = ProjectURIResolver.constructProjectURI(project.getFullPath());
RascalEclipseManifest manifest = new RascalEclipseManifest();
IListWriter libsWriter = vf.listWriter();
IListWriter srcsWriter = vf.listWriter();
// we special-case the rascal project for bootstrapping purposes (avoiding confusing between source and bootstrapped library)
if (!isRascalBootstrapProject(project)) {
// TODO: this needs to be configured elsewhere
libsWriter.append(URIUtil.correctLocation("stdlib", "", ""));
libsWriter.append(URIUtil.correctLocation("plugin", "rascal_eclipse", "/src/org/rascalmpl/eclipse/library"));
}
// These are jar files which make contain compiled Rascal code to link to:
for (String lib : manifest.getRequiredLibraries(project)) {
libsWriter.append(URIUtil.getChildLocation(projectLoc, lib));
}
// These are other projects referenced by the current project for which we add
// the bin folder to the lib path
try {
if (!isRascalBootstrapProject(project)) {
for (IProject ref : project.getReferencedProjects()) {
ISourceLocation child = URIUtil.getChildLocation(ProjectURIResolver.constructProjectURI(ref.getFullPath()), BIN_FOLDER);
libsWriter.append(child);
}
}
//TODO add required libraries of referenced projects as well.
}
catch (CoreException e) {
Activator.log(e.getMessage(), e);
}
for (String srcName : manifest.getSourceRoots(project)) {
ISourceLocation src = URIUtil.getChildLocation(projectLoc, srcName);
srcsWriter.append(src);
}
// TODO this is necessary while the kernel does not hold a compiled standard library, so remove later:
// We special-case the rascal project for bootstrapping purposes (avoiding confusing between source and bootstrapped library)
if (!isRascalBootstrapProject(project)) {
// srcsWriter.append(URIlUtil.correctLocation("std", "", ""));
srcsWriter.append(URIUtil.correctLocation("plugin", "rascal_eclipse", "/src/org/rascalmpl/eclipse/library"));
}
ISourceLocation bin = URIUtil.getChildLocation(projectLoc, BIN_FOLDER);
ISourceLocation boot = URIUtil.correctLocation("boot", "", "");
libsWriter.insert(bin);
// Here we find out what the compiler class path must be for compiling generated Rascal parsers
// and we construct a class path for the JavaBridge to load java builtins
List<ISourceLocation> javaCompilerPath = new ArrayList<>();
List<ISourceLocation> classloaders = new ArrayList<>();
try {
Bundle rascalBundle = Activator.getInstance().getBundle();
if (!isRascalBootstrapProject(project)) {
URL entry = FileLocator.toFileURL(rascalBundle.getEntry("lib/rascal.jar"));
javaCompilerPath.add(vf.sourceLocation(entry.toURI()));
classloaders.add(vf.sourceLocation(entry.toURI()));
}
collectPathForProject(project, javaCompilerPath, classloaders);
} catch (URISyntaxException | IOException | CoreException e) {
Activator.log("error while constructing compiler path", e);
}
return new PathConfig(
srcsWriter.done(),
libsWriter.done(),
bin,
boot,
vf.list(),
vf.list(javaCompilerPath.toArray(new IValue[0])),
vf.list(classloaders.toArray(new IValue[0])));
}
private boolean isRascalBootstrapProject(IProject project) {
return "rascal".equals(project.getName());
}
private void collectPathForProject(IProject project, List<ISourceLocation> compilerPath, List<ISourceLocation> classloaders) throws URISyntaxException, JavaModelException, CoreException {
// this even works if the project is not a Java project,
// we load bundle dependencies and local jars directly from RASCAL.MF
if (project.hasNature(IRascalResources.ID_RASCAL_NATURE)) {
RascalEclipseManifest mf = new RascalEclipseManifest();
List<String> requiredBundles = mf.getRequiredBundles(project);
if (requiredBundles != null) {
for (String lib : requiredBundles) {
ISourceLocation loc = vf.sourceLocation("plugin", Platform.getBundle(lib).getSymbolicName(), "");
if (!classloaders.contains(loc)) {
classloaders.add(loc);
}
}
}
List<String> requiredLibraries = mf.getRequiredLibraries(project);
if (requiredLibraries != null) {
for (String lib : requiredLibraries) {
ISourceLocation loc = vf.sourceLocation(project.getFile(lib).getFullPath().makeAbsolute().toFile().getAbsolutePath());
if (!classloaders.contains(loc)) {
classloaders.add(loc);
}
}
}
}
// Here we implement the meta-inf from META-INF/MANIFEST.MF, and
// the .classpath of Eclipse, adding all jar files to the compiler
// path and the classloader path, while in fact for the compilation we
// need less. TODO: only add what is necessary for compiling generated parser code.
if (project.hasNature(JavaCore.NATURE_ID)) {
IJavaProject jProject = JavaCore.create(project);
IPath binFolder = jProject.getOutputLocation();
String binLoc = project.getLocation() + "/" + binFolder.removeFirstSegments(1).toString();
classloaders.add(vf.sourceLocation("file", "", binLoc + "/"));
if (!isRascalBootstrapProject(project)) {
compilerPath.add(vf.sourceLocation("file", "", binLoc + "/"));
}
if (!jProject.isOpen()) {
return;
}
IClasspathEntry[] entries = jProject.getResolvedClasspath(true);
for (int i = 0; i < entries.length; i++) {
IClasspathEntry entry = entries[i];
switch (entry.getEntryKind()) {
case IClasspathEntry.CPE_LIBRARY:
if (entry.getPath().segment(0).equals(project.getName())) {
String file = project.getLocation() + "/" + entry.getPath().removeFirstSegments(1).toString();
ISourceLocation loc = vf.sourceLocation("file", "", file);
if (!classloaders.contains(loc)) {
classloaders.add(loc);
}
if (!compilerPath.contains(loc)) {
compilerPath.add(loc);
}
}
else {
ISourceLocation url = vf.sourceLocation("file", "", entry.getPath().toString());
if (!classloaders.contains(url)) {
classloaders.add(url);
}
if (!compilerPath.contains(url)) {
compilerPath.add(url);
}
}
break;
case IClasspathEntry.CPE_PROJECT:
collectPathForProject((IProject) project.getWorkspace().getRoot().findMember(entry.getPath()), compilerPath, classloaders);
break;
}
}
}
}
}