| /******************************************************************************* |
| * Copyright (c) 2012 NumberFour AG |
| * |
| * 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: |
| * 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; |
| } |
| |
| } |