blob: 2a0e00745bcbd5a5d2c5d22466e9271ad4e00d11 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010-2019 Mia-Software and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Nicolas Bros (Mia-Software) - initial API and implementation
* Nicolas Guyomar (Mia-Software) - Bug 340339 - Need some Utils class for Folder/File/Project management
* Gregoire Dupe (Mia-Software) - Bug 340339 - Need some Utils class for Folder/File/Project management
* Nicolas Guyomar (Mia-Software) - Bug 340681 - Facet column implementation
* Nicolas Bros (Mia-Software) - Bug 380391 - PluginUtils#importPlugin should use the Bundle API
* Gregoire Dupe (Mia-Software) - Bug 408344 - [Releng] Deep folders cause build break
*******************************************************************************/
package org.eclipse.modisco.facet.util.pde.core.internal;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaConventions;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.modisco.facet.util.core.Logger;
import org.eclipse.modisco.facet.util.core.internal.exported.FileUtils;
import org.eclipse.modisco.facet.util.core.internal.exported.FolderUtils;
import org.eclipse.modisco.facet.util.core.internal.exported.IFilter;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.core.plugin.IExtensions;
import org.eclipse.pde.core.plugin.IPluginAttribute;
import org.eclipse.pde.core.plugin.IPluginElement;
import org.eclipse.pde.core.plugin.IPluginExtension;
import org.eclipse.pde.core.plugin.IPluginModelBase;
import org.eclipse.pde.core.plugin.IPluginObject;
import org.eclipse.pde.core.plugin.PluginRegistry;
import org.osgi.framework.Bundle;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
import org.xml.sax.SAXException;
import com.ibm.icu.lang.UCharacter;
public final class PluginUtils {
// This class has been copied from
// org.eclipse.modisco.facet.infra.common.core.internal.utils.PluginUtils
private PluginUtils() {
// utilities class
}
/**
* Returns whether the given file is registered in the plugin.xml of the
* given project, using the given extension point.
*
* @param extensionPoint
* the extension point that is used to register elements of this
* kind (elements must be declared with a "file" attribute)
*/
public static boolean isRegistered(final IFile elementFile, final String extensionPoint) {
final IProject project = elementFile.getProject();
final IPluginModelBase pluginModel = PluginRegistry.findModel(project);
boolean result = false;
if (pluginModel != null) {
final IExtensions extensions2 = pluginModel.getExtensions();
final IPluginExtension[] extensions = extensions2.getExtensions();
for (IPluginExtension pluginExtension : extensions) {
if (extensionPoint.equals(pluginExtension.getPoint())) {
final IPluginObject[] children = pluginExtension.getChildren();
for (IPluginObject child : children) {
if (child instanceof IPluginElement) {
final IPluginElement pluginElement = (IPluginElement) child;
final IPluginAttribute[] attributes = pluginElement.getAttributes();
for (IPluginAttribute pluginAttribute : attributes) {
if ("file".equalsIgnoreCase(pluginAttribute.getName())) { //$NON-NLS-1$
final String strFile = pluginAttribute.getValue();
if (strFile != null && strFile.length() > 0) {
final IFile file = project.getFile(strFile);
if (file.exists() && elementFile.equals(file)) {
result = true;
}
}
}
}
}
}
}
}
}
return result;
}
/**
* Registers the given file in the plugin.xml of the given project, using
* the given extension point.
*
* @param project
* the project in which the element should be registered
* @param extensionToCheck
* the file extension of the element that should be registered
* @param extensionPoint
* the extension point that is used to register elements of this
* kind (elements must be declared with a "file" attribute)
*/
/**
* Registers the given file in the plugin.xml of the given project, using
* the given extension point.
*
* @param file
* the file to register
* @param extensionPointId
* the extension point that is used to register the file
* @param elementName
* the name of the XML element in which an attribute named "file"
* will be set to the path of the file
* @throws ParserConfigurationException
* @throws IOException
* @throws SAXException
* @throws TransformerException
* @throws CoreException
*/
public static void register(final IFile file, final String extensionPointId,
final String elementName) {
if (isRegistered(file, extensionPointId)) {
return;
}
final IPath filePath = file.getFullPath().removeFirstSegments(1);
final IProject project = file.getProject();
final IFile pluginXML = project.getFile("plugin.xml"); //$NON-NLS-1$
if (pluginXML.exists()) {
final DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder;
try {
docBuilder = docFactory.newDocumentBuilder();
final String pluginXmlLoc = pluginXML.getLocation().toOSString();
final Document doc = docBuilder.parse(pluginXmlLoc);
final Element root = doc.getDocumentElement();
final Text whitespace = doc.createTextNode(" "); //$NON-NLS-1$
root.appendChild(whitespace);
final Node extensionNode = doc.createElement("extension"); //$NON-NLS-1$
final Attr pointAttr = doc.createAttribute("point"); //$NON-NLS-1$
pointAttr.setValue(extensionPointId);
extensionNode.getAttributes().setNamedItem(pointAttr);
root.appendChild(extensionNode);
final Node elementNode = doc.createElement(elementName);
final Attr fileAttr = doc.createAttribute("file"); //$NON-NLS-1$
fileAttr.setValue(filePath.toString());
elementNode.getAttributes().setNamedItem(fileAttr);
extensionNode.appendChild(elementNode);
final TransformerFactory trFactory = TransformerFactory.newInstance();
final int indent = 3;
trFactory.setAttribute("indent-number", Integer.valueOf(indent)); //$NON-NLS-1$
final Transformer transformer = trFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); //$NON-NLS-1$
transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
final StreamResult result = new StreamResult(new StringWriter());
final DOMSource source = new DOMSource(doc);
transformer.transform(source, result);
final String xmlString = result.getWriter().toString();
final byte[] byteArray = xmlString.getBytes("UTF-8"); //$NON-NLS-1$
pluginXML.setContents(new ByteArrayInputStream(byteArray), true, true,
new NullProgressMonitor());
} catch (Exception e) {
Logger.logError(e, Activator.getDefault());
}
} else {
try {
// create plugin.xml
final String template = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" //$NON-NLS-1$
+ "<?eclipse version=\"3.4\"?>\n" + "<plugin>\n" //$NON-NLS-1$ //$NON-NLS-2$
+ " <extension point=\"{0}\">\n" + " <{1} file=\"{2}\"/>\n" //$NON-NLS-1$ //$NON-NLS-2$
+ " </extension>\n" //$NON-NLS-1$
+ "</plugin>\n"; //$NON-NLS-1$
final String content = NLS.bind(template, new Object[] { extensionPointId, elementName,
filePath.toString() });
byte[] byteArray;
byteArray = content.getBytes("UTF-8"); //$NON-NLS-1$
pluginXML.create(new ByteArrayInputStream(byteArray), true,
new NullProgressMonitor());
try {
BuildPropertiesUtils.addToBuild(pluginXML);
} catch (Exception e) {
Logger.logError(e, "Error adding file " + pluginXML.getFullPath() //$NON-NLS-1$
+ " to the build.properties", null); //$NON-NLS-1$
}
} catch (Exception e) {
Logger.logError(e, Activator.getDefault());
}
}
}
private static final String JAVA_VERSION = "JavaSE-1.8"; //$NON-NLS-1$
private static final String ACTIVATOR_NAME = "Activator"; //$NON-NLS-1$
private static final String MANIFEST_MF_TPL = "resources/MANIFEST.MF.template"; //$NON-NLS-1$
private static final String ACTIVATOR_TPL = "resources/Activator.java.template"; //$NON-NLS-1$
private static final String PDE_CLASSPATH_ID = "org.eclipse.pde.core.requiredPlugins"; //$NON-NLS-1$
private static final String PLUGIN_NATURE = "org.eclipse.pde.PluginNature"; //$NON-NLS-1$
private static void addPdeClassPath(final IProject project)
throws JavaModelException {
final IJavaProject javaProject = JavaCore.create(project);
final IClasspathEntry[] oldClassPath = javaProject.getRawClasspath();
for (IClasspathEntry classpathEntry : oldClassPath) {
if (classpathEntry.getPath().equals(new Path(PluginUtils.PDE_CLASSPATH_ID))) {
return;
}
}
final IClasspathEntry[] newClassPath = new IClasspathEntry[oldClassPath.length + 1];
System.arraycopy(oldClassPath, 0, newClassPath, 0, oldClassPath.length);
newClassPath[oldClassPath.length] = JavaCore
.newContainerEntry(new Path(PluginUtils.PDE_CLASSPATH_ID));
javaProject.setRawClasspath(newClassPath, new NullProgressMonitor());
}
/**
* @author Gregoire Dupe (Mia-Software) - Removing "Require-Bundle"
* statement
* @throws IOException
*/
private static void createManifest(final IProject project)
throws CoreException, IOException {
final IFolder folder = project.getFolder("META-INF"); //$NON-NLS-1$
if (!folder.exists()) {
folder.create(true, true, new NullProgressMonitor());
}
final IFile manifestFile = folder.getFile("MANIFEST.MF"); //$NON-NLS-1$
if (!manifestFile.exists()) {
String template = FileUtils.getFileContents(Activator.getDefault()
.getBundle(), PluginUtils.MANIFEST_MF_TPL);
template = template.replace("{projectName}", project.getName()); //$NON-NLS-1$
final String packageName = PluginUtils
.stringToValidPackageName(project.getName());
template = template.replace("{packageName}", packageName); //$NON-NLS-1$
final String activator = PluginUtils
.bundleActivatorQualifiedName(packageName);
template = template.replace("{activator}", activator); //$NON-NLS-1$
template = template.replace("{javaVersion}", //$NON-NLS-1$
PluginUtils.JAVA_VERSION);
final InputStream source = new ByteArrayInputStream(
template.getBytes());
manifestFile.create(source, true, new NullProgressMonitor());
}
}
private static String bundleActivatorQualifiedName(final String packageName) {
return packageName + '.' + PluginUtils.ACTIVATOR_NAME;
}
private static void createActivator(final IProject project)
throws CoreException, IOException {
final String packageName = PluginUtils.stringToValidPackageName(project
.getName());
final String qualifiedName = PluginUtils
.bundleActivatorQualifiedName(packageName);
final String path = qualifiedName.replaceAll("\\.", "/"); //$NON-NLS-1$ //$NON-NLS-2$
final IFile activatorFile = project
.getFile(new Path("src").append(path).addFileExtension("java")); //$NON-NLS-1$ //$NON-NLS-2$
FolderUtils.createFolder((IFolder) activatorFile.getParent());
if (!activatorFile.exists()) {
final String template = FileUtils.getFileContents(Activator
.getDefault().getBundle(), PluginUtils.ACTIVATOR_TPL);
final String activatorContents = template.replace("{0}", packageName); //$NON-NLS-1$
final InputStream source = new ByteArrayInputStream(
activatorContents.getBytes());
activatorFile.create(source, true, new NullProgressMonitor());
}
}
/** Transform the given name into a valid package and bundle name */
private static String stringToValidPackageName(final String name) {
final StringBuilder builder = new StringBuilder();
char prev = ' ';
for (int i = 0; i < name.length(); i++) {
final char character = name.charAt(i);
if (character >= 'a' && character <= 'z' || character >= 'A'
&& character <= 'Z' || character == '_') {
builder.append(character);
prev = character;
} else if (character >= '0' && character <= '9') {
if (builder.length() == 0 || prev == '.') {
builder.append("_"); //$NON-NLS-1$
}
builder.append(character);
prev = character;
} else if (character == '.') {
if (prev == '.') {
continue;
}
if (builder.length() == 0 || prev >= '0' && prev <= '9') {
builder.append("_"); //$NON-NLS-1$
}
builder.append(character);
prev = character;
} else {
builder.append("_"); //$NON-NLS-1$
}
}
String result = builder.toString();
// first letter to lowercase
if (result.length() > 0 && UCharacter.isUpperCase(result.charAt(0))) {
result = UCharacter.toLowerCase(result.charAt(0))
+ result.substring(1);
}
final IStatus status = JavaConventions.validatePackageName(result,
JavaCore.VERSION_1_5, JavaCore.VERSION_1_5);
if (!status.isOK()) {
Logger.logWarning(
"Couldn't make valid package name from project name: " //$NON-NLS-1$
+ status.getMessage(), Activator.getDefault());
result = name;
}
return result;
}
private static void addPdeNature(final IProject project)
throws CoreException {
final String pluginNature = PluginUtils.PLUGIN_NATURE;
final IProjectDescription description = project.getDescription();
final String[] natures = description.getNatureIds();
if (!Arrays.asList(natures).contains(pluginNature)) {
String[] newNatures = new String[natures.length + 1];
System.arraycopy(natures, 0, newNatures, 0, natures.length);
newNatures[natures.length] = pluginNature;
description.setNatureIds(newNatures);
project.setDescription(description, new NullProgressMonitor());
}
}
/**
* This method transforms a Java project into a plug-in project by creating
* a MANIFEST.MF and an activator and by adding a the PDE nature and the PDE
* classpath.
*
* @param project
* The project to transform in an plug-in project
* @throws CoreException
* @throws IOException
*/
public static void configureAsPluginProject(final IProject project)
throws CoreException, IOException {
PluginUtils.addPdeNature(project);
// PDE builders are automatically added when the PDE nature is added
PluginUtils.addPdeClassPath(project);
PluginUtils.createManifest(project);
PluginUtils.createActivator(project);
}
/**
* This method returns true if the project is a plug-in project.
*
* @param project
* @return True if the project is a plug-in project.
* @throws CoreException
*/
public static boolean isPluginProject(final IProject project)
throws CoreException {
boolean result = false;
if (project.isAccessible()) {
result = project.getNature(PluginUtils.PLUGIN_NATURE) != null;
}
return result;
}
/**
* This method returns true if the path refers a file or a folder contained
* in a plug-in project.
*
* @param path
* @return True if the path refers a file or a folder contained in a plug-in
* project.
* @throws CoreException
*/
public static boolean isInPluginProject(final IPath path)
throws CoreException {
IProject project;
if (path.segmentCount() == 1) {
project = ResourcesPlugin.getWorkspace().getRoot()
.getProject(path.segment(0));
} else {
final IFolder folder = ResourcesPlugin.getWorkspace().getRoot()
.getFolder(path);
project = folder.getProject();
}
return isPluginProject(project);
}
/**
* This method imports a plug-in in the workspace.
*
* @param bundle
* the bundle to import into a new project
* @return the created project
* @throws CoreException
* in case of error
*/
public static IProject importPlugin(final Bundle bundle) throws CoreException {
return PluginUtils.importPlugin(bundle, new IFilter<String>() {
public boolean filter(final String fileName) {
return true;
}
});
}
/**
* This method imports a plug-in in the workspace.
*
* @param bundle
* the bundle to import into a new project
* @param filter
* can be used to filter out files or folders from the import
* @return the created project
* @throws CoreException
* in case of error
*/
public static IProject importPlugin(final Bundle bundle, final IFilter<String> filter) throws CoreException {
final IProject[] project = new IProject[1];
final IWorkspaceRunnable workspaceRunnable = new IWorkspaceRunnable() {
public void run(final IProgressMonitor monitor) throws CoreException {
project[0] = internalImportPlugin(bundle, filter);
}
};
ResourcesPlugin.getWorkspace().run(workspaceRunnable, new NullProgressMonitor());
return project[0];
}
/**
* This method imports a plug-in in the workspace.
*
* @param bundle
* the bundle to import into a new project
* @param filter
* can be used to filter out files or folders from the import
* @return the created project
* @throws CoreException
* in case of error
*/
protected static IProject internalImportPlugin(final Bundle bundle, final IFilter<String> filter) throws CoreException {
final IProject project = createProjectWithUniqueName(bundle.getSymbolicName());
final List<IStatus> errors = new ArrayList<IStatus>();
final List<URL> urls = getURLsToCopy(bundle, project,
"/", filter, errors); //$NON-NLS-1$
for (URL url : urls) {
copyUrlToFile(project, errors, url);
}
handleErrors(errors);
project.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
project.build(IncrementalProjectBuilder.CLEAN_BUILD, new NullProgressMonitor());
project.build(IncrementalProjectBuilder.FULL_BUILD, new NullProgressMonitor());
return project;
}
private static void copyUrlToFile(final IProject project,
final List<IStatus> errors, final URL url) throws CoreException {
try {
final InputStream inputStream = url.openStream();
final String strSubpath = url.toString().replaceAll(
"bundleentry://[^/]*/", ""); //$NON-NLS-1$ //$NON-NLS-2$
final IFile file = project.getFile(new Path(strSubpath));
if (file.exists()) {
file.delete(true, new NullProgressMonitor());
}
final IContainer parent = file.getParent();
if ((!parent.exists()) && parent instanceof IFolder) {
createDir((IFolder) parent);
}
file.create(inputStream, true, new NullProgressMonitor());
inputStream.close();
} catch (FileNotFoundException e) {
/*
* gdupe> We ignore files removed during the execution of
* internalImportPlugin.
*/
String message;
try {
message = String.format("Ignoring the missing file %s.", //$NON-NLS-1$
url.toURI());
} catch (URISyntaxException e1) {
message = String.format("Ignoring the missing file."); //$NON-NLS-1$
}
Logger.logError(message, Activator.getDefault());
} catch (IOException e) {
final Bundle localBundle = Activator.getDefault().getBundle();
final String symbolicName = localBundle.getSymbolicName();
final Status status = new Status(IStatus.ERROR, symbolicName,
e.getMessage(), e);
errors.add(status);
}
}
private static void createDir(final IFolder folder) throws CoreException {
final IContainer parent = folder.getParent();
if ((!parent.exists()) && parent instanceof IFolder) {
createDir((IFolder) parent);
}
folder.create(true, true, new NullProgressMonitor());
}
protected static void handleErrors(final List<IStatus> errors) throws CoreException {
if (!errors.isEmpty()) {
final IStatus[] statusArray = errors.toArray(new IStatus[errors.size()]);
final IStatus status = new MultiStatus(Activator.getDefault().getBundle().getSymbolicName(),
IStatus.ERROR, statusArray, "Errors importing project", new Exception()); //$NON-NLS-1$
throw new CoreException(status);
}
}
private static List<URL> getURLsToCopy(final Bundle bundle,
final IProject project, final String path,
final IFilter<String> filter, final List<IStatus> errors) {
final List<URL> result = new ArrayList<URL>();
final Enumeration<?> entryPaths = bundle.getEntryPaths(path);
while (entryPaths != null && entryPaths.hasMoreElements()) {
final Object nextElement = entryPaths.nextElement();
if (nextElement instanceof String) {
final String strSubpath = (String) nextElement;
if (filter.filter(strSubpath)) {
// directory
if (strSubpath.endsWith("/")) { //$NON-NLS-1$
final List<URL> fromSubFolder = getURLsToCopy(bundle,
project, strSubpath, filter, errors);
result.addAll(fromSubFolder);
} else {
final URL url = bundle.getEntry(strSubpath);
if (url != null) {
result.add(url);
}
}
}
}
}
return result;
}
protected static IProject createProjectWithUniqueName(final String baseName) throws CoreException {
final IWorkspace workspace = ResourcesPlugin.getWorkspace();
final IWorkspaceRoot root = workspace.getRoot();
IProject project = root.getProject(baseName);
if (project.exists()) {
int version = 1;
final int maxIter = 100;
while (project.exists() && version < maxIter) {
final String uniqueName = baseName + " (" + version + ')'; //$NON-NLS-1$
project = root.getProject(uniqueName);
version++;
}
}
project.create(new NullProgressMonitor());
project.open(new NullProgressMonitor());
return project;
}
}