| /******************************************************************************* |
| * 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.typeinfo; |
| |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.dltk.annotations.Internal; |
| import org.eclipse.dltk.compiler.CharOperation; |
| import org.eclipse.dltk.javascript.typeinfo.model.SimpleType; |
| import org.eclipse.dltk.javascript.typeinfo.model.Type; |
| import org.eclipse.dltk.javascript.typeinfo.model.TypeInfoModelLoader; |
| import org.eclipse.emf.common.util.EList; |
| import org.eclipse.emf.common.util.TreeIterator; |
| import org.eclipse.emf.common.util.URI; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.InternalEObject; |
| import org.eclipse.emf.ecore.resource.Resource; |
| import org.eclipse.emf.ecore.resource.impl.ResourceImpl; |
| import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; |
| import org.eclipse.emf.ecore.util.EcoreUtil; |
| |
| /** |
| * TypeCache abstract implementation |
| */ |
| public abstract class TypeCache { |
| |
| private final URI baseURI; |
| private final TypeCacheResourceSet resourceSet; |
| |
| public TypeCache(String scheme, String authority) { |
| this(URI.createHierarchicalURI(scheme, authority, null, null, null)); |
| } |
| |
| public TypeCache(URI baseURI) { |
| Assert.isLegal(baseURI.isHierarchical(), |
| "TypeCache baseURI must be hierarchical"); |
| this.baseURI = baseURI; |
| this.resourceSet = new TypeCacheResourceSet(this); |
| } |
| |
| /** |
| * Adds the specified type to the specified bucket within the cache. |
| * Internal method to be called from {@link #createType(String)}. |
| * |
| * @param bucket |
| * @param type |
| * @return |
| */ |
| protected Type addType(String bucket, Type type) { |
| Assert.isLegal(type.eResource() == null); |
| Assert.isLegal(!type.eIsProxy()); |
| final TypeCacheResource resource = getResource(bucket, true); |
| synchronized (resource) { |
| final String name = type.getName(); |
| final Type previous = resource.types.get(name); |
| if (previous != null) { |
| return previous; |
| } |
| resource.types.put(name, type); |
| resource.getContents().add(type); |
| } |
| return type; |
| } |
| |
| static class ThreadState { |
| int activeOperations; |
| } |
| |
| private final ThreadLocal<ThreadState> threads = new ThreadLocal<ThreadState>() { |
| @Override |
| protected ThreadState initialValue() { |
| return new ThreadState(); |
| } |
| }; |
| |
| private TypeCacheResource getResource(String bucket, boolean loadOnDemand) { |
| synchronized (resourceSet) { |
| return (TypeCacheResource) resourceSet.getResource(getURI(bucket), |
| loadOnDemand); |
| } |
| } |
| |
| /** |
| * Finds the specified type in the cache. If type is not cached in the cache |
| * yet, then the {@link #createType(String)} is called to create the entry. |
| * |
| * @param typeName |
| * @return |
| */ |
| public Type findType(String context, String typeName) { |
| final TypeCacheResource resource = getResource(context, false); |
| if (resource != null) { |
| synchronized (resource) { |
| final Type type = resource.types.get(typeName); |
| if (type != null) { |
| return type; |
| } |
| } |
| } |
| final ThreadState state = threads.get(); |
| ++state.activeOperations; |
| try { |
| final String[] accessible = getAccessibleBuckets(context); |
| for (String ac : accessible) { |
| final TypeCacheResource ar = getResource(ac, false); |
| if (ar != null) { |
| synchronized (ar) { |
| final Type type = ar.types.get(typeName); |
| if (type != null) { |
| return type; |
| } |
| } |
| } |
| } |
| return createType(context, typeName); |
| } finally { |
| --state.activeOperations; |
| } |
| } |
| |
| /** |
| * Returns all the buckets accessible from the specified context. |
| * |
| * @param context |
| * @return |
| */ |
| protected String[] getAccessibleBuckets(String context) { |
| return CharOperation.NO_STRINGS; |
| } |
| |
| /** |
| * Creates the specified type. Should create the type, call |
| * {@link #addType(String, Type)} with it and return the result. If it needs |
| * other types, then should call {@link #getType(String)} and |
| * {@link #getTypeRef(String)}. |
| * |
| * @param typeName |
| * @return |
| */ |
| protected abstract Type createType(String context, String typeName); |
| |
| /** |
| * For the internal usage within {@link #createType(String)} |
| * |
| * @param typeName |
| * @return |
| */ |
| protected final Type getType(String context, String typeName) { |
| return getType(context, typeName, false); |
| } |
| |
| /** |
| * For the internal usage within {@link #createType(String)} |
| * |
| * @param typeName |
| * @return |
| */ |
| @Internal |
| Type getType(String context, String typeName, boolean force) { |
| Type type = TypeInfoModelLoader.getInstance().getType(typeName); |
| if (type != null) { |
| return type; |
| } |
| TypeCacheResource resource = getResource(context, false); |
| if (resource != null) { |
| synchronized (resource) { |
| type = resource.types.get(typeName); |
| if (type != null) { |
| return type; |
| } |
| } |
| } |
| final ThreadState state = threads.get(); |
| if (!force && state.activeOperations != 0) { |
| return TypeUtil.createProxy(typeName); |
| } |
| ++state.activeOperations; |
| try { |
| final String[] accessible = getAccessibleBuckets(context); |
| for (String bucket : accessible) { |
| resource = getResource(bucket, false); |
| if (resource != null) { |
| synchronized (resource) { |
| type = resource.types.get(typeName); |
| if (type != null) { |
| return type; |
| } |
| } |
| } |
| } |
| return createType(context, typeName); |
| } finally { |
| --state.activeOperations; |
| } |
| } |
| |
| /** |
| * For the internal usage within {@link #createType(String)} |
| * |
| * @param typeName |
| * @return |
| */ |
| protected SimpleType getTypeRef(String context, String typeName) { |
| return TypeUtil.ref(getType(context, typeName)); |
| } |
| |
| /** |
| * Returns the resource URI for the specified cache bucket. |
| * |
| * @param bucket |
| * @return |
| */ |
| private URI getURI(String bucket) { |
| if (bucket == null || bucket.length() == 0) { |
| return baseURI; |
| } else { |
| // TODO (alex) cache URIs? |
| return baseURI.appendSegment(bucket); |
| } |
| } |
| |
| /** |
| * Tests if one URI is a prefix of another. |
| * |
| * @param base |
| * @param uri |
| * @return |
| */ |
| private static boolean isPrefixOf(URI base, URI uri) { |
| int segmentCount; |
| if (uri.isHierarchical() && base.scheme().equals(uri.scheme()) |
| && base.authority().equals(uri.authority()) |
| && (segmentCount = base.segmentCount()) <= uri.segmentCount()) { |
| for (int i = 0; i < segmentCount; ++i) { |
| if (!base.segment(i).equals(uri.segment(i))) |
| return false; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Returns the cache bucket of the specified resource. |
| * |
| * @param resourceContext |
| * @return |
| */ |
| @Internal |
| String getContextOf(Resource resourceContext) { |
| final URI uri = resourceContext.getURI(); |
| if (uri != null && isPrefixOf(baseURI, uri)) { |
| if (baseURI.segmentCount() == uri.segmentCount()) { |
| return ""; |
| } else { |
| return uri.segment(baseURI.segmentCount()); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Clears the cache |
| */ |
| public void clear() { |
| synchronized (resourceSet) { |
| final EList<Resource> resources = resourceSet.getResources(); |
| for (Resource resource : resources) { |
| synchronized (resource) { |
| resource.unload(); |
| } |
| } |
| resources.clear(); |
| } |
| } |
| |
| /** |
| * Clears the specified bucket of the cache |
| * |
| * @param bucket |
| */ |
| public void clear(String bucket) { |
| final TypeCacheResource resource; |
| synchronized (resourceSet) { |
| resource = getResource(bucket, false); |
| } |
| if (resource != null) { |
| synchronized (resource) { |
| resource.unload(); |
| } |
| } |
| } |
| |
| private static class TypeCacheResourceSet extends ResourceSetImpl implements |
| TypeInfoResourceSet { |
| |
| private final TypeCache cache; |
| |
| public TypeCacheResourceSet(TypeCache cache) { |
| super(); |
| this.cache = cache; |
| setURIResourceMap(new HashMap<URI, Resource>()); |
| } |
| |
| @Override |
| public Resource createResource(URI uri, String contentType) { |
| final TypeCacheResource resource = new TypeCacheResource(uri); |
| getResources().add(resource); |
| return resource; |
| } |
| |
| @Override |
| protected void demandLoadHelper(Resource resource) { |
| resource.getContents().clear(); // set the loaded flag. |
| } |
| |
| public EObject resolve(InternalEObject proxy, EObject objectContext, |
| Resource resourceContext) { |
| final URI uri = proxy.eProxyURI(); |
| if (TypeUtil.isTypeProxy(uri)) { |
| return cache.getType(cache.getContextOf(resourceContext), |
| URI.decode(uri.fragment()), true); |
| } else { |
| return EcoreUtil.resolve(proxy, this); |
| } |
| } |
| } |
| |
| private static class TypeCacheResource extends ResourceImpl { |
| |
| public TypeCacheResource(URI uri) { |
| super(uri); |
| } |
| |
| final Map<String, Type> types = new HashMap<String, Type>(); |
| |
| @Override |
| public String toString() { |
| return getClass().getSimpleName() + '@' |
| + Integer.toHexString(hashCode()) + "@" + uri; |
| } |
| |
| @Override |
| protected void doUnload() { |
| types.clear(); |
| super.doUnload(); |
| } |
| |
| /** |
| * This method is overridden to return only direct contents of the list |
| * (i.e. types). We are not interested in making proxies out of |
| * properties and methods. Other callers of this method are not used |
| * here. |
| */ |
| @Override |
| protected TreeIterator<EObject> getAllProperContents( |
| List<EObject> contents) { |
| return new ForwardingTreeIterator<EObject>(contents.iterator()); |
| } |
| } |
| |
| private static class ForwardingTreeIterator<E> implements TreeIterator<E> { |
| private final Iterator<E> delegate; |
| |
| public ForwardingTreeIterator(Iterator<E> delegate) { |
| this.delegate = delegate; |
| } |
| |
| public boolean hasNext() { |
| return delegate.hasNext(); |
| } |
| |
| public E next() { |
| return delegate.next(); |
| } |
| |
| public void remove() { |
| delegate.remove(); |
| } |
| |
| public void prune() { |
| } |
| } |
| |
| } |