blob: 4aeb2560cf2e6e85efb222ea1d797f0fb561a974 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2012 Obeo.
* 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:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.acceleo.common.internal.utils;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.acceleo.common.AcceleoCommonMessages;
import org.eclipse.acceleo.common.AcceleoCommonPlugin;
import org.eclipse.acceleo.common.internal.utils.workspace.AcceleoWorkspaceUtil;
import org.eclipse.acceleo.common.internal.utils.workspace.BundleURLConverter;
import org.eclipse.acceleo.common.utils.CompactLinkedHashSet;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.common.util.URI;
import org.osgi.framework.Bundle;
/**
* Eclipse-specific utilities for Acceleo services. It will be initialized with all services that could be
* parsed from the extension point if Eclipse is running and won't be used when outside of Eclipse.
*
* @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
*/
public final class AcceleoServicesEclipseUtil {
/** Services are cached within {@link AcceleoWorkspaceUtil}. This will only store their qualified names. */
private static final Set<String> REGISTERED_SERVICES = new CompactLinkedHashSet<String>();
/**
* If we need to load services that are not in the workspace, but instead in the plugins, we'll have to
* cache them separately.
*/
private static final Map<String, Class<?>> REGISTERED_CLASSES = new HashMap<String, Class<?>>();
/**
* Utility classes don't need a default constructor.
*/
private AcceleoServicesEclipseUtil() {
// hides constructor
}
/**
* This will clear the registry off all services that have been registered for evaluation.
*/
public static void clearRegistry() {
REGISTERED_SERVICES.clear();
}
/**
* This will return the singleton instance of the given class that serves as invocation source.
*
* @param serviceClass
* The class we need the service singleton of.
* @return The singleton instance of the given service class.
* @since 3.0
*/
public static Object getServiceInstance(Class<?> serviceClass) {
return AcceleoWorkspaceUtil.INSTANCE.getServiceInstance(serviceClass);
}
/**
* This will return an instance of class named <code>qualifiedName</code> loaded from the given bundle.
* This is fully equivalent to calling <code>bundle.loadClass(qualifiedName).newInstance()</code>.
* <p>
* As a result of this call, the service will be added to the list of registered services, allowing it to
* be retrieved through {@link #getRegisteredServices()} afterwards.
* </p>
*
* @param bundle
* The {@link Bundle} containing the acceleo file which tries to make use of a service name
* <code>qualifiedName</code>.
* @param qualifiedName
* Qualified name of the service we are looking for.
* @return An instance of the loaded service. Loaded services are stored as singleton instances.
*/
public static Class<?> registerService(Bundle bundle, String qualifiedName) {
Class<?> clazz = REGISTERED_CLASSES.get(qualifiedName);
if (clazz != null) {
return clazz;
}
try {
clazz = bundle.loadClass(qualifiedName);
if (clazz != null) {
REGISTERED_SERVICES.add(qualifiedName);
REGISTERED_CLASSES.put(qualifiedName, clazz);
}
} catch (ClassNotFoundException e) {
AcceleoCommonPlugin.log(AcceleoCommonMessages.getString("BundleClassLookupFailure", //$NON-NLS-1$
qualifiedName, bundle.getSymbolicName()), e, false);
}
return clazz;
}
/**
* This will refresh workspace contributions to Acceleo and load the given service if it is located in a
* workspace bundle.
*
* @param project
* The {@link IProject} containing the acceleo file which tries to make use of a service name
* <code>qualifiedName</code>.
* @param qualifiedName
* Qualified name of the service we are looking for.
* @return An instance of the loaded service. Loaded services are stored as singleton instances.
*/
public static Class<?> registerService(IProject project, String qualifiedName) {
final Class<?> clazz = AcceleoWorkspaceUtil.INSTANCE.getClass(project, qualifiedName);
if (clazz != null) {
REGISTERED_SERVICES.add(qualifiedName);
}
return clazz;
}
/**
* This will return an instance of class named <code>qualifiedName</code> loaded from the given bundle.
* This will first attempt to search through the workspace projects if one of them corresponds to this
* symbolic name, and will be fully equivalent to calling
* <code>Platform.getBundle(bundleName).loadClass(qualifiedName).newinstance()</code> otherwise.
*
* @param bundleName
* The symbolic name of the bundle {@link Bundle} containing the acceleo file which tries to
* make use of a service name <code>qualifiedName</code>.
* @param qualifiedName
* Qualified name of the service we are looking for.
* @return An instance of the loaded service. Loaded services are stored as singleton instances.
*/
public static Class<?> registerService(String bundleName, String qualifiedName) {
Class<?> clazz = REGISTERED_CLASSES.get(qualifiedName);
if (clazz != null) {
return clazz;
}
final IProject project = AcceleoWorkspaceUtil.getProject(bundleName);
if (project != null) {
clazz = registerService(project, qualifiedName);
} else {
clazz = registerService(Platform.getBundle(bundleName), qualifiedName);
}
return clazz;
}
/**
* This will return an instance of class named <code>qualifiedName</code> loaded from the bundle
* containing the file described by <code>uri</code>.
* <p>
* As a result of this call, the service will be added to the list of registered services, allowing it to
* be retrieved through {@link #getRegisteredServices()} afterwards.
* </p>
*
* @param uri
* URI of the module currently being evaluated. This will be used as a source to find the
* required service by looking through its dependencies.
* @param qualifiedName
* Qualified name of the service we are looking for.
* @return An instance of the loaded service. Loaded services are stored as singleton instances.
*/
public static Class<?> registerService(URI uri, String qualifiedName) {
Class<?> clazz = REGISTERED_CLASSES.get(qualifiedName);
if (clazz != null) {
return clazz;
}
if (uri.isPlatformPlugin()) {
final String bundleName = uri.segment(1);
final Bundle bundle = Platform.getBundle(bundleName);
if (bundle != null) {
clazz = registerService(bundle, qualifiedName);
}
} else {
final IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
String projectName = null;
if (uri.isPlatformResource()) {
projectName = uri.segment(1);
} else {
final URI workspaceRootURI = URI.createURI(URI.decode(workspaceRoot.getLocationURI()
.toString()));
URI workspaceRelative = uri.deresolve(workspaceRootURI);
if (!workspaceRelative.equals(uri)) {
// Have a look at the workspaceRelative uri to see if it starts with "../../"
workspaceRelative = trimDoubleDot(workspaceRelative);
projectName = workspaceRelative.segment(1);
}
}
if (projectName != null) {
final IProject project = workspaceRoot.getProject(projectName);
if (project != null && project.exists()) {
clazz = registerService(project, qualifiedName);
}
}
if (clazz == null) {
clazz = workspaceSuffixWorkaround(uri, qualifiedName);
}
}
// This is our last, most costly ... but most effective test
if (clazz == null) {
BundleURLConverter converter = new BundleURLConverter(uri.toString());
Bundle bundle = converter.resolveInBundle(qualifiedName);
if (bundle != null) {
clazz = registerService(bundle, qualifiedName);
}
}
if (clazz != null) {
REGISTERED_SERVICES.add(qualifiedName);
}
return clazz;
}
/**
* Trims the ".." at the beginning of the uri.
*
* @param workspaceRelative
* The given uri
* @return The uri without the ".." at the beginning.
*/
private static URI trimDoubleDot(URI workspaceRelative) {
int segmentToTrim = 0;
String[] segments = workspaceRelative.segments();
for (String segment : segments) {
if ("..".equals(segment)) { //$NON-NLS-1$
segmentToTrim++;
} else {
break;
}
}
if (segmentToTrim != 0 && workspaceRelative.segments().length > (segmentToTrim * 2)) {
String[] newSegments = new String[segments.length - (segmentToTrim * 2)];
System.arraycopy(segments, segmentToTrim * 2, newSegments, 0, segments.length
- (segmentToTrim * 2));
if (newSegments.length > 0) {
URI uriTmp = URI.createURI(newSegments[0]);
for (int i = 1; i < newSegments.length; i++) {
uriTmp = uriTmp.appendSegment(newSegments[i]);
}
return uriTmp;
}
}
return workspaceRelative;
}
/**
* This is a workaround for workspace with same suffix.
*
* @param uri
* URI of the module currently being evaluated. This will be used as a source to find the
* required service by looking through its dependencies.
* @param qualifiedName
* Qualified name of the service we are looking for.
* @return An instance of the loaded service. Loaded services are stored as singleton instances.
*/
private static Class<?> workspaceSuffixWorkaround(URI uri, String qualifiedName) {
Class<?> clazz = null;
String resolvedURI = AcceleoWorkspaceUtil.resolveAsPlatformPlugin(URI.decode(uri.toString()));
URI platformURI = null;
if (resolvedURI != null) {
platformURI = URI.createURI(resolvedURI);
}
if (platformURI != null) {
String bundleName = platformURI.segment(1);
Bundle bundle = Platform.getBundle(bundleName);
if (bundle != null) {
clazz = registerService(bundle, qualifiedName);
}
}
return clazz;
}
}