blob: b0dc84bbc751e61b45127b44943c19a733b6e26a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2015 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.texteditor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import org.osgi.framework.Bundle;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.resources.IFile;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.editors.text.NLSUtility;
import org.eclipse.ui.editors.text.EditorsUI;
/**
* This registry manages shared document providers. Document
* providers are specified in <code>plugin.xml</code> either
* per name extension or per editor input type. A name extension
* rule always overrules an editor input type rule. Editor input
* type rules follow the same rules <code>IAdapterManager</code>
* used to find object adapters.
*
* @see org.eclipse.core.runtime.IAdapterManager
* @noinstantiate This class is not intended to be instantiated by clients.
* @noextend This class is not intended to be subclassed by clients.
*/
public class DocumentProviderRegistry {
/** The registry singleton. */
private static DocumentProviderRegistry fgRegistry;
/**
* Returns the standard document provider registry.
*
* @return the default document provider registry
*/
public static DocumentProviderRegistry getDefault() {
if (fgRegistry == null)
fgRegistry= new DocumentProviderRegistry();
return fgRegistry;
}
/** The mapping between name extensions and configuration elements. */
private Map<String, Set<IConfigurationElement>> fExtensionMapping= new HashMap<>();
/** The mapping between editor input type names and configuration elements. */
private Map<String, Set<IConfigurationElement>> fInputTypeMapping= new HashMap<>();
/** The mapping between configuration elements and instantiated document providers. */
private Map<IConfigurationElement, IDocumentProvider> fInstances= new HashMap<>();
/**
* Creates a new document provider registry and initializes it with the information
* found in the plug-in registry.
*/
private DocumentProviderRegistry() {
initialize();
}
/**
* Reads the comma-separated value of the given configuration element
* for the given attribute name and remembers the configuration element
* in the given map under the individual tokens of the attribute value.
*
* @param map the map
* @param element the configuration element
* @param attributeName the attribute name
*/
private void read(Map<String, Set<IConfigurationElement>> map, IConfigurationElement element, String attributeName) {
String value= element.getAttribute(attributeName);
if (value != null) {
StringTokenizer tokenizer= new StringTokenizer(value, ","); //$NON-NLS-1$
while (tokenizer.hasMoreTokens()) {
String token= tokenizer.nextToken().trim();
Set<IConfigurationElement> s= map.get(token);
if (s == null) {
s= new HashSet<>();
map.put(token, s);
}
s.add(element);
}
}
}
/**
* Initializes the document provider registry. It retrieves all implementers of the <code>documentProviders</code>
* extension point and remembers those implementers based on the name extensions and the editor input
* types they are for.
*/
private void initialize() {
IExtensionPoint extensionPoint;
extensionPoint= Platform.getExtensionRegistry().getExtensionPoint(EditorsUI.PLUGIN_ID, "documentProviders"); //$NON-NLS-1$
if (extensionPoint == null) {
String msg= NLSUtility.format(TextEditorMessages.DocumentProviderRegistry_error_extension_point_not_found, PlatformUI.PLUGIN_ID);
Bundle bundle = Platform.getBundle(PlatformUI.PLUGIN_ID);
ILog log= Platform.getLog(bundle);
log.log(new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, IStatus.OK, msg, null));
return;
}
IConfigurationElement[] elements= extensionPoint.getConfigurationElements();
for (int i= 0; i < elements.length; i++) {
read(fExtensionMapping, elements[i], "extensions"); //$NON-NLS-1$
read(fInputTypeMapping, elements[i], "inputTypes"); //$NON-NLS-1$
}
}
/**
* Returns the document provider for the given configuration element.
* If there is no instantiated document provider remembered for this
* element, a new document provider is created and put into the cache.
*
* @param entry the configuration element
* @return the document provider for the given entry
*/
private IDocumentProvider getDocumentProvider(IConfigurationElement entry) {
IDocumentProvider provider= fInstances.get(entry);
if (provider == null) {
try {
provider= (IDocumentProvider) entry.createExecutableExtension("class"); //$NON-NLS-1$
fInstances.put(entry, provider);
} catch (CoreException x) {
}
}
return provider;
}
/**
* Returns the first enumerated element of the given set.
*
* @param set the set
* @return the first configuration element in the set or <code>null</code> if none
*/
private IConfigurationElement selectConfigurationElement(Set<IConfigurationElement> set) {
if (set != null && !set.isEmpty()) {
Iterator<IConfigurationElement> e= set.iterator();
return e.next();
}
return null;
}
/**
* Returns a shared document provider for the given name extension.
*
* @param extension the name extension to be used for lookup
* @return the shared document provider or <code>null</code>
*/
public IDocumentProvider getDocumentProvider(String extension) {
Set<IConfigurationElement> set= fExtensionMapping.get(extension);
if (set != null) {
IConfigurationElement entry= selectConfigurationElement(set);
return getDocumentProvider(entry);
}
return null;
}
/**
* Computes the class hierarchy of the given type. The type is
* part of the computed hierarchy.
*
* @param type the type
* @return a list containing the super class hierarchy
*/
private List<Class<?>> computeClassList(Class<?> type) {
List<Class<?>> result= new ArrayList<>();
Class<?> c= type;
while (c != null) {
result.add(c);
c= c.getSuperclass();
}
return result;
}
/**
* Computes the list of all interfaces for the given list of
* classes. The interface lists of the given classes are
* concatenated.
*
* @param classes a list of {@link java.lang.Class} objects
* @return a list with elements of type <code>Class</code>
*/
private List<Class<?>> computeInterfaceList(List<Class<?>> classes) {
List<Class<?>> result= new ArrayList<>(4);
Hashtable<Class<?>, Class<?>> visited= new Hashtable<>(4);
Iterator<Class<?>> e= classes.iterator();
while (e.hasNext()) {
Class<?> c= e.next();
computeInterfaceList(c.getInterfaces(), result, visited);
}
return result;
}
/**
* Computes the list of all interfaces of the given list of interfaces,
* taking a depth-first approach.
*
* @param interfaces an array of {@link java.lang.Class} objects denoting interfaces
* @param result the result list
* @param visited map of visited interfaces
*/
private void computeInterfaceList(Class<?>[] interfaces, List<Class<?>> result, Hashtable<Class<?>, Class<?>> visited) {
List<Class<?>> toBeVisited= new ArrayList<>(interfaces.length);
for (int i= 0; i < interfaces.length; i++) {
Class<?> iface= interfaces[i];
if (visited.get(iface) == null) {
visited.put(iface, iface);
result.add(iface);
toBeVisited.add(iface);
}
}
Iterator<Class<?>> e= toBeVisited.iterator();
while(e.hasNext()) {
Class<?> iface= e.next();
computeInterfaceList(iface.getInterfaces(), result, visited);
}
}
/**
* Returns the configuration elements for the first class in the list
* of given classes for which configuration elements have been remembered.
*
* @param classes a list of {@link java.lang.Class} objects
* @return an input type mapping or <code>null</code>
*/
private Set<IConfigurationElement> getFirstInputTypeMapping(List<Class<?>> classes) {
Iterator<Class<?>> e= classes.iterator();
while (e.hasNext()) {
Class<?> c= e.next();
Set<IConfigurationElement> mapping= fInputTypeMapping.get(c.getName());
if (mapping != null)
return mapping;
}
return null;
}
/**
* Returns the appropriate configuration element for the given type. If
* there is no configuration element for the type's name, first the list of
* super classes is searched, and if not successful the list of all interfaces.
*
* @param type a {@link java.lang.Class} object
* @return an input type mapping or <code>null</code>
*/
private Set<IConfigurationElement> findInputTypeMapping(Class<?> type) {
if (type == null)
return null;
Set<IConfigurationElement> mapping= fInputTypeMapping.get(type.getName());
if (mapping != null)
return mapping;
List<Class<?>> classList= computeClassList(type);
mapping= getFirstInputTypeMapping(classList);
if (mapping != null)
return mapping;
return getFirstInputTypeMapping(computeInterfaceList(classList));
}
/**
* Returns the shared document for the type of the given editor input.
*
* @param editorInput the input for whose type the provider is looked up
* @return the shared document provider
*/
public IDocumentProvider getDocumentProvider(IEditorInput editorInput) {
IDocumentProvider provider= null;
IFile file= editorInput.getAdapter(IFile.class);
if (file != null)
provider= getDocumentProvider(file.getFileExtension());
if (provider == null) {
Set<IConfigurationElement> set= findInputTypeMapping(editorInput.getClass());
if (set != null) {
IConfigurationElement entry= selectConfigurationElement(set);
provider= getDocumentProvider(entry);
}
}
return provider;
}
}