blob: c8d67355dca5c18fe19a1a9b508d67bf7ff61a9e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013 Christian Pontesegger and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* Contributors:
* Christian Pontesegger - initial API and implementation
*******************************************************************************/
package org.eclipse.ease.service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.InvalidRegistryObjectException;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.content.IContentDescription;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.content.IContentTypeManager;
import org.eclipse.ease.AbstractScriptEngine;
import org.eclipse.ease.Activator;
import org.eclipse.ease.ICodeFactory;
import org.eclipse.ease.ICodeParser;
import org.eclipse.ease.IExecutionListener;
import org.eclipse.ease.IScriptEngine;
import org.eclipse.ease.IScriptEngineLaunchExtension;
import org.eclipse.ease.Logger;
import org.eclipse.ease.Script;
import org.eclipse.ease.ScriptResult;
import org.eclipse.ease.modules.ModuleCategoryDefinition;
import org.eclipse.ease.modules.ModuleDefinition;
import org.eclipse.ease.tools.ResourceTools;
import org.eclipse.ui.PlatformUI;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
public final class ScriptService implements IScriptService, BundleListener, IExecutionListener {
private static final String ENGINE = "engine";
private static final String ENGINE_ID = "engineID";
private static final Object EXTENSION_MODULE = "module";
private static final Object EXTENSION_CATEGORY = "category";
private static final String EXTENSION_LANGUAGE_ID = "org.eclipse.ease.language";
private static final String EXTENSION_MODULES_ID = "org.eclipse.ease.modules";
private static final String EXTENSION_SCRIPTTYPE_ID = "org.eclipse.ease.scriptType";
private static final String SCRIPTTYPE_NAME = "name";
private static final String LAUNCH_EXTENSION = "launchExtension";
private static ScriptService fInstance = null;
public static IScriptService getService() {
try {
return PlatformUI.getWorkbench().getService(IScriptService.class);
} catch (final IllegalStateException e) {
// workbench has not been created yet, might be running in headless mode
return ScriptService.getInstance();
}
}
public static synchronized ScriptService getInstance() {
if (fInstance == null)
fInstance = new ScriptService();
return fInstance;
}
private Collection<ModuleDefinition> fAvailableModules = null;
private Map<String, EngineDescription> fEngineDescriptions = null;
private Map<String, ScriptType> fScriptTypes = null;
private Map<String, ModuleCategoryDefinition> fAvailableModuleCategories = null;
private final Collection<IScriptEngine> fRunningEngines = new ArrayList<>();
private final ListenerList<IScriptEngineLaunchExtension> fEngineListeners = new ListenerList<>();
private ScriptService() {
Activator.getDefault().getContext().addBundleListener(this);
}
@Override
public void addEngineListener(IScriptEngineLaunchExtension listener) {
if (listener != null)
fEngineListeners.add(listener);
}
@Override
public void removeEngineListener(IScriptEngineLaunchExtension listener) {
fEngineListeners.remove(listener);
}
@Override
public EngineDescription getEngineByID(final String engineID) {
return getEngineDescriptions().get(engineID);
}
@Override
public synchronized Collection<ModuleDefinition> getAvailableModules() {
if (fAvailableModules == null) {
fAvailableModules = new HashSet<>();
final IConfigurationElement[] config = Platform.getExtensionRegistry().getConfigurationElementsFor(EXTENSION_MODULES_ID);
for (final IConfigurationElement e : config) {
if (e.getName().equals(EXTENSION_MODULE)) {
// module extension detected
final ModuleDefinition definition = new ModuleDefinition(e);
if (definition.getModuleClass() != null)
fAvailableModules.add(definition);
else
Logger.warning(Activator.PLUGIN_ID,
"Module <" + definition.getName() + "> in plugin <" + definition.getBundleID() + "> could not be located!");
}
}
}
return fAvailableModules;
}
@Override
public Collection<EngineDescription> getEngines() {
return getEngineDescriptions().values();
}
@Override
public List<EngineDescription> getEngines(final String scriptType) {
final List<EngineDescription> result = new ArrayList<>();
for (final EngineDescription description : getEngines()) {
if (description.supports(scriptType))
result.add(description);
}
// sort by priority
Collections.sort(result, (o1, o2) -> o2.getPriority() - o1.getPriority());
return result;
}
private synchronized Map<String, EngineDescription> getEngineDescriptions() {
if (fEngineDescriptions == null) {
fEngineDescriptions = new HashMap<>();
final IConfigurationElement[] config = Platform.getExtensionRegistry().getConfigurationElementsFor(EXTENSION_LANGUAGE_ID);
for (final IConfigurationElement e : config) {
if (ENGINE.equals(e.getName())) {
final EngineDescription engine = new EngineDescription(e);
fEngineDescriptions.put(engine.getID(), engine);
}
}
}
return fEngineDescriptions;
}
@Override
public Collection<IScriptEngineLaunchExtension> getLaunchExtensions(final EngineDescription engineDescription) {
final String targetEngineID = engineDescription.getID();
final Collection<IScriptEngineLaunchExtension> extensions = new HashSet<>();
final IConfigurationElement[] config = Platform.getExtensionRegistry().getConfigurationElementsFor(EXTENSION_LANGUAGE_ID);
for (final IConfigurationElement e : config) {
try {
if (LAUNCH_EXTENSION.equals(e.getName())) {
// Parse engine ID to backwards compatible regular expression
String engineID = e.getAttribute(ENGINE_ID);
if ((engineID == null) || engineID.matches("^\\*?$")) {
engineID = ".*";
}
if (!targetEngineID.matches(engineID)) {
continue;
}
// Check if script type given
final String scriptType = e.getAttribute("scriptType");
if ((scriptType != null) && !scriptType.isEmpty()) {
if (!engineDescription.supports(scriptType)) {
continue;
}
}
// Create IScriptEngineLaunchExtension based on given string
final Object extension = e.createExecutableExtension("class");
if (extension instanceof IScriptEngineLaunchExtension) {
extensions.add((IScriptEngineLaunchExtension) extension);
}
}
} catch (final InvalidRegistryObjectException e1) {
} catch (final CoreException e1) {
}
}
return extensions;
}
@Override
public synchronized Map<String, ScriptType> getAvailableScriptTypes() {
if (fScriptTypes == null) {
fScriptTypes = new HashMap<>();
final IConfigurationElement[] config = Platform.getExtensionRegistry().getConfigurationElementsFor(EXTENSION_SCRIPTTYPE_ID);
for (final IConfigurationElement e : config) {
if ("scriptType".equals(e.getName()))
fScriptTypes.put(e.getAttribute(SCRIPTTYPE_NAME), new ScriptType(e));
}
}
return fScriptTypes;
}
@Override
public ScriptType getScriptType(final String location) {
if (location == null) {
return null;
}
final Object resource = ResourceTools.resolve(location);
try {
if (resource instanceof IFile) {
// try to resolve by content type
final IContentDescription description = ((IFile) resource).getContentDescription();
if (description != null) {
final IContentType contentType = description.getContentType();
for (final ScriptType scriptType : getAvailableScriptTypes().values()) {
if (scriptType.getContentTypes().contains(contentType.getId()))
return scriptType;
}
}
}
} catch (final CoreException e) {
// could not retrieve content type, continue using file extension
}
// try to resolve by extension
if (location != null) {
final int pos = location.lastIndexOf('.');
if (pos != -1) {
String extension = location.substring(pos + 1);
if (extension.contains("?"))
extension = extension.substring(0, extension.indexOf('?'));
for (final ScriptType scriptType : getAvailableScriptTypes().values()) {
if (scriptType.getDefaultExtension().equalsIgnoreCase(extension))
return scriptType;
}
// not found, verify content types
final IContentTypeManager contentTypeManager = Platform.getContentTypeManager();
final IContentType[] contentTypes = contentTypeManager.findContentTypesFor("foo." + extension);
if (contentTypes != null) {
for (final ScriptType scriptType : getAvailableScriptTypes().values()) {
// now lets see if one of the content types matches
for (final String contentTypeIdentifier : scriptType.getContentTypes()) {
for (final IContentType candidate : contentTypes) {
if (candidate.getId().equals(contentTypeIdentifier))
return scriptType;
}
}
}
}
}
}
return null;
}
@Override
public EngineDescription getEngine(final String scriptType) {
final List<EngineDescription> engines = getEngines(scriptType);
if (!engines.isEmpty())
return engines.get(0);
return null;
}
@Override
public synchronized Map<String, ModuleCategoryDefinition> getAvailableModuleCategories() {
if (fAvailableModuleCategories == null) {
fAvailableModuleCategories = new HashMap<>();
final IConfigurationElement[] config = Platform.getExtensionRegistry().getConfigurationElementsFor(EXTENSION_MODULES_ID);
for (final IConfigurationElement e : config) {
if (e.getName().equals(EXTENSION_CATEGORY)) {
// module category detected
final ModuleCategoryDefinition definition = new ModuleCategoryDefinition(e);
fAvailableModuleCategories.put(definition.getId(), definition);
}
}
}
return fAvailableModuleCategories;
}
@Override
public ModuleDefinition getModuleDefinition(final String moduleId) {
for (final ModuleDefinition definition : getAvailableModules()) {
if (definition.getId().equals(moduleId))
return definition;
}
return null;
}
/**
* Get the default {@link ICodeFactory} for a given script engine.
*
* @param engine
* script engine to look up
* @return code factory or <code>null</code>
*/
public static ICodeFactory getCodeFactory(final IScriptEngine engine) {
final EngineDescription description = engine.getDescription();
if (description != null) {
final List<ScriptType> scriptTypes = description.getSupportedScriptTypes();
if (!scriptTypes.isEmpty())
return scriptTypes.get(0).getCodeFactory();
}
return null;
}
/**
* Get the default {@link ICodeParser} for a given script engine.
*
* @param engine
* script engine to look up
* @return code factory or <code>null</code>
*/
public static ICodeParser getCodeParser(final IScriptEngine engine) {
final EngineDescription description = engine.getDescription();
if (description != null) {
final List<ScriptType> scriptTypes = description.getSupportedScriptTypes();
if (!scriptTypes.isEmpty())
return scriptTypes.get(0).getCodeParser();
}
return null;
}
@Override
public void bundleChanged(BundleEvent event) {
final int type = event.getType();
if ((type == BundleEvent.RESOLVED) || (type == BundleEvent.STARTED) || (type == BundleEvent.STOPPED) || (type == BundleEvent.UPDATED)) {
synchronized (this) {
// clear cached entries
fAvailableModules = null;
fEngineDescriptions = null;
fScriptTypes = null;
fAvailableModuleCategories = null;
}
}
}
@Override
public Object executeScript(String scriptLocation, String engineID, String... arguments) throws Throwable {
// find script engine
EngineDescription engineDescription = null;
final IScriptService scriptService = ScriptService.getInstance();
if (engineID != null)
// locate engine by ID
engineDescription = scriptService.getEngineByID(engineID);
else {
// locate engine by file extension
final ScriptType scriptType = scriptService.getScriptType(scriptLocation);
if (scriptType != null)
engineDescription = scriptService.getEngine(scriptType.getName());
}
if (engineDescription != null) {
// create engine
final IScriptEngine engine = createEngine(engineDescription);
engine.setVariable("argv", arguments);
// TODO implement better URI handling - eg create URI and pass to script engine
Object scriptObject = ResourceTools.resolve(scriptLocation);
if (scriptObject == null)
// no file available, try to include to resolve URIs
scriptObject = "include(\"" + scriptLocation + "\")";
final ScriptResult scriptResult = engine.execute(scriptObject);
engine.schedule();
return scriptResult.get();
}
throw new IllegalArgumentException("Cannot locate a matching script engine");
}
@Override
public IScriptEngine createEngine(EngineDescription description) {
IScriptEngine engine;
try {
engine = description.createInstance();
// configure engine
if (engine instanceof AbstractScriptEngine)
((AbstractScriptEngine) engine).setEngineDescription(description);
// register engine locally
fRunningEngines.add(engine);
engine.addExecutionListener(this);
// inform listeners
for (final IScriptEngineLaunchExtension listener : fEngineListeners)
listener.createEngine(engine);
// engine loaded, now load launch extensions
for (final IScriptEngineLaunchExtension extension : getLaunchExtensions(description))
extension.createEngine(engine);
return engine;
} catch (final CoreException e) {
Logger.error(Activator.PLUGIN_ID, "Could not create script engine: " + description.getID(), e);
}
return null;
}
@Override
public Collection<IScriptEngine> getRunningEngines() {
return fRunningEngines;
}
@Override
public void notify(IScriptEngine engine, Script script, int status) {
if (status == IExecutionListener.ENGINE_END)
fRunningEngines.remove(engine);
}
}