blob: 89c7cc2790395f6b864e1c8209ca5223fd2efc95 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.e4.internal.languages.javascript;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.eclipse.e4.languages.javascript.JSBundle;
import org.eclipse.e4.languages.javascript.JSConstants;
import org.eclipse.e4.languages.javascript.JSONUtil;
import org.mozilla.javascript.Callable;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextAction;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Scriptable;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.framework.Version;
public class JSBundleImpl implements JSBundle {
private static final String ACTIVATOR_STOP_METHOD = "stop"; //$NON-NLS-1$
private static final String ACTIVATOR_START_METHOD = "start"; //$NON-NLS-1$
private String symbolicName;
private Version version;
private List imports;
private List requires;
private List exports;
private boolean singleton = false;
private boolean markedStarted = false;
private int state;
Scriptable scope;
private JSFrameworkImpl framework;
private JSBundleData bundleData;
JSBundleContext bundleContext;
Scriptable activator;
public JSBundleImpl(JSFrameworkImpl framework, JSBundleData bundleData) {
this.framework = framework;
this.bundleData = bundleData;
state = INSTALLED;
Map headers = bundleData.getHeaders();
parseSymbolicName((String) headers.get(Constants.BUNDLE_SYMBOLICNAME));
parseVersion((String) headers.get(Constants.BUNDLE_VERSION));
parseImports((String) headers.get(Constants.IMPORT_PACKAGE));
parseExports((String) headers.get(Constants.EXPORT_PACKAGE));
parseRequires((String) headers.get(Constants.REQUIRE_BUNDLE));
}
/* (non-Javadoc)
* @see org.eclipse.e4.internal.javascript.JSBundle#getSymbolicName()
*/
public String getSymbolicName() {
return symbolicName;
}
/* (non-Javadoc)
* @see org.eclipse.e4.internal.javascript.JSBundle#getVersion()
*/
public Version getVersion() {
return version;
}
public List getImports() {
return imports;
}
public List getRequires() {
return requires;
}
public List getExports() {
return exports;
}
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((symbolicName == null) ? 0 : symbolicName.hashCode());
result = prime * result + ((version == null) ? 0 : version.hashCode());
return result;
}
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
JSBundleImpl other = (JSBundleImpl) obj;
if (symbolicName == null) {
if (other.symbolicName != null)
return false;
} else if (!symbolicName.equals(other.symbolicName))
return false;
if (version == null) {
if (other.version != null)
return false;
} else if (!version.equals(other.version))
return false;
return true;
}
private void parseSymbolicName(String symbolicNameHeader) {
if (symbolicNameHeader == null)
throw new IllegalArgumentException("Bundle-SymbolicName cannot be null");
StringTokenizer tokenizer = new StringTokenizer(symbolicNameHeader, JSConstants.PARAMETER_DELIMITER);
symbolicName = tokenizer.nextToken().trim();
if (symbolicName.length() == 0)
throw new IllegalArgumentException("Illegal Bundle-SymbolicName");
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
if (token.indexOf(JSConstants.DIRECTIVE_EQUALS) != -1) {
int index = token.indexOf(JSConstants.DIRECTIVE_EQUALS);
String directiveName = token.substring(0, index).trim();
if (directiveName.length() == 0)
throw new IllegalArgumentException("bad syntax: " + token + " in " + symbolicNameHeader); //$NON-NLS-1$//$NON-NLS-2$
if (!directiveName.equals(Constants.SINGLETON_DIRECTIVE))
continue;
String value = token.substring(index + JSConstants.DIRECTIVE_EQUALS.length()).trim();
if (value.equalsIgnoreCase(Boolean.TRUE.toString()))
singleton = true;
} else
throw new IllegalArgumentException("bad syntax: " + token + " in " + symbolicNameHeader); //$NON-NLS-1$//$NON-NLS-2$
}
}
private void parseVersion(String header) {
version = Version.parseVersion(header);
}
private void parseRequires(String header) {
if (header == null) {
requires = Collections.EMPTY_LIST;
return;
}
requires = new ArrayList();
StringTokenizer tokenizer = new StringTokenizer(header, JSConstants.CLAUSE_DELIMITER);
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
JSRequireBundle jsRequire = new JSRequireBundle(token);
if (jsRequire != null)
requires.add(jsRequire);
}
}
private void parseExports(String header) {
if (header == null) {
exports = Collections.EMPTY_LIST;
return;
}
exports = new ArrayList();
StringTokenizer tokenizer = new StringTokenizer(header, JSConstants.CLAUSE_DELIMITER);
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
JSExportPackage jsExport = new JSExportPackage(token, this);
if (jsExport != null)
exports.add(jsExport);
}
}
private void parseImports(String header) {
if (header == null) {
imports = Collections.EMPTY_LIST;
return;
}
imports = new ArrayList();
StringTokenizer tokenizer = new StringTokenizer(header, JSConstants.CLAUSE_DELIMITER);
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
JSImportPackage jsImport = new JSImportPackage(token);
if (jsImport != null)
imports.add(jsImport);
}
}
/* (non-Javadoc)
* @see org.eclipse.e4.internal.javascript.JSBundle#getBundleId()
*/
public int getBundleId() {
return bundleData.getBundleId();
}
/* (non-Javadoc)
* @see org.eclipse.e4.internal.javascript.JSBundle#getLocation()
*/
public String getLocation() {
return bundleData.getLocation();
}
/* (non-Javadoc)
* @see org.eclipse.e4.internal.javascript.JSBundle#getHeaders()
*/
public Map getHeaders() {
return bundleData.getHeaders();
}
/* (non-Javadoc)
* @see org.eclipse.e4.internal.javascript.JSBundle#getState()
*/
public int getState() {
return state;
}
/* (non-Javadoc)
* @see org.eclipse.e4.internal.javascript.JSBundle#uninstall()
*/
public void uninstall() {
stop();
state = UNINSTALLED;
}
protected void resolve() {
if (state != INSTALLED)
return;
Map namedExports = new HashMap();
// process imports (first)
for (Iterator iterator = imports.iterator(); iterator.hasNext();) {
JSImportPackage jsImport = (JSImportPackage) iterator.next();
JSExportPackage jsExport = jsImport.getWiredExport();
if (!namedExports.containsKey(jsExport.getName()))
namedExports.put(jsExport.getName(), jsExport);
}
// process requires
for (Iterator iterator = requires.iterator(); iterator.hasNext();) {
JSRequireBundle jsRequire = (JSRequireBundle) iterator.next();
JSBundleImpl wiredBundle = jsRequire.getWiredBundle();
List wiredBundleExports = wiredBundle.getExports();
for (Iterator exportIterator = wiredBundleExports.iterator(); exportIterator.hasNext();) {
JSExportPackage jsExport = (JSExportPackage) exportIterator.next();
if (!namedExports.containsKey(jsExport.getName()))
namedExports.put(jsExport.getName(), jsExport);
}
}
ArrayList names = new ArrayList(namedExports.keySet());
// this sorts the set of names we'll be importing alphabetically and
// perhaps more importantly will allow us to create the shorter/parent dotted entries first
Collections.sort(names);
Scriptable newScope = framework.createScope(bundleData.getContextClassLoader());
for (Iterator iterator = names.iterator(); iterator.hasNext();) {
String exportName = (String) iterator.next();
JSExportPackage jsExport = (JSExportPackage) namedExports.get(exportName);
jsExport.addToScope(newScope);
}
evalScript(newScope);
scope = newScope;
state = RESOLVED;
}
private void evalScript(Scriptable scriptScope) {
Context context = Context.getCurrentContext();
String scriptPath = (String) bundleData.getHeaders().get(JSConstants.BUNDLE_SCRIPTPATH);
if (scriptPath == null)
scriptPath = JSConstants.SCRIPT_PATH_DOT;
StringTokenizer tokenizer = new StringTokenizer(scriptPath, JSConstants.SCRIPT_PATH_DELIMITER);
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken().trim();
if (token.equals(JSConstants.SCRIPT_PATH_DOT)) {
String script = (String) bundleData.getHeaders().get(JSConstants.BUNDLE_SCRIPT);
if (script != null)
context.evaluateString(scriptScope, script, "dot.js", 0, null); //$NON-NLS-1$
continue;
}
Reader reader = null;
try {
URL scriptURL = bundleData.getEntry(token);
reader = new InputStreamReader(scriptURL.openStream());
Map scriptProperties = new HashMap();
scriptProperties.put("name", token);
scriptProperties.put("base", bundleData.getLocation());
scriptProperties.put("path", bundleData.getRelativePath());
scriptProperties.put(Constants.BUNDLE_SYMBOLICNAME, symbolicName);
scriptProperties.put(Constants.BUNDLE_VERSION, version.toString());
if (bundleData.getContextClassLoader() instanceof RhinoClassLoader) {
RhinoClassLoader rhinoClassLoader = (RhinoClassLoader) bundleData.getContextClassLoader();
Bundle bundle = rhinoClassLoader.getBundle();
if (bundle != null) {
scriptProperties.put("OSGi." + Constants.BUNDLE_SYMBOLICNAME, bundle.getSymbolicName());
scriptProperties.put("OSGi." + Constants.BUNDLE_VERSION, bundle.getVersion().toString());
}
}
context.evaluateReader(scriptScope, reader, JSONUtil.write(scriptProperties), 0, null);
} catch (IOException e) {
throw new IllegalStateException("Error reading script for " + this.toString() + ": " + e.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$
} finally {
try {
if (reader != null)
reader.close();
} catch (IOException e) {
// ignore
}
}
}
}
protected void unresolve() {
if (state == ACTIVE) {
stop();
markedStarted = true;
}
scope = null;
if (state == RESOLVED)
state = INSTALLED;
}
public String toString() {
return "JavaScriptBundle{symbolic-name=" + symbolicName + ", version=" + version.toString() + "}"; //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
}
/* (non-Javadoc)
* @see org.eclipse.e4.internal.javascript.JSBundle#getScope()
*/
public Scriptable getScope() {
return scope;
}
/* (non-Javadoc)
* @see org.eclipse.e4.internal.javascript.JSBundle#start()
*/
public void start() {
markedStarted = true;
if (state != RESOLVED)
return;
state = STARTING;
bundleContext = framework.getBundleContext(this);
activator = createActivatorInstance();
if (activator != null) {
final Callable startMethod = (Callable) activator.get(ACTIVATOR_START_METHOD, activator);
framework.getContextFactory().call(new ContextAction() {
public Object run(Context cx) {
startMethod.call(cx, activator, activator, new Object[] {bundleContext});
return null;
}
});
}
state = ACTIVE;
}
private Scriptable createActivatorInstance() {
final String activatorName = (String) getHeaders().get(Constants.BUNDLE_ACTIVATOR);
if (activatorName == null)
return null;
Object value = lookup(activatorName);
if (!(value instanceof Function))
throw new IllegalStateException(activatorName + " is not a function."); //$NON-NLS-1$
final Function constructorFunction = (Function) value;
return (Scriptable) framework.getContextFactory().call(new ContextAction() {
public Object run(Context cx) {
return constructorFunction.construct(cx, scope, null);
}
});
}
/* (non-Javadoc)
* @see org.eclipse.e4.internal.javascript.JSBundle#stop()
*/
public void stop() {
markedStarted = false;
if (state != ACTIVE)
return;
state = STOPPING;
if (activator != null) {
final Callable stopMethod = (Callable) activator.get(ACTIVATOR_STOP_METHOD, activator);
framework.getContextFactory().call(new ContextAction() {
public Object run(Context cx) {
stopMethod.call(Context.getCurrentContext(), activator, activator, new Object[] {bundleContext});
return null;
}
});
}
state = RESOLVED;
}
public boolean isSingleton() {
return singleton;
}
public boolean isMarkedStarted() {
return markedStarted;
}
public Object call(final ContextAction action) {
final ClassLoader bundleClassLoader = bundleData.getContextClassLoader();
if (bundleClassLoader == null)
return framework.getContextFactory().call(action);
return framework.getContextFactory().call(new ContextAction() {
public Object run(Context context) {
ClassLoader current = context.getApplicationClassLoader();
context.setApplicationClassLoader(bundleClassLoader);
try {
return action.run(context);
} finally {
context.setApplicationClassLoader(current);
}
}
});
}
public Object lookup(String name) {
Scriptable currentScope = scope;
StringTokenizer tokenizer = new StringTokenizer(name, "."); //$NON-NLS-1$
while (true) {
String token = tokenizer.nextToken();
Object value = currentScope.get(token, currentScope);
if (!tokenizer.hasMoreTokens())
return value;
if (value instanceof Scriptable)
currentScope = (Scriptable) value;
else
throw new RuntimeException("Not Found: " + name + " in " + this.toString()); //$NON-NLS-1$//$NON-NLS-2$
}
}
}