blob: 2ce2f388f7d95aefa8797aaf7155c922b79af0e0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014, 2017 TwelveTone LLC 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:
* Steven Spungin <steven@spungin.tv> - initial API and implementation, Bug 424730, Bug 435625, Bug 436133, Bug 436132,
* Bug 436283, Bug 436281, Bug 443510
* Fabian Miehe - Bug 440327
*******************************************************************************/
package org.eclipse.e4.tools.emf.ui.internal.common.resourcelocator;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.e4.tools.emf.ui.common.IClassContributionProvider;
import org.eclipse.e4.tools.emf.ui.common.IClassContributionProvider.ContributionData;
import org.eclipse.e4.tools.emf.ui.common.IModelElementProvider;
import org.eclipse.e4.tools.emf.ui.common.IProviderStatusCallback;
import org.eclipse.e4.tools.emf.ui.common.ProviderStatus;
import org.eclipse.e4.tools.emf.ui.common.ResourceSearchScope;
import org.eclipse.e4.tools.emf.ui.internal.common.ClassContributionCollector;
import org.eclipse.e4.tools.emf.ui.internal.common.component.dialogs.FilteredContributionDialog;
import org.eclipse.e4.tools.emf.ui.internal.common.component.tabs.empty.E;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.pde.core.plugin.IPluginBase;
import org.eclipse.pde.core.plugin.IPluginModelBase;
import org.eclipse.pde.internal.core.PDECore;
import org.eclipse.pde.internal.core.TargetPlatformHelper;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
/**
* A contribution collector encompassing the current target platform.<br />
* Uses filter for bundle, package, and location filtering.<br />
* This implementation finds resources based on file names, not by parsing file
* contents.
*
* @author Steven Spungin
*
*/
public abstract class TargetPlatformContributionCollector extends ClassContributionCollector {
CopyOnWriteArrayList<Entry> cacheEntry = new CopyOnWriteArrayList<>();
HashSet<String> cacheBundleId = new HashSet<>();
HashSet<String> cachePackage = new HashSet<>();
HashSet<String> cacheLocation = new HashSet<>();
private Pattern patternFile;
protected String cacheName;
protected boolean stopFiltering;
static class Entry {
String name;
String path;
String installLocation;
String relativePath;
String bundleSymName;
String pakage;
}
@SuppressWarnings("unused")
private TargetPlatformContributionCollector() {
}
protected TargetPlatformContributionCollector(String cacheName) {
this.cacheName = cacheName;
patternFile = getFilePattern();
addContributor(new IClassContributionProvider() {
@Override
public void findContribution(Filter filter, ContributionResultHandler handler) {
final Pattern patternName = Pattern.compile(filter.namePattern, Pattern.CASE_INSENSITIVE);
reloadCache(false, filter.getProviderStatusCallback());
int maxResults = filter.maxResults;
if (maxResults == 0) {
maxResults = 100;
}
int found = 0;
boolean more = false;
stopFiltering = false;
for (final Entry e : cacheEntry) {
if (stopFiltering) {
break;
}
final IProgressMonitor monitor = filter.getProgressMonitor();
if (monitor != null) {
if (monitor.isCanceled()) {
stopFiltering = true;
break;
}
monitor.subTask(Messages.TargetPlatformContributionCollector_Searching
+ " " + e.installLocation); //$NON-NLS-1$
}
if (E.notEmpty(filter.getBundles())) {
if (!filter.getBundles().contains(e.bundleSymName)) {
continue;
}
}
if (E.notEmpty(filter.getPackages())) {
if (!filter.getPackages().contains(e.pakage)) {
continue;
}
}
if (E.notEmpty(filter.getLocations())) {
boolean locationFound = false;
for (final String location : filter.getLocations()) {
if (e.installLocation.startsWith(location)) {
locationFound = true;
break;
}
}
if (!locationFound) {
continue;
}
}
if (filter.isIncludeNonBundles() == false) {
if (e.bundleSymName == null) {
continue;
}
}
if (filter.getSearchScope().contains(ResourceSearchScope.WORKSPACE)) {
if (filter.project != null) {
final IWorkspace workspace = filter.project.getWorkspace();
boolean fnd = false;
for (final IProject project : workspace.getRoot().getProjects()) {
// String path =
// project.getLocationURI().getPath();
final String path = project.getName();
if (e.installLocation.contains(path)) {
fnd = true;
break;
}
}
if (!fnd) {
continue;
}
}
}
final Matcher m = patternName.matcher(e.name);
if (m.find()) {
found++;
if (found > maxResults) {
more = true;
handler.moreResults(ContributionResultHandler.MORE_UNKNOWN, filter);
break;
}
handler.result(makeData(e));
}
}
if (!more) {
if (stopFiltering) {
handler.moreResults(ContributionResultHandler.MORE_CANCELED, filter);
} else {
handler.moreResults(0, filter);
}
}
}
});
addModelElementContributor(new IModelElementProvider() {
@Override
public void getModelElements(Filter filter, ModelResultHandler handler) {
// TODO Auto-generated method stub
}
@Override
public void clearCache() {
stopFiltering = true;
cacheEntry.clear();
cacheBundleId.clear();
cachePackage.clear();
cacheLocation.clear();
outputDirectories.clear();
}
});
}
protected ContributionData makeData(Entry e) {
// If class is in a java project, strip the source directory
// String path = e.path;// .replace("/", ".") + e.name;
// path = stripSourceDirectory(path, e.installLocation);
IPath ip = Path.fromOSString(e.path);
ip = ip.addTrailingSeparator().makeRelative();
ip = ip.append(e.name);
final String className = ip.toOSString().replace(File.separatorChar, '.');
final ContributionData data = new ContributionData(e.bundleSymName, className, "Java", e.installLocation); //$NON-NLS-1$
data.installLocation = e.installLocation;
data.resourceRelativePath = e.relativePath;
return data;
}
/**
*
* @return A copy of the bundle IDs in the cache.
*/
public Collection<String> getBundleIds() {
reloadCache(false, null);
return new ArrayList<>(cacheBundleId);
}
/**
*
* @return A copy of the bundle IDs in the cache.
*/
public Collection<String> getPackages() {
reloadCache(false, null);
return new ArrayList<>(cachePackage);
}
/**
*
* @return A copy of the bundle IDs in the cache.
*/
public Collection<String> getLocations() {
reloadCache(false, null);
return new ArrayList<>(cacheLocation);
}
/**
* Ensures the cache is loaded. By default it is loaded on first access, and
* kept static until forced to reloaded.
*
* @param force
* true to force reload the cache
* @param providerStatusCallback
*/
private void reloadCache(boolean force, final IProviderStatusCallback providerStatusCallback) {
if (cacheEntry.isEmpty() || force) {
if (providerStatusCallback != null) {
providerStatusCallback.onStatusChanged(ProviderStatus.INITIALIZING);
}
cacheEntry.clear();
cacheBundleId.clear();
cachePackage.clear();
cacheLocation.clear();
outputDirectories.clear();
final Job job = new Job(Messages.TargetPlatformContributionCollector_BuildTargetPlatformIndex) {
@Override
protected IStatus run(IProgressMonitor monitor) {
// load workspace projects
final IProject[] projects = PDECore.getWorkspace().getRoot().getProjects();
final IPluginModelBase[] models = TargetPlatformHelper.getPDEState().getTargetModels();
final int total = projects.length + models.length;
monitor.beginTask(Messages.TargetPlatformContributionCollector_updatingTargetPlatformCache
+ cacheName + ")", total); //$NON-NLS-1$
for (final IProject pj : projects) {
if (monitor.isCanceled()) {
break;
}
final String rootDirectory = pj.getLocation().toOSString();
monitor.subTask(rootDirectory);
monitor.worked(1);
TargetPlatformContributionCollector.this
.visit(monitor, FilteredContributionDialog.getBundle(rootDirectory), rootDirectory,
new File(rootDirectory));
}
// load target platform bundles
for (final IPluginModelBase pluginModelBase : models) {
monitor.subTask(pluginModelBase.getPluginBase().getId());
monitor.worked(1);
if (monitor.isCanceled()) {
break;
}
final IPluginBase pluginBase = pluginModelBase.getPluginBase();
if (pluginBase == null) {
// bundle = getBundle(new File())
continue;
}
URL url;
try {
final String installLocation = pluginModelBase.getInstallLocation();
if (installLocation.endsWith(".jar")) { //$NON-NLS-1$
url = new URL("file://" + installLocation); //$NON-NLS-1$
final ZipInputStream zis = new ZipInputStream(url.openStream());
while (true) {
final ZipEntry entry = zis.getNextEntry();
if (entry == null) {
break;
}
final String name2 = entry.getName();
if (shouldIgnore(name2)) {
continue;
}
final Matcher m = patternFile.matcher(name2);
if (m.matches()) {
final Entry e = new Entry();
e.installLocation = installLocation;
cacheLocation.add(installLocation);
e.name = m.group(2);
e.path = m.group(1);
if (e.path != null) {
e.pakage = e.path.replace("/", "."); //$NON-NLS-1$ //$NON-NLS-2$
if (e.pakage.startsWith(".")) { //$NON-NLS-1$
e.pakage = e.pakage.substring(1);
}
if (e.pakage.endsWith(".")) { //$NON-NLS-1$
e.pakage = e.pakage.substring(0, e.pakage.length() - 1);
}
} else {
e.pakage = ""; //$NON-NLS-1$
}
cachePackage.add(e.pakage);
e.bundleSymName = pluginBase.getId();
if (e.path == null) {
e.path = ""; //$NON-NLS-1$
}
cacheEntry.add(e);
cacheBundleId.add(pluginBase.getId());
//
// System.out.println(group
// + " -> "
// +
// m.group(2));
}
}
} else {
// not a jar file
final String bundle = getBundle(new File(installLocation));
if (bundle != null) {
visit(monitor, bundle, installLocation, new File(installLocation));
}
}
} catch (final MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (final IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
monitor.done();
if (monitor.isCanceled()) {
if (providerStatusCallback != null) {
providerStatusCallback.onStatusChanged(ProviderStatus.CANCELLED);
}
return Status.CANCEL_STATUS;
}
if (providerStatusCallback != null) {
providerStatusCallback.onStatusChanged(ProviderStatus.READY);
}
return Status.OK_STATUS;
}
};
job.schedule();
// User Job will not display dialog if called from a modal dialog,
// so we wrap a plain ol' job in a ProgressMonitorDialog
Display.getDefault().syncExec(new Runnable() {
boolean runInBackground = false;
@Override
public void run() {
final ProgressMonitorDialog dlg = new ProgressMonitorDialog(Display.getDefault().getActiveShell()) {
@Override
protected Control createContents(Composite parent) {
// TODO odd this is not a bean.
final Composite ret = (Composite) super.createContents(parent);
final Label label = new Label(ret, SWT.NONE);
label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false, 2, 1));
label.setText(Messages.TargetPlatformContributionCollector_pleaseWait);
return ret;
}
@Override
protected void createButtonsForButtonBar(Composite parent) {
final Button button = createButton(parent, 101,
Messages.TargetPlatformContributionCollector_RunInBackground, false);
// TODO JA
button.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
runInBackground = true;
}
});
super.createButtonsForButtonBar(parent);
// Do not use arrow cursor until calling super
// TODO ProgressMonitorDialog should encapsulate
// arrowCurson
button.setCursor(arrowCursor);
}
@Override
protected void cancelPressed() {
job.cancel();
}
};
try {
dlg.run(true, true, new IRunnableWithProgress() {
@Override
public void run(final IProgressMonitor monitor) throws InvocationTargetException,
InterruptedException {
monitor
.beginTask(
Messages.TargetPlatformContributionCollector_WaitingForTargetPlatformIndexingToComplete,
IProgressMonitor.UNKNOWN);
while (job.getState() == Job.RUNNING && !runInBackground) {
Thread.sleep(100);
}
monitor.done();
}
});
} catch (final InvocationTargetException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (final InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
});
}
}
// @Refactor
static public String getBundle(File file) {
if (file.isDirectory() == false) {
return null;
}
final File f = new File(file, "META-INF/MANIFEST.MF"); //$NON-NLS-1$
if (f.exists() && f.isFile()) {
try (final InputStream s = new FileInputStream(f);
BufferedReader r = new BufferedReader(new InputStreamReader(s))) {
String line;
while ((line = r.readLine()) != null) {
if (line.startsWith("Bundle-SymbolicName:")) { //$NON-NLS-1$
final int start = line.indexOf(':');
int end = line.indexOf(';');
if (end == -1) {
end = line.length();
}
return line.substring(start + 1, end).trim();
}
}
} catch (final IOException e) {
e.printStackTrace();
}
}
return null;
}
protected void visit(IProgressMonitor monitor, String bundleName, String installLocation, File file) {
for (final File fChild : file.listFiles()) {
if (monitor.isCanceled()) {
break;
}
if (fChild.isDirectory()) {
visit(monitor, bundleName, installLocation, fChild);
} else {
String name2 = fChild.getAbsolutePath().substring(installLocation.length() + 1);
name2 = stripOutputDirectory(name2, installLocation);
if (shouldIgnore(name2)) {
continue;
}
final Matcher m = patternFile.matcher(name2);
if (m.matches()) {
final Entry e = new Entry();
e.installLocation = installLocation;
cacheLocation.add(installLocation);
e.name = m.group(2);
if (e.name.contains("$")) { //$NON-NLS-1$
continue;
}
e.path = m.group(1);
if (e.path != null) {
e.pakage = e.path.replace("/", "."); //$NON-NLS-1$ //$NON-NLS-2$
if (e.pakage.startsWith(".")) { //$NON-NLS-1$
e.pakage = e.pakage.substring(1);
}
if (e.pakage.endsWith(".")) { //$NON-NLS-1$
e.pakage = e.pakage.substring(0, e.pakage.length() - 1);
}
} else {
e.pakage = ""; //$NON-NLS-1$
}
if (e.path == null) {
e.path = ""; //$NON-NLS-1$
}
e.relativePath = Path
.fromOSString(file.getAbsolutePath().replace(e.installLocation, "")).makeRelative().toOSString(); //$NON-NLS-1$
e.bundleSymName = bundleName;
// TODO we need project to strip source paths.
// e.pakage = e.pakage.replaceAll("^bin.", "");
cachePackage.add(e.pakage);
cacheEntry.add(e);
if (bundleName != null) {
cacheBundleId.add(bundleName);
}
}
}
}
}
static private String stripOutputDirectory(String path, String installLocation) {
if (installLocation.matches(".*\\.jar")) { //$NON-NLS-1$
return path;
}
for (final String sourceDirectory : getOutputDirectories(installLocation)) {
if (path.startsWith(sourceDirectory)) {
path = path.substring(sourceDirectory.length());
break;
}
}
return path;
}
/**
* A cache of the output directories for install locations (if install
* location has a classpath file with appropriate output entries)
*/
static private HashMap<String, List<String>> outputDirectories = new HashMap<>();
// Returns the Eclipse output directories for an install location. The
// directories are relative to the install location.
// <classpathentry kind="output" path="bin"/>
static private List<String> getOutputDirectories(String installLocation) {
List<String> ret = outputDirectories.get(installLocation);
if (ret == null) {
ret = new ArrayList<>();
outputDirectories.put(installLocation, ret);
try {
final Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
.parse(new File(installLocation + File.separator + ".classpath")); //$NON-NLS-1$
final XPath xp = XPathFactory.newInstance().newXPath();
final NodeList list = (NodeList) xp.evaluate(
"//classpathentry[@kind='output']/@path", doc, XPathConstants.NODESET); //$NON-NLS-1$
for (int i = 0; i < list.getLength(); i++) {
final String value = list.item(i).getNodeValue();
ret.add(value);
}
} catch (final Exception e) {
}
}
return ret;
}
protected boolean shouldIgnore(String name) {
return false;
}
protected abstract Pattern getFilePattern();
}