blob: f5234220f436c61dc6e8989c52773d940d224c3e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 xored software, Inc.
*
* 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:
* xored software, Inc. - initial API and Implementation (Alex Panchenko)
*******************************************************************************/
package org.eclipse.dltk.javascript.typeinfo;
import static java.util.Collections.unmodifiableList;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.Platform;
import org.eclipse.dltk.annotations.ConfigurationElement;
import org.eclipse.dltk.annotations.Nullable;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.javascript.core.JavaScriptPlugin;
import org.eclipse.dltk.javascript.typeinfo.model.Element;
import org.eclipse.dltk.javascript.typeinfo.model.NamedElement;
import org.eclipse.dltk.javascript.typeinfo.model.TypeInfoModelResourceSet;
import org.eclipse.dltk.javascript.typeinfo.model.TypeVariable;
import org.eclipse.dltk.utils.LazyExtensionManager;
import org.eclipse.dltk.utils.LazyExtensionManager.Descriptor;
import org.eclipse.dltk.utils.SimpleExtensionManager;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.xmi.XMIResource;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceImpl;
import org.eclipse.osgi.util.NLS;
public class TypeInfoManager {
public static final String EXT_POINT = JavaScriptPlugin.PLUGIN_ID
+ ".typeinfo";
private static final String MODEL_ELEMENT = "model";
private static final String RESOURCE_ATTR = "resource";
private static final String URI_ATTR = "uri";
private static final String PROVIDER_ELEMENT = "provider";
private static String trim(String str) {
if (str != null) {
str = str.trim();
if (str.length() == 0) {
str = null;
}
}
return str;
}
private static URI createURI(IConfigurationElement element, String resource) {
return URI.createPlatformPluginURI("/"
+ element.getContributor().getName() + "/" + resource, true);
}
private static IConfigurationElement[] getConfigurationElements() {
return Platform.getExtensionRegistry().getConfigurationElementsFor(
EXT_POINT);
}
private static class ExtManager<E> extends SimpleExtensionManager<E> {
private final String elementName;
ExtManager(Class<E> elementType) {
super(elementType, EXT_POINT);
this.elementName = elementType.getAnnotation(
ConfigurationElement.class).value();
}
@Override
protected E createInstance(IConfigurationElement element) {
if (elementName.equals(element.getName())) {
return super.createInstance(element);
} else {
return null;
}
}
}
public static <E> SimpleExtensionManager<E> createManager(
Class<E> elementType) {
return new ExtManager<E>(elementType);
}
private static final SimpleExtensionManager<IModelBuilder> modelBuilderManager = createManager(IModelBuilder.class);
private static final LazyExtensionManager<ITypeProvider> providerManager = new LazyExtensionManager<ITypeProvider>(
EXT_POINT) {
class TypeProviderDescriptor extends Descriptor<ITypeProvider> {
public TypeProviderDescriptor(
LazyExtensionManager<ITypeProvider> manager,
IConfigurationElement configurationElement) {
super(manager, configurationElement);
}
@Override
public ITypeProvider get() {
return create();
}
}
@Override
protected boolean isValidElement(IConfigurationElement element) {
return PROVIDER_ELEMENT.equals(element.getName());
}
@Override
protected LazyExtensionManager.Descriptor<ITypeProvider> createDescriptor(
IConfigurationElement element) {
return new TypeProviderDescriptor(this, element);
}
};
private static final SimpleExtensionManager<IElementResolver> resolverManager = createManager(IElementResolver.class);
private static final SimpleExtensionManager<IElementConverter> converterManager = createManager(IElementConverter.class);
private static final SimpleExtensionManager<IMemberEvaluator> evaluatorManager = createManager(IMemberEvaluator.class);
private static final SimpleExtensionManager<ITypeInferenceHandlerFactory> nodeHandlerManager = createManager(ITypeInferenceHandlerFactory.class);
private static final SimpleExtensionManager<IRTypeFactory> typeFactoryManager = createManager(IRTypeFactory.class);
static class ModelBuilderRec {
IModelBuilder builder;
int priority;
}
/**
* Return contributed {@link IModelBuilder}s matching to the specified
* context. If context is <code>null</code> all model builders are returned.
*
* @param context
* @return
*/
public static IModelBuilder[] getModelBuilders(ITypeInfoContext context) {
final IModelBuilder[] all = modelBuilderManager.getInstances();
if (context == null) {
return all;
}
final Map<String, ModelBuilderRec> recs = new HashMap<String, ModelBuilderRec>();
for (IModelBuilder builder : all) {
final int priority = builder.priorityFor(context);
if (priority == IModelBuilder.PRIORITY_UNSUPPORTED) {
continue;
}
String featureId = builder.getFeatureId();
ModelBuilderRec rec = recs.get(featureId);
if (rec != null) {
if (priority > rec.priority) {
rec.priority = priority;
rec.builder = builder;
}
} else {
rec = new ModelBuilderRec();
rec.builder = builder;
rec.priority = priority;
recs.put(featureId, rec);
}
}
final IModelBuilder[] result = new IModelBuilder[recs.size()];
int index = 0;
for (ModelBuilderRec rec : recs.values()) {
result[index++] = rec.builder;
}
return result;
}
public static ITypeProvider[] createTypeProviders(ITypeInfoContext context) {
final Descriptor<ITypeProvider>[] descriptors = providerManager
.getDescriptors();
final ITypeProvider[] providers = new ITypeProvider[descriptors.length];
int index = 0;
for (Descriptor<ITypeProvider> descriptor : descriptors) {
final ITypeProvider provider = descriptor.get();
if (provider != null && provider.initialize(context)) {
providers[index++] = provider;
}
}
if (index != providers.length) {
final ITypeProvider[] result = new ITypeProvider[index];
System.arraycopy(providers, 0, result, 0, index);
return result;
}
return providers;
}
public static IElementResolver[] getElementResolvers() {
return resolverManager.getInstances();
}
public static IElementConverter[] getElementConverters() {
return converterManager.getInstances();
}
public static IMemberEvaluator[] getMemberEvaluators() {
return evaluatorManager.getInstances();
}
public static ITypeInferenceHandlerFactory[] getNodeHandlerFactories() {
return nodeHandlerManager.getInstances();
}
/**
* Creates extensions of the specified type for the specified context.
*
* @param context
* can be adapted to {@link ITypeInfoContext} or
* {@link ReferenceSource}
* @param extensionClass
* extension type, see {@link ITypeInferenceExtensionFactory} for
* the list of supported extensions.
* @param arg
* extension specific parameter, can be <code>null</code>
* @see ITypeInferenceExtensionFactory
*/
@SuppressWarnings("unchecked")
public static <E> List<E> createExtensions(IAdaptable context,
Class<E> extensionClass, Object arg) {
final List<E> extensions = new ArrayList<E>();
for (ITypeInferenceHandlerFactory factory : TypeInfoManager
.getNodeHandlerFactories()) {
if (factory instanceof ITypeInferenceExtensionFactory) {
final Object extension = ((ITypeInferenceExtensionFactory) factory)
.createExtension(context, extensionClass, arg);
if (extension != null && extensionClass.isInstance(extension)) {
extensions.add((E) extension);
}
}
}
return extensions;
}
public static IRTypeFactory[] getRTypeFactories() {
return typeFactoryManager.getInstances();
}
public static TypeInfoModelResourceSet loadModelResources() {
final TypeInfoModelResourceSet resourceSet = new TypeInfoModelResourceSet();
for (IConfigurationElement element : getConfigurationElements()) {
if (MODEL_ELEMENT.equals(element.getName())) {
final String resource = trim(element
.getAttribute(RESOURCE_ATTR));
final String uri = trim(element.getAttribute(URI_ATTR));
try {
if (uri != null) {
if (resource != null) {
resourceSet
.getURIConverter()
.getURIMap()
.put(URI.createURI(uri),
createURI(element, resource));
resourceSet.getResources().add(
newResource(URI.createURI(uri)));
}
} else if (resource != null) {
resourceSet.getResources().add(
newResource(createURI(element, resource)));
}
} catch (IllegalArgumentException e) {
JavaScriptPlugin.error(e);
}
}
}
/*
* iterate over copy, as it's possible that additional resources will
* appear while loading.
*/
for (Resource r : new ArrayList<Resource>(resourceSet.getResources())) {
if (!r.isLoaded()) {
try {
r.load(null);
} catch (IOException e) {
JavaScriptPlugin.error("Error loading " + r.getURI(), e);
if (!r.isLoaded()) {
r.getContents().clear();
}
}
}
}
return resourceSet;
}
public static XMIResource newResource() {
return new TypeInfoXMIResource();
}
public static XMIResource newResource(URI uri) {
return new TypeInfoXMIResource(uri);
}
/**
* Resource implementation which provides human readable fragments for
* {@link TypeVariable} references.
*/
public static class TypeInfoXMIResource extends XMIResourceImpl {
public TypeInfoXMIResource() {
super();
}
public TypeInfoXMIResource(URI uri) {
super(uri);
}
@Override
public EObject getEObject(String uriFragment) {
if (uriFragment.startsWith(ROOT)
&& uriFragment.length() > ROOT.length()
&& uriFragment.charAt(ROOT.length()) == '/') {
final StringTokenizer tokenizer = new StringTokenizer(
uriFragment.substring(ROOT.length() + 1), "/");
Object obj = this;
TOKENS: while (tokenizer.hasMoreTokens()) {
final String name = URI.decode(tokenizer.nextToken());
List<EObject> children = obj instanceof Resource ? ((Resource) obj)
.getContents() : ((EObject) obj).eContents();
for (EObject child : children) {
if (child instanceof NamedElement) {
if (name.equals(((NamedElement) child).getName())) {
obj = child;
continue TOKENS;
}
}
}
return super.getEObject(uriFragment);
}
return (EObject) obj;
}
return super.getEObject(uriFragment);
}
private static final String ROOT = "@ROOT";
@Override
public String getURIFragment(final EObject eObject) {
if (eObject.eContainer() != null) {
final List<String> path = new ArrayList<String>();
EObject obj = eObject;
for (;;) {
path.add(((NamedElement) obj).getName());
obj = obj.eContainer();
if (obj == null) {
break;
}
if (!(obj instanceof NamedElement)) {
return super.getURIFragment(eObject);
}
}
final StringBuilder sb = new StringBuilder();
sb.append(ROOT);
for (int i = path.size() - 1; i >= 0; --i) {
sb.append('/');
sb.append(URI.encodeSegment(path.get(i), false));
}
return sb.toString();
}
return super.getURIFragment(eObject);
}
}
private static final SimpleExtensionManager<MetaType> metaTypeManager = createManager(MetaType.class);
private static final Set<String> reportedMetaTypeIds = new HashSet<String>();
public static MetaType getMetaType(String initialValue) {
if (initialValue != null) {
for (MetaType metaType : metaTypeManager.getInstances()) {
if (initialValue.equals(metaType.getId())) {
return metaType;
}
}
synchronized (reportedMetaTypeIds) {
if (reportedMetaTypeIds.size() < 16
&& reportedMetaTypeIds.add(initialValue)) {
JavaScriptPlugin.error(NLS.bind("MetaType {0} not found",
initialValue));
}
}
}
return DefaultMetaType.DEFAULT;
}
/**
* Returns the unmodifiable list of all the registered {@link MetaType
* metatypes}.
*/
public static List<MetaType> getMetaTypes() {
return unmodifiableList(Arrays.asList(metaTypeManager.getInstances()));
}
/**
* Converts the specified JavaScript model element to {@link IModelElement}
* using all the contributed {@link IElementConverter}, returns the
* corresponding {@link IModelElement} or <code>null</code>.
*/
@Nullable
public static IModelElement convertElement(ISourceModule module,
Element element) {
for (IElementConverter converter : TypeInfoManager
.getElementConverters()) {
final IModelElement converted = converter.convert(module, element);
if (converted != null) {
return converted;
}
}
return null;
}
}