/*******************************************************************************
* Copyright (c) 2009-2015 CWI
* 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:
* * Jurgen J. Vinju - Jurgen.Vinju@cwi.nl - CWI
*******************************************************************************/
package org.rascalmpl.eclipse.nature;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.WeakHashMap;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
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.osgi.framework.Bundle;
import org.osgi.framework.wiring.BundleWire;
import org.osgi.framework.wiring.BundleWiring;
import org.rascalmpl.eclipse.Activator;
import org.rascalmpl.eclipse.util.RascalEclipseManifest;
import org.rascalmpl.interpreter.Evaluator;
import org.rascalmpl.interpreter.env.GlobalEnvironment;
import org.rascalmpl.interpreter.env.ModuleEnvironment;
import org.rascalmpl.interpreter.load.RascalSearchPath;
import org.rascalmpl.interpreter.utils.RascalManifest;
import org.rascalmpl.uri.ProjectURIResolver;
import org.rascalmpl.uri.URIUtil;
import io.usethesource.vallang.ISourceLocation;
import org.rascalmpl.values.ValueFactoryFactory;
import io.usethesource.impulse.runtime.RuntimePlugin;
public class ProjectEvaluatorFactory {
private final WeakHashMap<IProject, Evaluator> parserForProject = new WeakHashMap<IProject, Evaluator>();
private final WeakHashMap<IProject, ModuleReloader> reloaderForProject = new WeakHashMap<IProject, ModuleReloader>();
private final PrintWriter out;
private final PrintWriter err;
private ProjectEvaluatorFactory() {
try {
out = new PrintWriter(new OutputStreamWriter(RuntimePlugin.getInstance().getConsoleStream(), "UTF16"));
err = new PrintWriter(new OutputStreamWriter(RuntimePlugin.getInstance().getConsoleStream(), "UTF16"), true);
} catch (UnsupportedEncodingException e) {
Activator.getInstance().logException("internal error", e);
throw new RuntimeException("???", e);
}
}
private static class InstanceHolder {
public static final ProjectEvaluatorFactory sInstance = new ProjectEvaluatorFactory();
}
public static ProjectEvaluatorFactory getInstance() {
return InstanceHolder.sInstance;
}
public void clear() {
reloaderForProject.clear();
parserForProject.clear();
}
public void resetParser(IProject project) {
parserForProject.remove(project);
reloaderForProject.remove(project);
}
public RascalSearchPath getProjectSearchPath(IProject project) {
Evaluator eval = getOrCreateEvaluator(project);
return eval.getRascalResolver();
}
public Evaluator getEvaluator(IProject project) {
Evaluator parser = getOrCreateEvaluator(project);
assert reloaderForProject.get(project) != null;
reloaderForProject.get(project).updateModules(new NullProgressMonitor(), new WarningsToPrintWriter(parser.getStdErr()), Collections.emptySet());
return parser;
}
/**
* This method returns and shares a single evaluator for each project
*/
public Evaluator getEvaluator(IProject project, IWarningHandler warnings) {
Evaluator parser = getOrCreateEvaluator(project);
assert reloaderForProject.get(project) != null;
reloaderForProject.get(project).updateModules(new NullProgressMonitor(), warnings, Collections.emptySet());
return parser;
}
public void reloadProject(IProject project, IWarningHandler handler, Set<String> ignored) {
ModuleReloader reloader = reloaderForProject.get(project);
if (reloader != null) {
reloader.updateModules(new NullProgressMonitor(), handler, ignored);
}
}
private Evaluator getOrCreateEvaluator(IProject project) {
Evaluator parser = parserForProject.get(project);
if (parser == null) {
parser = createProjectEvaluator(project, err, out);
reloaderForProject.put(project, new ModuleReloader(project, parser, new WarningsToPrintWriter(parser.getStdErr())));
parserForProject.put(project, parser);
}
return parser;
}
/**
* This method creates a fresh evaluator every time you call it.
*/
public Evaluator createProjectEvaluator(IProject project, Writer err, Writer out) {
Activator.getInstance().checkRascalRuntimePreconditions(project);
GlobalEnvironment heap = new GlobalEnvironment();
Evaluator parser = new Evaluator(ValueFactoryFactory.getValueFactory(), new PrintWriter(err), new PrintWriter(out), new ModuleEnvironment("$root$", heap), heap);
configure(project, parser);
return parser;
}
public Evaluator getBundleEvaluator(Bundle bundle) {
GlobalEnvironment heap = new GlobalEnvironment();
Evaluator parser = new Evaluator(ValueFactoryFactory.getValueFactory(), err, out, new ModuleEnvironment("$parser$", heap), heap);
initializeBundleEvaluator(bundle, parser);
return parser;
}
/**
* This method configures an evaluator for use in an eclipse context
*/
public static void configure(Evaluator evaluator) {
// NB. the code in this method is order dependent because it constructs a rascal module path in a particular order
evaluator.addRascalSearchPath(URIUtil.rootLocation("test-modules"));
evaluator.addClassLoader(ProjectEvaluatorFactory.class.getClassLoader());
evaluator.addClassLoader(Evaluator.class.getClassLoader());
evaluator.addRascalSearchPath(URIUtil.rootLocation("std"));
configureRascalLibraryPlugins(evaluator);
}
/**
* This method configures an evaluator for use in an eclipse context.
* @param project context to run the evaluator in, may be null
* @param evaluator the evaluator to configure, may not be null
*/
public void configure(IProject project, Evaluator evaluator) {
if (project != null) {
try {
addProjectToSearchPath(project, evaluator);
IProject[] projects = project.getReferencedProjects();
for (IProject ref : projects) {
addProjectToSearchPath(ref, evaluator);
}
}
catch (URISyntaxException usex) {
Activator.getInstance().logException("could not construct search path", usex);
}
catch (CoreException e) {
Activator.getInstance().logException("could not construct search path", e);
}
}
configure(evaluator);
try {
configureClassPath(project, evaluator);
}
catch (CoreException e) {
Activator.getInstance().logException("exception while constructing classpath for evaluator", e);
}
}
private static void configure(Bundle bundle, Evaluator evaluator) {
configure(evaluator);
configure(bundle, evaluator, new HashSet<String>());
}
private static void configure(Bundle bundle, Evaluator eval, Set<String> configured) {
if (bundle != null) {
configured.add(bundle.getSymbolicName());
try {
addBundleToSearchPath(bundle, eval);
eval.addClassLoader(new BundleClassLoader(bundle));
configureClassPath(bundle, eval);
RascalEclipseManifest mf = new RascalEclipseManifest();
List<String> requiredBundles = mf.getRequiredBundles(bundle);
if (requiredBundles != null) {
for (String required : requiredBundles) {
if (!configured.contains(required)) {
configure(Platform.getBundle(required), eval);
}
}
}
List<String> libs = mf.getRequiredLibraries(bundle);
if (libs != null) {
for (String required : libs) {
URI entryURI = bundle.getEntry(required).toURI();
addJarToSearchPath(eval.getValueFactory().sourceLocation(entryURI), eval);
}
}
}
catch (URISyntaxException e) {
Activator.getInstance().logException("could not construct search path", e);
}
}
}
/**
* This method configures an evaluator for use in an eclipse context.
* @param bundle context to run the evaluator in, may be null
* @param evaluator the evaluator to configure, may not be null
*/
public void initializeBundleEvaluator(Bundle bundle, Evaluator evaluator) {
configure(bundle, evaluator);
}
public void loadInstalledRascalLibraryPlugins() {
IExtensionPoint extensionPoint = Platform.getExtensionRegistry()
.getExtensionPoint("rascal_eclipse", "rascalLibrary");
if (extensionPoint == null) {
return; // this may happen when nobody extends this point.
}
for (IExtension element : extensionPoint.getExtensions()) {
String name = element.getContributor().getName();
Bundle bundle = Platform.getBundle(name);
Evaluator bundleEval = getBundleEvaluator(bundle);
// first load the other plugins
// TODO: support true dependencies
configureRascalLibraryPlugins(bundleEval);
// then run the main of the current one
runLibraryPluginMain(bundleEval, bundle);
}
}
public static void configureRascalLibraryPlugins(Evaluator evaluator) {
IExtensionPoint extensionPoint = Platform.getExtensionRegistry()
.getExtensionPoint("rascal_eclipse", "rascalLibrary");
if (extensionPoint == null) {
return; // this may happen when nobody extends this point.
}
try {
for (IExtension element : extensionPoint.getExtensions()) {
String name = element.getContributor().getName();
Bundle bundle = Platform.getBundle(name);
configureRascalLibraryPlugin(evaluator, bundle);
}
}
catch (URISyntaxException e) {
Activator.log("could not load some library", e);
}
}
public static void configureRascalLibraryPlugin(Evaluator evaluator, Bundle bundle) throws URISyntaxException {
List<String> roots = new RascalEclipseManifest().getSourceRoots(bundle);
for (String root : roots) {
// TODO: add check to see if library is referenced in RASCAL.MF
evaluator.addRascalSearchPath(URIUtil.correctLocation("plugin", bundle.getSymbolicName(), "/" + root));
}
evaluator.addClassLoader(new BundleClassLoader(bundle));
}
public static void runLibraryPluginMain(Evaluator evaluator, Bundle bundle) {
try {
RascalEclipseManifest mf = new RascalEclipseManifest();
if (!mf.hasManifest(bundle)) {
return;
}
String mainModule = mf.getMainModule(bundle);
String mainFunction = mf.getMainFunction(bundle);
// we only run a function if the main module and function have been configured.
// this is to give the option to NOT run a main module, but provide only the
// plugin as a library to other plugins.
if (mainModule != null && mainFunction != null) {
evaluator.doImport(evaluator.getMonitor(), mainModule);
evaluator.call(mainFunction);
}
}
catch (Throwable e) {
Activator.log("Library defined by bundle " + bundle.getSymbolicName() + " has no main module or main function", e);
}
}
public static void addProjectToSearchPath(IProject project, Evaluator eval)
throws URISyntaxException {
RascalEclipseManifest mf = new RascalEclipseManifest();
for (String root : mf.getSourceRoots(project)) {
eval.addRascalSearchPath(ProjectURIResolver.constructProjectURI(project, project.getFile(root).getProjectRelativePath()));
}
List<String> requiredBundles = mf.getRequiredBundles(project);
if (requiredBundles != null) {
for (String lib : requiredBundles) {
configure(Platform.getBundle(lib), eval);
}
}
List<String> requiredLibraries = mf.getRequiredLibraries(project);
if (requiredLibraries != null) {
for (String lib : requiredLibraries) {
addJarToSearchPath(ProjectURIResolver.constructProjectURI(project, project.getFile(lib).getProjectRelativePath()), eval);
}
}
}
public static void addJarToSearchPath(ISourceLocation jar, Evaluator eval) {
try {
String scheme = "jar+" + jar.getScheme();
String path = jar.getPath().endsWith("!/") ? jar.getPath() : jar.getPath() + "!/";
ISourceLocation prefix = URIUtil.changeScheme(URIUtil.changePath(jar, path), scheme);
RascalManifest mf = new RascalManifest();
List<String> roots = mf.getManifestSourceRoots(mf.manifest(jar));
if (roots != null) {
for (String root : roots) {
eval.addRascalSearchPath(URIUtil.getChildLocation(prefix, root));
}
}
} catch (URISyntaxException e) {
Activator.log("could not add jar to search path " + jar, e);
}
}
public static void addBundleToSearchPath(Bundle bundle, Evaluator eval) throws URISyntaxException {
RascalEclipseManifest mf = new RascalEclipseManifest();
List<String> srcs = mf.getSourceRoots(bundle);
if (srcs != null) {
for (String root : srcs) {
eval.addRascalSearchPath(URIUtil.correctLocation("plugin", bundle.getSymbolicName(), "/" + root.trim()));
}
}
else {
eval.addRascalSearchPath(URIUtil.correctLocation("plugin", bundle.getSymbolicName(), "/"));
}
}
private static void collectClassPathForBundle(Bundle bundle, List<URL> classPath, List<String> compilerClassPath) {
try {
File file = FileLocator.getBundleFile(bundle);
// in the case we are loading a bundle which is actually a first Eclipse level source project,
// we now just concatenate the bin folder and hope to find our class files there.
// this is only relevant in the context of people developing Rascal, not people using it.
if (file.isDirectory()) {
File bin = new File(file, "bin");
if (bin.exists()) {
file = bin;
}
}
URL url = file.toURI().toURL();
if (classPath.contains(url)) {
return; // kill infinite loop
}
classPath.add(0, url);
compilerClassPath.add(0, file.getAbsolutePath());
BundleWiring wiring = bundle.adapt(BundleWiring.class);
for (BundleWire dep : wiring.getRequiredWires(null)) {
collectClassPathForBundle(dep.getProviderWiring().getBundle(), classPath, compilerClassPath);
}
}
catch (IOException e) {
Activator.log("error construction classpath for bundle: " + bundle.getSymbolicName(), e);
}
}
private void collectClassPathForProject(IProject project, List<URL> classPath, List<String> compilerClassPath, Evaluator parser) {
if (project == null) {
return;
}
try {
if (!project.hasNature(JavaCore.NATURE_ID)) {
for (IProject ref : project.getReferencedProjects()) {
collectClassPathForProject(ref, classPath, compilerClassPath, parser);
}
}
else {
IJavaProject jProject = JavaCore.create(project);
IPath binFolder = jProject.getOutputLocation();
String binLoc = project.getLocation() + "/" + binFolder.removeFirstSegments(1).toString();
compilerClassPath.add(binLoc);
URL binURL = new URL("file", "", binLoc + "/");
parser.addClassLoader(new URLClassLoader(new URL[] {binURL}, getClass().getClassLoader()));
classPath.add(binURL);
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();
URL url = new URL("file", "", file);
if (!classPath.contains(url)) {
classPath.add(url);
compilerClassPath.add(file);
}
}
else {
URL url = new URL("file", "", entry.getPath().toString());
if (!classPath.contains(url)) {
classPath.add(url);
compilerClassPath.add(entry.getPath().toString());
}
}
break;
case IClasspathEntry.CPE_PROJECT:
collectClassPathForProject((IProject) project.getWorkspace().getRoot().findMember(entry.getPath()), classPath, compilerClassPath, parser);
break;
}
}
}
}
catch (CoreException e) {
Activator.getInstance().logException("failed to configure classpath", e);
}
catch (MalformedURLException e) {
Activator.getInstance().logException("failed to configure classpath", e);
}
}
public void configureClassPath(IProject project, Evaluator parser) throws CoreException {
List<URL> classPath = new LinkedList<URL>();
List<String> compilerClassPath = new LinkedList<String>();
Bundle rascalBundle = Activator.getInstance().getBundle();
// order is important
if (project != null && project.isOpen() && project.hasNature(JavaCore.NATURE_ID)) {
collectClassPathForProject(project, classPath, compilerClassPath, parser);
}
collectClassPathForBundle(rascalBundle, classPath, compilerClassPath);
configureClassPath(parser, classPath, compilerClassPath);
}
public static void configureClassPath(Bundle bundle, Evaluator evaluator) {
List<URL> classPath = new LinkedList<URL>();
List<String> compilerClassPath = new LinkedList<String>();
collectClassPathForBundle(bundle, classPath, compilerClassPath);
Bundle rascalBundle = Activator.getInstance().getBundle();
if (!bundle.getSymbolicName().equals(rascalBundle.getSymbolicName())) {
collectClassPathForBundle(rascalBundle, classPath, compilerClassPath);
}
configureClassPath(evaluator, classPath, compilerClassPath);
}
private static void configureClassPath(Evaluator parser, List<URL> classPath, List<String> compilerClassPath) {
// this registers the run-time path:
URL[] urls = new URL[classPath.size()];
classPath.toArray(urls);
URLClassLoader classPathLoader = new URLClassLoader(urls, ProjectEvaluatorFactory.class.getClassLoader());
parser.addClassLoader(classPathLoader);
try {
// The Java compiler does not extract classes from nested jars, therefore we try to find a file URL for the nested fat
// jar (probably extracted in a temp folder by OSGI) and add it to the Java compiler classpath which is used for compiling
// generated code by the Rascal parser generator:
Bundle rascalBundle = Activator.getInstance().getBundle();
URL entry = FileLocator.toFileURL(rascalBundle.getEntry("lib/rascal.jar"));
// this registers the compile-time path:
String ccp = new File(entry.toURI()).getAbsolutePath();
for (String elem : compilerClassPath) {
ccp += File.pathSeparatorChar + elem;
}
parser.getConfiguration().setRascalJavaClassPathProperty(ccp);
} catch (URISyntaxException e) {
Activator.log("URL of rascal is not a valid URI???", e);
} catch (IOException e1) {
Activator.log("could not find fat rascal jar", e1);
}
}
}