/*-
* Copyright © 2009 Diamond Light Source Ltd.
*
* This file is part of GDA.
*
* GDA is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License version 3 as published by the Free
* Software Foundation.
*
* GDA is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along
* with GDA. If not, see <http://www.gnu.org/licenses/>.
*/
package uk.ac.gda.common.rcp.util;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.resources.ICommand;
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.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.ISelectionService;
import org.eclipse.ui.IURIEditorInput;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IViewReference;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.FileStoreEditorInput;
import org.eclipse.ui.ide.ResourceUtil;
import org.eclipse.ui.part.FileEditorInput;
import uk.ac.gda.util.OSUtils;
import uk.ac.gda.util.beans.xml.URLResolver;
/**
* Eclipse utilities
*
*/
public class EclipseUtils {
/**
* Get the file path from a FileStoreEditorInput
*
* @param fileInput
* @return URI or null
*/
public static URI getFileURI(IEditorInput fileInput) {
if (fileInput instanceof IURIEditorInput) {
URI uri = ((IURIEditorInput)fileInput).getURI();
return uri;
}
return null;
}
/**
*
* @param fileInput
* @return File or null
*/
public static File getFile(IEditorInput fileInput) {
URI uri = getFileURI(fileInput);
return uri == null ? null : new File(uri);
}
/**
*
* @param fileInput
* @return path or null
*/
public static String getFilePath(IEditorInput fileInput) {
File f = getFile(fileInput);
return f == null || !f.exists() ? null : f.getPath();
}
/**
* Try to determine the IFile from the edit input
* @param input
* @return file
*/
public static IFile getIFile(IEditorInput input) {
if (input instanceof FileEditorInput) {
return ((FileEditorInput)input).getFile();
}
return (IFile)input.getAdapter(IFile.class);
}
/**
*
* @param input
* @return file name or null
*/
public static String getFileName(IEditorInput input) {
File f = getFile(input);
return f == null || !f.exists() ? null : f.getName();
}
/**
* @param bundleUrl
* @return bundleUrl
*/
public static URL getAbsoluteUrl(final URL bundleUrl) {
if (bundleUrl==null) return null;
if (bundleUrl.toString().startsWith("bundle"))
try {
return FileLocator.resolve(bundleUrl);
} catch (IOException e) {
return bundleUrl;
}
return bundleUrl;
}
/**
* Gets the page, even during startup.
* @return the page
*/
public static IWorkbenchPage getPage() {
IWorkbenchPage activePage = EclipseUtils.getActivePage();
if (activePage!=null) return activePage;
return EclipseUtils.getDefaultPage();
}
/**
* @return IWorkbenchPage
*/
public static IWorkbenchPage getActivePage() {
final IWorkbench bench = PlatformUI.getWorkbench();
if (bench==null) return null;
final IWorkbenchWindow window = bench.getActiveWorkbenchWindow();
if (window==null) return null;
return window.getActivePage();
}
/**
* @return IWorkbenchPage
*/
public static IEditorPart getActiveEditor() {
final IWorkbenchPage page = EclipseUtils.getPage();
return page.getActiveEditor();
}
/**
* @return IWorkbenchPage
*/
public static IWorkbenchPage getDefaultPage() {
final IWorkbench bench = PlatformUI.getWorkbench();
if (bench==null) return null;
final IWorkbenchWindow[] windows = bench.getWorkbenchWindows();
if (windows==null) return null;
return windows[0].getActivePage();
}
private static URLResolver resolver;
/**
* Returns a URLResolver to transform bundle urls
* to absolute.
*
* @return URLResolver
*/
public static URLResolver getUrlResolver() {
if (resolver==null) resolver = new URLResolver() {
@Override
public URL resolve(URL url) {
return EclipseUtils.getAbsoluteUrl(url);
}
};
return resolver;
}
/**
* Declare a builder id in a project, this is then called to build it.
* @param project
* @param id
* @throws CoreException
*/
public static void addBuilderToProject(IProject project, String id, IProgressMonitor monitor) throws CoreException {
if (!project.isOpen()) return;
IProjectDescription des = project.getDescription();
ICommand[] cmds = des.getBuildSpec();
for( ICommand cmd : cmds){
if (cmd.getBuilderName().equals(id)) return;
}
ICommand com = des.newCommand();
com.setBuilderName(id);
List<ICommand> coms = new ArrayList<ICommand>(cmds.length+1);
coms.addAll(Arrays.asList(cmds));
coms.add(com);
des.setBuildSpec(coms.toArray(new ICommand[0]));
project.setDescription(des, monitor);
}
/**
*
* @param project
* @param id
* @throws CoreException
*/
public static void removeBuilderFromProject(IProject project, String id, IProgressMonitor monitor) throws CoreException {
if (!project.isOpen()) return;
IProjectDescription des = project.getDescription();
ICommand[] cmds = des.getBuildSpec();
Vector<ICommand> newCmds = new Vector<ICommand>();
for( ICommand cmd : cmds){
if (!cmd.getBuilderName().equals(id))
newCmds.add(cmd);
}
des.setBuildSpec(newCmds.toArray(new ICommand[0]));
project.setDescription(des, monitor);
}
/**
* Checks of the id passed in == the current perspectives.
* @param id
* @return true if is
*/
public static boolean isActivePerspective(final String id) {
final IWorkbenchPage page = getActivePage();
if (page==null) return false;
try {
return id.equals(page.getPerspective().getId());
} catch (Exception ignored) {
return false;
}
}
/**
* Process UI input but do not return for the specified time interval.
*
* @param waitTimeMillis
* the number of milliseconds
*/
public static void delay(long waitTimeMillis) {
delay(waitTimeMillis, false);
}
/**
* Process UI input but do not return for the specified time interval.
*
* @param waitTimeMillis
* the number of milliseconds
* @param returnInsteadOfSleep
* Once there is nothing left to do return instead of sleep. In practice this means that async messages
* should be complete before this method returns (unless it times out first)
*/
public static void delay(long waitTimeMillis, boolean returnInsteadOfSleep) {
Display display = Display.getCurrent();
// If this is the UI thread,
// then process input.
if (display != null) {
long endTimeMillis = System.currentTimeMillis() + waitTimeMillis;
while (System.currentTimeMillis() < endTimeMillis) {
try {
if (!display.readAndDispatch()) {
if (returnInsteadOfSleep)
break;
display.sleep();
}
} catch (Exception ne) {
try {
if (returnInsteadOfSleep)
break;
Thread.sleep(waitTimeMillis);
} catch (InterruptedException e) {
// Ignored
}
break;
}
}
display.update();
}
// Otherwise, perform a simple sleep.
else {
try {
if (!returnInsteadOfSleep)
Thread.sleep(waitTimeMillis);
} catch (InterruptedException e) {
// Ignored.
}
}
}
/**
* Perform like a normal {@link Thread#join(long)} but process UI events while waiting for join
*
* @param thread
* Thread to "join" on
* @param waitTimeMillis
* the number of milliseconds
*/
public static void threadJoin(Thread thread, long waitTimeMillis) {
Display display = Display.getCurrent();
// If this is the UI thread,
// then process input.
if (display != null) {
long endTimeMillis = System.currentTimeMillis() + waitTimeMillis;
while (System.currentTimeMillis() < endTimeMillis && thread.isAlive()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.update();
}
// Otherwise, perform a simple join.
else {
try {
thread.join(waitTimeMillis);
} catch (InterruptedException e) {
// Ignored.
}
}
}
/**
* Gets a unique file. The file must have a parent of IFolder.
* @param file
* @return new file, not created.
*/
public static IFile getUniqueFile(IFile file, final String extension) {
return getUniqueFile(file, null, extension);
}
/**
* Gets a unique file. The file must have a parent of IFolder.
* @param file
* @return new file, not created.
*/
public static IFile getUniqueFile(IFile file, final String conjunctive, final String extension) {
final String name = file.getName();
final Matcher matcher = Pattern.compile("(.+)(\\d+)\\."+extension, Pattern.CASE_INSENSITIVE).matcher(name);
int start = 0;
String frag = name.substring(0,name.lastIndexOf("."));
if (matcher.matches()) {
frag = matcher.group(1);
start = Integer.parseInt(matcher.group(2));
}
if (conjunctive!=null) {
frag = frag+conjunctive;
}
// First try without a start position
final IContainer parent = file.getParent();
final IFile newFile;
if (parent instanceof IFolder) {
newFile = ((IFolder)parent).getFile(frag+"."+extension);
} else if (parent instanceof IProject) {
newFile = ((IProject)parent).getFile(frag+"."+extension);
} else {
newFile = null;
}
if (newFile!=null&&!newFile.exists()) return newFile;
return getUniqueFile(parent, frag, ++start, extension);
}
public static IFile getUniqueFile(IContainer parent, String filename, final String extension){
final Matcher matcher = Pattern.compile("(.+)(\\d+)\\."+extension, Pattern.CASE_INSENSITIVE).matcher(filename);
int start = 0;
String frag = filename.substring(0,filename.lastIndexOf("."));
if (matcher.matches()) {
frag = matcher.group(1);
start = Integer.parseInt(matcher.group(2));
}
return getUniqueFile(parent, frag, start, extension);
}
private static IFile getUniqueFile(IContainer parent, String frag, int start, final String extension) {
final IFile file;
if (parent instanceof IFolder) {
file = ((IFolder)parent).getFile(frag+start+"."+extension);
} else if (parent instanceof IProject) {
file = ((IProject)parent).getFile(frag+start+"."+extension);
} else {
throw new RuntimeException("The parent is neither a project nor a folder.");
}
if (!file.exists()) return file;
return getUniqueFile(parent, frag, ++start, extension);
}
private static final Pattern UNIQUE_PATTERN = Pattern.compile("(.+)(\\d+)", Pattern.CASE_INSENSITIVE);
public static String getUnique(IResource res) {
final String name = res.getName();
final Matcher matcher = UNIQUE_PATTERN.matcher(name);
int start = 0;
String frag = name.indexOf(".")>-1
? name.substring(0,name.lastIndexOf("."))
: name;
if (matcher.matches()) {
frag = matcher.group(1);
start = Integer.parseInt(matcher.group(2));
}
return getUnique(res.getParent(), frag, ++start);
}
private static String getUnique(IContainer parent, String frag, int start) {
final IFile file;
final IFolder folder;
if (parent instanceof IFolder) {
file = ((IFolder)parent).getFile(frag+start);
folder = ((IFolder)parent).getFolder(frag+start);
} else if (parent instanceof IProject) {
file = ((IProject)parent).getFile(frag+start);
folder = ((IProject)parent).getFolder(frag+start);
} else {
throw new RuntimeException("The parent is niether a project nor a folder.");
}
if (!file.exists()&&!folder.exists()) return file.getName();
return getUnique(parent, frag, ++start);
}
// Source code and JavaDoc adapted from org.eclipse.ui.internal.util.Util.getAdapter
/**
* If it is possible to adapt the given object to the given type, this
* returns the adapter. Performs the following checks:
*
* <ol>
* <li>Returns <code>sourceObject</code> if it is an instance of the
* adapter type.</li>
* <li>If sourceObject implements IAdaptable, it is queried for adapters.</li>
* <li>If sourceObject is not an instance of PlatformObject (which would have
* already done so), the adapter manager is queried for adapters</li>
* </ol>
*
* Otherwise returns null.
*
* @param sourceObject
* object to adapt, or null
* @param adapterType
* type to adapt to
* @return a representation of sourceObject that is assignable to the
* adapter type, or null if no such representation exists
*/
public static Object getAdapter(Object sourceObject, Class<?> adapterType) {
Assert.isNotNull(adapterType);
if (sourceObject == null) {
return null;
}
if (adapterType.isInstance(sourceObject)) {
return sourceObject;
}
return ResourceUtil.getAdapter(sourceObject, adapterType, true);
}
/**
* Opens an external editor on a file path
* @param file
* @throws PartInitException
*/
public static IEditorPart openEditor(IFile file) throws PartInitException {
final IWorkbenchPage page = EclipseUtils.getActivePage();
IEditorDescriptor desc = PlatformUI.getWorkbench().getEditorRegistry().getDefaultEditor(file.getName());
if (desc == null) desc = PlatformUI.getWorkbench().getEditorRegistry().getDefaultEditor(file.getName()+".txt");
return page.openEditor(new FileEditorInput(file), desc.getId());
}
/**
* Opens an external editor on a file path
* @param filename
* @throws PartInitException
*/
public static IEditorPart openExternalEditor(String filename) throws PartInitException {
final IWorkbenchPage page = EclipseUtils.getActivePage();
IEditorDescriptor desc = PlatformUI.getWorkbench().getEditorRegistry().getDefaultEditor(filename);
if (desc == null) desc = PlatformUI.getWorkbench().getEditorRegistry().getDefaultEditor(filename+".txt");
final IFileStore externalFile = EFS.getLocalFileSystem().fromLocalFile(new File(filename));
return page.openEditor(new FileStoreEditorInput(externalFile), desc.getId());
}
/**
* Returns the active project based on active selection
*/
public static IProject getActiveProject() {
final IWorkbenchPage page = EclipseUtils.getActivePage();
if (page==null) return null;
final IEditorPart activeEditor = page.getActiveEditor();
if (activeEditor!=null) {
final IEditorInput input = activeEditor.getEditorInput();
if (input instanceof FileEditorInput) {
return ((FileEditorInput)input).getFile().getProject();
}
}
final ISelectionService service = page.getWorkbenchWindow().getSelectionService();
final ISelection sel = service.getSelection();
if (!(sel instanceof IStructuredSelection)) return null;
final IStructuredSelection ss = (IStructuredSelection) sel;
final Object element = ss.getFirstElement();
if (element instanceof IResource) return ((IResource)element).getProject();
if (!(element instanceof IAdaptable)) return null;
IAdaptable adaptable = (IAdaptable)element;
Object adapter = adaptable.getAdapter(IResource.class);
return ((IResource)adapter).getProject();
}
/**
* Activate the view @ID if it exists, nothing if it does not
*/
@SuppressWarnings("unused")
public static void activateView(String ID){
IViewReference[] viewReferences = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage()
.getViewReferences();
boolean found = false;
for (IViewReference view : viewReferences) {
if (view.getId().equals(ID)) {
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage()
.activate(view.getView(true).getViewSite().getPart());
found = true;
break;
}
}
}
/**
* Thread Safe. This is a hard busy setter - it overrides everything and sets everything busy.
*
* Always use with try, finally. Only use when you are sure that there is no other alternative.
* For instance @see Job class.
*
* @param isBusy
*/
public static void setBusy(final boolean isBusy) {
final Display display = PlatformUI.getWorkbench().getDisplay();
if (display==null || display.isDisposed()) return;
display.syncExec(new Runnable() {
@Override
public void run() {
Cursor cursor = display.getSystemCursor(SWT.CURSOR_WAIT);
Shell[] shells = display.getShells();
for (int i = 0; i < shells.length; i++) {
if (isBusy) {
shells[i].setCursor(cursor);
} else {
shells[i].setCursor(null);
}
}
}
});
}
public static IViewPart findView(String id) {
IViewPart view = null;
IWorkbench wb = PlatformUI.getWorkbench();
for (IWorkbenchWindow win : wb.getWorkbenchWindows()) {
for (IWorkbenchPage page : win.getPages()) {
view = page.findView(id);
if (view != null) {
return view;
}
}
}
return null;
}
public static final String URI_SEPARATOR = "/";
public static final String PLATFORM_BUNDLE_PREFIX = "platform:/plugin/%s";
/**
* Removes the leading / from file path strings of the form "/α:/<some_path>" (where α is a windows drive letter)
* which are returned by the FileLocator.toFileURL method on Windows boxes.
*
* @param filePath A file path string
* @return The adjusted string, if running on a windows box otherwise filePath unchanged
*/
private static String sanitizeForWindows(final String filePath) {
return (OSUtils.isWindowsOS() && filePath.matches("/[a-zA-Z]{1}:/.*")) ? filePath.substring(1) : filePath;
}
/**
* Retrieves the file corresponding to an Eclipse OSGi Bundle Entry URL referencing a file within the bundle,
* such as is returned by the FileLocator.find method. The corresponding file path is resolved and adjusted for
* the leading slash before the drive letter in windows if necessary.
*
* @param fileURL An Eclipse OSGi format Bundle Entry URL
* @return The corresponding File object if it exists
* @throws IOException If the resolved path or supplied URL is invalid
*/
public static File resolveFileFromPlatformURL(final URL fileURL) throws IOException {
if (!fileURL.toExternalForm().contains("bundleentry://")) {
throw new IOException(String.format("The supplied URL %s is invalid", fileURL));
}
final String filePath = sanitizeForWindows(FileLocator.toFileURL(fileURL).getPath()); // if no corresponding file URL can be found, no conversion will
final File file = Paths.get(filePath).toFile(); // happen meaning file will not exist resulting in an exception
if( !(file).exists()) {
throw new IOException(String.format("Resolved bundle file path %s does not exist for %s.", file.getAbsolutePath(), filePath));
}
return file;
}
/**
* Retrieves a File object embodying the absolute path of a file within a bundle from the active Equinox context
* based on a partial path identifying the bundle and the relative path of the file within it. For instance to get
* the target.txt file within the org.example bundle, the path would be of the form org.example/target.txt. This
* allows correct paths to be retrieved regardless of whether the application was started from eclipse or an exported
* product build.
*
* @param bundleFilePath The path that identifies the bundle and the required file within it
* @return The corresponding File object resolved from the active Equinox context
* @throws IOException If the resolved file does not exist or cannot be resolved in the first place
*/
public static File resolveBundleFile(String bundleFilePath) throws IOException {
bundleFilePath = bundleFilePath.replace("\\", URI_SEPARATOR);
final URL fileURL = FileLocator.find(new URL(String.format(PLATFORM_BUNDLE_PREFIX, bundleFilePath)));
if (fileURL != null) {
return resolveFileFromPlatformURL(fileURL);
}
throw new IOException(String.format("Bundle file path %s not found", bundleFilePath));
}
/**
* Retrieves a File object embodying the absolute path of a folder within a bundle from the active Equinox context
* based on a partial path identifying the bundle and the relative path of the folder within it. For instance to get
* the target folder within the org.example bundle, the path would be of the form org.example/target. This allows
* correct paths to be retrieved regardless of whether the application was started from eclipse or an exported
* product build.
*
* @param bundleFolderPath The path that identifies the bundle and the required folder within it
* @return The corresponding File object resolved from the active Equinox context
* @throws IOException If the resolved folder is not actually a folder (i.e. it is some other type of file)
* or does not exist or cannot be resolved in the first place
*/
public static File resolveBundleFolderFile(final String bundleFolderPath) throws IOException {
final File folder = resolveBundleFile(bundleFolderPath);
if (!folder.isDirectory()) {
throw new IOException(String.format("Resolved bundle folder path %s for %s is not a folder.", folder.getAbsolutePath(), bundleFolderPath));
}
return folder;
}
}