blob: b720f80734fa378ac168d412b2544adb219be1a9 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014 Nicolas Rouquette, JPL, Caltech and others.
* 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:
* Nicolas Rouquette, JPL, Caltech - initial API and implementation
*******************************************************************************/
package org.eclipse.ease.lang.jvm.compiled;
import java.io.File;
import java.io.InputStream;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.AbstractMap;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.regex.Pattern;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Platform;
import org.eclipse.ease.AbstractScriptEngine;
import org.eclipse.ease.IScriptEngine;
import org.eclipse.ease.Script;
import org.eclipse.ease.tools.ResourceTools;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.pde.core.project.IBundleProjectDescription;
import org.eclipse.pde.core.project.IBundleProjectService;
import org.eclipse.pde.core.project.IRequiredBundleDescription;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceReference;
public class JVMCompiledScriptEngine extends AbstractScriptEngine implements IScriptEngine {
private final Map<String, Object> fVariables = new HashMap<String, Object>();
public JVMCompiledScriptEngine() {
super("JVMCompiled");
}
@Override
public void terminateCurrent() {
}
@Override
protected Object internalGetVariable(final String name) {
return fVariables.get(name);
}
@Override
protected Map<String, Object> internalGetVariables() {
return fVariables;
}
@Override
protected boolean internalHasVariable(final String name) {
return fVariables.containsKey(name);
}
@Override
protected void internalSetVariable(final String name, final Object content) {
fVariables.put(name, content);
}
@Override
protected Object internalRemoveVariable(final String name) {
return fVariables.remove(name);
}
@Override
public String getSaveVariableName(final String name) {
return getSaveName(name);
}
public static String getSaveName(final String identifier) {
// check if name is already valid
if (isSaveName(identifier))
return identifier;
// not valid, convert string to valid format
final StringBuilder buffer = new StringBuilder(identifier.replaceAll("[^a-zA-Z0-9_$]", "_"));
// check for valid first character
if (buffer.length() > 0) {
final char start = buffer.charAt(0);
if (((start < 65) || ((start > 90) && (start < 97)) || (start > 122)) && (start != '_'))
buffer.insert(0, '_');
} else {
// buffer is empty, create a random string of lowercase letters
buffer.append('_');
for (int index = 0; index < new Random().nextInt(20); index++)
buffer.append('a' + new Random().nextInt(26));
}
return buffer.toString();
}
public static boolean isSaveName(final String identifier) {
return Pattern.matches("[a-zA-Z_$][a-zA-Z0-9_$]*", identifier);
}
@Override
public void registerJar(final URL url) {
throw new RuntimeException("Functionality not supported by this engine");
}
@Override
protected void setupEngine() {
}
@Override
protected void teardownEngine() {
}
@Override
protected Object execute(final Script script, final Object reference, final String fileName, final boolean uiThread) throws Exception {
final Class<?> clazz = loadClass(reference);
if (clazz != null) {
final Method mainMethod = clazz.getMethod("main", String[].class);
if (mainMethod != null) {
final ClassLoader localClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(clazz.getClassLoader());
try {
// before we run main, we try to initialize the class
try {
final Method initialize = clazz.getMethod("initialize", InputStream.class, PrintStream.class, PrintStream.class);
initialize.invoke(null, getInputStream(), getOutputStream(), getErrorStream());
} catch (final NoSuchMethodException e) {
// initialize method not available, to be ignored
}
final Object result = mainMethod.invoke(null, internalGetVariable("argv"));
return result;
} finally {
Thread.currentThread().setContextClassLoader(localClassLoader);
}
}
}
throw new ClassNotFoundException();
}
/**
* Loads a class definition for a given source file.
*
* @param reference
* file name or {@link IFile} instance of the source file.
* @return class definition
* @throws ClassNotFoundException
* If the class was not found
*/
public static Class<?> loadClass(final Object reference) throws JavaModelException, MalformedURLException, ClassNotFoundException {
final Object file = ResourceTools.resolveFile(reference, null, true);
// find source project and resolve dependencies
final SimpleEntry<IFile, IBundleProjectDescription> pair = getBundleProjectDescription(file);
if (pair != null) {
final IFile sourceFile = pair.getKey();
final IBundleProjectDescription scriptBundleProject = pair.getValue();
final List<URL> urls = new ArrayList<URL>();
final IProject project = scriptBundleProject.getProject();
final IJavaProject javaProject = JavaCore.create(project);
final IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
final IClasspathEntry[] cpEntries = javaProject.getRawClasspath();
if (cpEntries != null) {
for (final IClasspathEntry cpEntry : cpEntries) {
// The script bundle project may have a ".classpath"
// dependency on another
// Eclipse "source" project (could be Java or Scala or
// anything else based on the Eclipse Java nature)
if ((cpEntry.getEntryKind() == IClasspathEntry.CPE_PROJECT) && (cpEntry.getContentKind() == IPackageFragmentRoot.K_SOURCE)) {
final IPath cpPath = cpEntry.getPath();
final IProject cpProject = root.getProject(cpPath.toString());
if (cpProject != null) {
final IJavaProject jcpProject = JavaCore.create(cpProject);
final IPath output = jcpProject.getOutputLocation();
final IResource bin = root.findMember(output);
final IPath binPath = bin.getRawLocation();
final URL url = binPath.toFile().toURI().toURL();
urls.add(url);
}
} else if ((cpEntry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) && (cpEntry.getContentKind() == IPackageFragmentRoot.K_BINARY)) {
IPath path = cpEntry.getPath();
urls.add(path.toFile().toURL());
}
}
}
final IPath output = javaProject.getOutputLocation();
final IResource bin = root.findMember(output);
final IPath binPath = bin.getRawLocation();
final URL url = binPath.toFile().toURI().toURL();
urls.add(url);
final IPath wsPath = sourceFile.getProjectRelativePath();
IPath wsSource = wsPath.removeFirstSegments(1);
if (wsSource.getFileExtension().equals("xtend")) {
wsSource = wsSource.removeFileExtension();
wsSource = wsSource.addFileExtension("java");
}
final IRequiredBundleDescription[] requiredBundles = scriptBundleProject.getRequiredBundles();
final List<Bundle> bundles = new ArrayList<Bundle>();
if (requiredBundles != null) {
for (final IRequiredBundleDescription requiredBundle : requiredBundles) {
final String id = requiredBundle.getName();
final Bundle b = Platform.getBundle(id);
if (b != null)
// The script bundle project (in the Eclipse workspace)
// has a MANIFEST dependency
// on an Eclipse bundle (in the Eclipse installation)
bundles.add(b);
else {
// The script bundle project (in the Eclipse workspace)
// has a MANIFEST dependency
// on an Eclipse plugin that is not in the Eclipse
// installation -- so it must be in the Eclipse
// workspace...
final IProject bProject = root.getProject("/" + id);
if (bProject != null) {
final IJavaProject bjProject = JavaCore.create(bProject);
final IPath bOutput = bjProject.getOutputLocation();
final IResource bBin = root.findMember(bOutput);
final IPath bBinPath = bBin.getRawLocation();
final URL bUrl = bBinPath.toFile().toURI().toURL();
urls.add(bUrl);
}
}
}
}
final URLClassLoader cl = new URLClassLoader(urls.toArray(new URL[urls.size()]), JVMCompiledScriptEngine.class.getClassLoader());
try {
final IJavaElement wsElement = javaProject.findElement(wsSource);
if (wsElement instanceof ICompilationUnit) {
final ICompilationUnit u = (ICompilationUnit) wsElement;
final String uName = u.getElementName();
final int dot = uName.indexOf('.');
String qName = uName.substring(0, dot);
IJavaElement uParent = u.getParent();
while (uParent instanceof IPackageFragment) {
final IPackageFragment uPkg = (IPackageFragment) uParent;
final String pkgName = uPkg.getElementName();
if ((pkgName != null) && (!pkgName.isEmpty()))
qName = pkgName + "." + qName;
uParent = uParent.getParent();
}
return cl.loadClass(qName);
}
} finally {
// TODO needs Java 1.7
// cl.close();
}
}
return null;
}
private static AbstractMap.SimpleEntry<IFile, IBundleProjectDescription> getBundleProjectDescription(final Object reference) {
IFile sourceFile = null;
if (reference instanceof IFile)
sourceFile = (IFile) reference;
else if (reference instanceof File) {
final URI scriptURI = ((File) reference).toURI();
final IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
final IFile[] files = workspaceRoot.findFilesForLocationURI(scriptURI);
if ((files != null) && (files.length == 1))
sourceFile = files[0];
}
if (sourceFile != null) {
final Bundle bundle = FrameworkUtil.getBundle(IBundleProjectService.class);
final BundleContext context = bundle.getBundleContext();
final ServiceReference<IBundleProjectService> ref = context.getServiceReference(IBundleProjectService.class);
final IBundleProjectService service = context.getService(ref);
try {
final IBundleProjectDescription projectDescription = service.getDescription(sourceFile.getProject());
if (projectDescription != null)
return new AbstractMap.SimpleEntry<IFile, IBundleProjectDescription>(sourceFile, projectDescription);
} catch (final IllegalArgumentException ex) {
// ignore
} catch (final CoreException e) {
// ignore
}
}
return null;
}
}