blob: 89eff5cf96dbac247f328c42a13825474ea1f930 [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.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() {
}
}
}