blob: 1dc3d36a1dcada691fb60fd1072f4cbdd6b2d60e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2015 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ui;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IAdapterManager;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExecutableExtension;
import org.eclipse.core.runtime.Platform;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.osgi.framework.Bundle;
import org.osgi.service.packageadmin.ExportedPackage;
import org.osgi.service.packageadmin.PackageAdmin;
import org.osgi.util.tracker.ServiceTracker;
/**
* Basic IWorkingSetElementAdapter implementation that allows plugins to
* describe simple declarative element adapters.
* <p>
* The executable extension format for this class is as follows:<br>
* </p>
*
* <pre>
* <code>&lt;workingSet elementAdapterClass="org.eclipse.ui.BasicWorkingSetElementAdapter:class1.to.adapt.to[;option1=value1][;option2=value2],class2.to.adapt.to[;option1=value1][;option2=value2],..."&gt;
* ...
* &lt;/workingSet&gt;</code>
* </pre>
* <p>
* The valid options are:<br>
* </p>
* <dl>
* <dt>adapt</dt>
* <dd>Values: <code>true</code> or <code>true</code>. Specifies whether or not
* the platform {@link org.eclipse.core.runtime.IAdapterManager} and the
* {@link org.eclipse.core.runtime.IAdaptable} interface should be
* consulted.</dd>
* </dl>
*
* Please see the {@link #adaptElements(IWorkingSet, IAdaptable[])} method for
* details on behavior of this implementation.
*
* @since 3.3
*/
public final class BasicWorkingSetElementAdapter implements IWorkingSetElementAdapter, IExecutableExtension {
private static class Type {
private static final int NONE = 0;
private static final int ADAPT = 1;
String className;
int flags;
}
private Type[] preferredTypes = new Type[0];
private ServiceTracker<?, ?> packageTracker;
/**
* When invoked this method will iterate over all classes specified as
* IExecutableExtension arguements to this class in order and compare with the
* elements. If the element is directly assignable to the provided class then it
* is added to the result array as is. If the class has specified "adapt=true"
* as an argument and there is an available adapter in the platform
* IAdapterManager then it is returned. Finally, if "adapt=true" and the class
* is already loaded (determined by inspecting exported bundles via the platform
* PackageAdmin) a direct query for the adapter is made on the object and if it
* is not <code>null</code> then it is returned.
* <p>
* A consequence of the above is that it is possible for this method to return
* differing results based on the state of bundles loaded within the system.
* </p>
*
* @see org.eclipse.ui.IWorkingSetElementAdapter#adaptElements(org.eclipse.ui.IWorkingSet,
* org.eclipse.core.runtime.IAdaptable[])
* @see org.eclipse.core.runtime.IAdapterManager#getAdapter(Object, String)
* @see org.osgi.service.packageadmin.PackageAdmin#getExportedPackage(String)
*/
@Override
public IAdaptable[] adaptElements(IWorkingSet ws, IAdaptable[] elements) {
List<IAdaptable> adaptedElements = new ArrayList<>();
for (IAdaptable element : elements) {
IAdaptable adaptable = adapt(element);
if (adaptable != null)
adaptedElements.add(adaptable);
}
return adaptedElements.toArray(new IAdaptable[adaptedElements.size()]);
}
/**
* Adapt the given adaptable. Compares the given adaptable against the list of
* desired types and returns the first type that generates a match.
*
* @param adaptable the adaptable to adapt
* @return the resultant adaptable. May be the same adaptable, a new adaptable,
* or <code>null</code>.
*/
private IAdaptable adapt(IAdaptable adaptable) {
for (Type preferredType : preferredTypes) {
IAdaptable adaptedAdaptable = adapt(preferredType, adaptable);
if (adaptedAdaptable != null)
return adaptedAdaptable;
}
return null;
}
/**
* Adapt the given adaptable given the reference type.
*
* @param type the reference type
* @param adaptable the adaptable to adapt
* @return the resultant adaptable. May be the same adaptable, a new adaptable,
* or <code>null</code>.
*/
private IAdaptable adapt(Type type, IAdaptable adaptable) {
IAdapterManager adapterManager = Platform.getAdapterManager();
Class<?>[] directClasses = adapterManager.computeClassOrder(adaptable.getClass());
for (Class<?> clazz : directClasses) {
if (clazz.getName().equals(type.className))
return adaptable;
}
if ((type.flags & Type.ADAPT) != 0) {
Object adapted = adapterManager.getAdapter(adaptable, type.className);
if (adapted instanceof IAdaptable)
return (IAdaptable) adapted;
PackageAdmin admin = getPackageAdmin();
if (admin != null) {
int lastDot = type.className.lastIndexOf('.');
if (lastDot > 0) { // this lives in a package
String packageName = type.className.substring(0, lastDot);
ExportedPackage[] packages = admin.getExportedPackages(packageName);
if (packages != null && packages.length == 1) {
// if there is exactly one exporter of this
// package
// we can go further
if (packages[0].getExportingBundle().getState() == Bundle.ACTIVE) {
try {
// if the bundle is loaded we can safely get the
// class object and check for an adapter on the
// object directly
adapted = adaptable
.getAdapter(packages[0].getExportingBundle().loadClass(type.className));
if (adapted instanceof IAdaptable)
return (IAdaptable) adapted;
} catch (ClassNotFoundException e) {
WorkbenchPlugin.log(e);
}
}
}
}
}
}
return null;
}
@Override
public void dispose() {
if (packageTracker != null)
packageTracker.close();
}
@Override
public void setInitializationData(IConfigurationElement config, String propertyName, Object data) {
if (data instanceof String) {
List<Type> preferredTypes = new ArrayList<>(0);
for (StringTokenizer toker = new StringTokenizer((String) data, ","); toker.hasMoreTokens();) {//$NON-NLS-1$
String classNameAndOptions = toker.nextToken();
Type record = new Type();
parseOptions(classNameAndOptions, record);
preferredTypes.add(record);
}
this.preferredTypes = preferredTypes.toArray(new Type[preferredTypes.size()]);
}
}
/**
* Parse classname/option strings in the form:<br>
* <code>some.package.Class[:option1=value1][:option2=value2]...</code>
*
* @param classNameAndOptions the class name and possibly options to parse
* @param record the record to fill
*/
private void parseOptions(String classNameAndOptions, Type record) {
for (StringTokenizer toker = new StringTokenizer(classNameAndOptions, ";"); toker.hasMoreTokens();) { //$NON-NLS-1$
String token = toker.nextToken();
if (record.className == null)
record.className = token;
else {
for (StringTokenizer pair = new StringTokenizer(token, "="); pair.hasMoreTokens();) {//$NON-NLS-1$
if (pair.countTokens() == 2) {
String param = pair.nextToken();
String value = pair.nextToken();
if ("adapt".equals(param)) { //$NON-NLS-1$
record.flags ^= "true".equals(value) ? Type.ADAPT : Type.NONE; //$NON-NLS-1$
}
}
}
}
}
}
/**
* Prime the PackageAdmin service tracker and return the service (if available).
*
* @return the PackageAdmin service or null if it is not available
*/
private PackageAdmin getPackageAdmin() {
if (packageTracker == null) {
packageTracker = new ServiceTracker<>(WorkbenchPlugin.getDefault().getBundleContext(),
PackageAdmin.class.getName(), null);
packageTracker.open();
}
return (PackageAdmin) packageTracker.getService();
}
}