blob: 80b0d33f3b203e3af45261f5d5d1ba84bc28bb9d [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.javascript;
import java.io.*;
import java.net.URL;
import java.util.*;
import org.eclipse.e4.javascript.Constants;
import org.eclipse.e4.javascript.JSBundle;
import org.mozilla.javascript.*;
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_SYMBOLIC_NAME_HEADER));
parseVersion((String) headers.get(Constants.BUNDLE_VERSION_HEADER));
parseImports((String) headers.get(Constants.IMPORTS_HEADER));
parseExports((String) headers.get(Constants.EXPORTS_HEADER));
parseRequires((String) headers.get(Constants.REQUIRES_HEADER));
}
/* (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) {
StringTokenizer tokenizer = new StringTokenizer(symbolicNameHeader, Constants.PARAMETER_DELIMITER);
symbolicName = tokenizer.nextToken().trim();
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
if (token.indexOf(Constants.ATTRIBUTE_EQUALS) != -1) {
int index = token.indexOf(Constants.ATTRIBUTE_EQUALS);
String attributeName = token.substring(0, index).trim();
if (attributeName.length() == 0)
throw new IllegalArgumentException("bad syntax: " + token + " in " + symbolicNameHeader); //$NON-NLS-1$//$NON-NLS-2$
if (!attributeName.equals(Constants.SINGLETON_ATTRIBUTE))
continue;
String value = token.substring(index + Constants.ATTRIBUTE_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, Constants.CLAUSE_DELIMITER);
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
JSRequire jsRequire = new JSRequire(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, Constants.CLAUSE_DELIMITER);
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
JSExport jsExport = new JSExport(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, Constants.CLAUSE_DELIMITER);
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
JSImport jsImport = new JSImport(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();) {
JSImport jsImport = (JSImport) iterator.next();
JSExport jsExport = jsImport.getWiredExport();
if (!namedExports.containsKey(jsExport.getName()))
namedExports.put(jsExport.getName(), jsExport);
}
// process requires
for (Iterator iterator = requires.iterator(); iterator.hasNext();) {
JSRequire jsRequire = (JSRequire) iterator.next();
JSBundleImpl wiredBundle = jsRequire.getWiredBundle();
List wiredBundleExports = wiredBundle.getExports();
for (Iterator exportIterator = wiredBundleExports.iterator(); exportIterator.hasNext();) {
JSExport jsExport = (JSExport) 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();
JSExport jsExport = (JSExport) 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(Constants.SCRIPT_PATH_HEADER);
if (scriptPath == null)
scriptPath = Constants.SCRIPT_PATH_DOT;
StringTokenizer tokenizer = new StringTokenizer(scriptPath, Constants.SCRIPT_PATH_DELIMITER);
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken().trim();
if (token.equals(Constants.SCRIPT_PATH_DOT)) {
String script = (String) bundleData.getHeaders().get(Constants.SCRIPT_HEADER);
if (script != null)
context.evaluateString(scriptScope, script, "script.js", 0, null); //$NON-NLS-1$
continue;
}
Reader reader = null;
try {
URL scriptURL = bundleData.getEntry(token);
reader = new InputStreamReader(scriptURL.openStream());
context.evaluateReader(scriptScope, reader, token, 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);
ContextFactory.getGlobal().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.ACTIVATOR_HEADER);
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) ContextFactory.getGlobal().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);
ContextFactory.getGlobal().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 ContextFactory.getGlobal().call(action);
return ContextFactory.getGlobal().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$
}
}
}