blob: 766332f7fa22e1fd52a6e1ffb0e3f9ded1f0029c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012 NumberFour AG
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* NumberFour AG - initial API and Implementation (Alex Panchenko)
*******************************************************************************/
package org.eclipse.dltk.javascript.core;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import org.eclipse.dltk.ast.ASTNode;
import org.eclipse.dltk.codeassist.ICompletionEngine;
import org.eclipse.dltk.codeassist.ISelectionEngine;
import org.eclipse.dltk.compiler.env.IModuleSource;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.builder.IBuildContext;
import org.eclipse.dltk.core.builder.IBuildParticipant;
import org.eclipse.dltk.internal.javascript.ti.TypeInferencer2;
import org.eclipse.dltk.internal.javascript.validation.TypeInfoValidator;
import org.eclipse.dltk.javascript.ast.JSNode;
import org.eclipse.dltk.javascript.ast.Script;
import org.eclipse.dltk.javascript.internal.core.CollectingVisitor;
import org.eclipse.dltk.javascript.internal.core.CoreMessages;
import org.eclipse.dltk.javascript.parser.JavaScriptParserUtil;
import org.eclipse.dltk.javascript.typeinference.IValueReference;
import org.eclipse.dltk.javascript.typeinfo.ITypeSystem;
import org.eclipse.osgi.util.NLS;
/**
* Bindings for the module, i.e. type information for the AST nodes.
*/
public class JSBindings implements Map<ASTNode, IValueReference> {
private final ITypeSystem typeSystem;
private final Map<ASTNode, IValueReference> nodeMap;
protected JSBindings(ITypeSystem typeSystem,
Map<ASTNode, IValueReference> nodeMap) {
this.typeSystem = typeSystem;
this.nodeMap = nodeMap;
}
protected boolean isCacheable() {
return true;
}
/**
* Returns bindings for the specified {@link Script} or <code>null</code> if
* not available.
*/
public static JSBindings of(Script script) {
final ISourceModule module = (ISourceModule) script
.getAttribute(JavaScriptParserUtil.ATTR_MODULE);
if (module != null) {
return JSBindings.get(module, script);
} else {
return null;
}
}
/**
* Returns the {@link IValueReference} describing the specified node or
* <code>null</code> if not available.
*/
public static IValueReference resolveBinding(ASTNode node) {
if (node instanceof JSNode) {
final JSNode jnode = (JSNode) node;
final Script script = jnode.getScript();
if (script != null) {
final JSBindings bindings = of(script);
if (bindings != null) {
return bindings.get(node);
}
}
}
return null;
}
private static JSBindings buildBindings(IModelElement element, Script script) {
final JSBindings cached = TypeInfoValidator.getCachedBindings(script);
if (cached != null) {
return cached;
}
if (DLTKCore.PERFOMANCE) {
if (element != null) {
System.out.println("build bindings for " + element.getPath());
}
}
final TypeInferencer2 inferencer = new TypeInferencer2();
final CollectingVisitor collector = new CollectingVisitor(inferencer);
inferencer.setModelElement(element);
inferencer.setVisitor(collector);
inferencer.doInferencing(script);
return new JSBindings(inferencer, collector.bindings);
}
/**
* Returns the bindings for the specified {@link IModuleSource} (input of
* {@link ICompletionEngine} and {@link ISelectionEngine}). If source
* implements {@link ISourceModule} then this function just delegates to the
* next one which also does caching, otherwise the result is computed just
* for the specified <code>source</code>.
*/
public static JSBindings of(IModuleSource source) {
if (source instanceof ISourceModule) {
return of((ISourceModule) source);
} else if (source instanceof IBuildContext) {
return of((IBuildContext) source);
}
final Script script = JavaScriptParserUtil.parse(source, null);
return buildBindings(source.getModelElement(), script);
}
/**
* Returns bindings for the specified {@link ISourceModule}. The result is
* cached in shared cached AST. The cache is cleared on resource change or
* changes in the editor.
*/
public static JSBindings of(ISourceModule module) {
final Script script = JavaScriptParserUtil.parse(module, null);
return get(module, script);
}
private static final String ATTR_BINDINGS = JSBindings.class.getName();
/**
* Returns bindings for the specified {@link ISourceModule} and AST. The
* result is cached in AST.
*/
private static JSBindings get(ISourceModule module, Script script) {
JSBindings bindings = (JSBindings) script.getAttribute(ATTR_BINDINGS);
if (bindings != null) {
return bindings;
}
bindings = buildBindings(module, script);
if (bindings.isCacheable()) {
script.setAttribute(ATTR_BINDINGS, bindings);
}
return bindings;
}
/**
* Returns bindings for the specified {@link IBuildContext}. This method
* should be called only from {@link IBuildParticipant} which has dependency
* on {@link TypeInfoValidator}.
*
* @throws IllegalStateException
* if {@link IBuildParticipant} preconditions not met.
*/
public static JSBindings of(IBuildContext context) {
final ITypeSystem typeSystem = ITypeSystem.CURRENT.get();
if (typeSystem == null) {
throw new IllegalStateException(NLS.bind(
CoreMessages.JSBindings_not_available,
CoreMessages.JSBindings_currentTypeSystem,
TypeInfoValidator.ID));
}
@SuppressWarnings("unchecked")
final Map<ASTNode, IValueReference> bindings = (Map<ASTNode, IValueReference>) context
.get(TypeInfoValidator.ATTR_BINDINGS);
if (bindings == null) {
throw new IllegalStateException(NLS.bind(
CoreMessages.JSBindings_not_available,
CoreMessages.JSBindings_precomputedBindings,
TypeInfoValidator.ID));
}
return new JSBindings(typeSystem, bindings);
}
public ITypeSystem getTypeSystem() {
return typeSystem;
}
public IValueReference get(ASTNode node) {
return nodeMap.get(node);
}
/**
* Executes the code temporary setting type system of this instance as
* current.
*/
public void run(Runnable runnable) {
ITypeSystem.CURRENT.runWith(typeSystem, runnable);
}
/**
* Executes the code temporary setting type system of this instance as
* current.
*/
public <V> V run(Callable<V> callable) throws Exception {
return ITypeSystem.CURRENT.runWith(typeSystem, callable);
}
public int size() {
return nodeMap.size();
}
public boolean isEmpty() {
return nodeMap.isEmpty();
}
public boolean containsKey(Object key) {
return nodeMap.containsKey(key);
}
public boolean containsValue(Object value) {
return nodeMap.containsValue(value);
}
public IValueReference get(Object key) {
return nodeMap.get(key);
}
public IValueReference put(ASTNode key, IValueReference value) {
throw new UnsupportedOperationException();
}
public IValueReference remove(Object key) {
throw new UnsupportedOperationException();
}
public void putAll(Map<? extends ASTNode, ? extends IValueReference> m) {
throw new UnsupportedOperationException();
}
public void clear() {
throw new UnsupportedOperationException();
}
static class Views {
volatile Set<ASTNode> keySet;
volatile Collection<IValueReference> values;
volatile Set<Map.Entry<ASTNode, IValueReference>> entrySet;
}
private transient Views views;
private synchronized Views getViews() {
if (views == null) {
views = new Views();
}
return views;
}
public Set<ASTNode> keySet() {
final Views v = getViews();
if (v.keySet == null) {
v.keySet = Collections.unmodifiableSet(nodeMap.keySet());
}
return v.keySet;
}
public Collection<IValueReference> values() {
final Views v = getViews();
if (v.values == null) {
v.values = Collections.unmodifiableCollection(nodeMap.values());
}
return v.values;
}
public Set<Map.Entry<ASTNode, IValueReference>> entrySet() {
final Views v = getViews();
if (v.entrySet == null) {
v.entrySet = Collections.unmodifiableSet(nodeMap.entrySet());
}
return v.entrySet;
}
}