/******************************************************************************* * Copyright (c) 2010 Neil Bartlett. * 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: * Neil Bartlett - initial API and implementation *******************************************************************************/ package bndtools.views.resolution; import java.io.File; import java.text.MessageFormat; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.bndtools.core.ui.icons.Icons; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.IJobChangeEvent; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.jobs.JobChangeAdapter; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.ui.JavaUI; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.jface.util.LocalSelectionTransfer; import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; import org.eclipse.jface.viewers.IOpenListener; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.OpenEvent; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.DragSourceEvent; import org.eclipse.swt.dnd.DragSourceListener; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.Tree; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IPartListener; import org.eclipse.ui.ISelectionListener; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PartInitException; import org.eclipse.ui.ide.ResourceUtil; import org.eclipse.ui.part.ViewPart; import org.eclipse.ui.plugin.AbstractUIPlugin; import org.osgi.framework.namespace.HostNamespace; import org.osgi.framework.namespace.IdentityNamespace; import org.osgi.resource.Capability; import org.osgi.resource.Resource; import aQute.bnd.osgi.Clazz; import aQute.lib.io.IO; import bndtools.Plugin; import bndtools.model.repo.RepositoryResourceElement; import bndtools.model.resolution.CapReqMapContentProvider; import bndtools.model.resolution.CapabilityLabelProvider; import bndtools.model.resolution.RequirementWrapper; import bndtools.model.resolution.RequirementWrapperLabelProvider; import bndtools.tasks.AnalyseBundleResolutionJob; import bndtools.tasks.BndBuilderCapReqLoader; import bndtools.tasks.BndFileCapReqLoader; import bndtools.tasks.CapReqLoader; import bndtools.tasks.JarFileCapReqLoader; import bndtools.tasks.ResourceCapReqLoader; import bndtools.utils.PartAdapter; import bndtools.utils.SelectionUtils; public class ResolutionView extends ViewPart implements ISelectionListener, IResourceChangeListener { private static String[] FILTERED_CAPABILITY_NAMESPACES = { IdentityNamespace.IDENTITY_NAMESPACE, HostNamespace.HOST_NAMESPACE }; private Display display = null; private Tree reqsTree = null; private Table capsTable = null; private TreeViewer reqsViewer; private TableViewer capsViewer; private ViewerFilter hideSelfImportsFilter; private boolean inputLocked = false; private boolean outOfDate = false; Set<CapReqLoader> loaders; private Job analysisJob; private final Set<String> filteredCapabilityNamespaces; public ResolutionView() { filteredCapabilityNamespaces = new HashSet<String>(Arrays.asList(FILTERED_CAPABILITY_NAMESPACES)); loaders = Collections.emptySet(); } private final IPartListener partAdapter = new PartAdapter() { @Override public void partActivated(IWorkbenchPart part) { if (part == ResolutionView.this) { if (outOfDate) { executeAnalysis(); } } else if (part instanceof IEditorPart) { IEditorInput editorInput = ((IEditorPart) part).getEditorInput(); IFile file = ResourceUtil.getFile(editorInput); if (file != null) { CapReqLoader loader = getLoaderForFile(file.getLocation().toFile()); if (loader != null) { setLoaders(Collections.singleton(loader)); if (getSite().getPage().isPartVisible(ResolutionView.this)) { executeAnalysis(); } else { outOfDate = true; } } } } } }; private boolean setLoaders(Set<CapReqLoader> newLoaders) { Set<CapReqLoader> oldLoaders = loaders; boolean swap = !oldLoaders.equals(newLoaders); if (swap) { loaders = newLoaders; } for (CapReqLoader l : swap ? oldLoaders : newLoaders) { IO.close(l); } return swap; } private CapReqLoader getLoaderForFile(File file) { CapReqLoader loader; if (file.getName().toLowerCase().endsWith(".bnd")) { loader = new BndFileCapReqLoader(file); } else if (file.getName().toLowerCase().endsWith(".jar")) { loader = new JarFileCapReqLoader(file); } else { loader = null; } return loader; } @Override public void createPartControl(Composite parent) { this.display = parent.getDisplay(); SashForm splitPanel = new SashForm(parent, SWT.HORIZONTAL); splitPanel.setLayout(new FillLayout()); Composite reqsPanel = new Composite(splitPanel, SWT.NONE); reqsPanel.setBackground(parent.getBackground()); GridLayout reqsLayout = new GridLayout(1, false); reqsLayout.marginWidth = 0; reqsLayout.marginHeight = 0; reqsLayout.verticalSpacing = 2; reqsPanel.setLayout(reqsLayout); new Label(reqsPanel, SWT.NONE).setText("Requirements:"); reqsTree = new Tree(reqsPanel, SWT.FULL_SELECTION | SWT.MULTI | SWT.BORDER); reqsTree.setHeaderVisible(false); reqsTree.setLinesVisible(false); reqsTree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); reqsViewer = new TreeViewer(reqsTree); ColumnViewerToolTipSupport.enableFor(reqsViewer); reqsViewer.setLabelProvider(new RequirementWrapperLabelProvider(true)); reqsViewer.setContentProvider(new CapReqMapContentProvider()); Composite capsPanel = new Composite(splitPanel, SWT.NONE); capsPanel.setBackground(parent.getBackground()); GridLayout capsLayout = new GridLayout(1, false); capsLayout.marginWidth = 0; capsLayout.marginHeight = 0; capsLayout.verticalSpacing = 2; capsPanel.setLayout(capsLayout); new Label(capsPanel, SWT.NONE).setText("Capabilities:"); capsTable = new Table(capsPanel, SWT.FULL_SELECTION | SWT.MULTI | SWT.BORDER); capsTable.setHeaderVisible(false); capsTable.setLinesVisible(false); capsTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); capsViewer = new TableViewer(capsTable); ColumnViewerToolTipSupport.enableFor(capsViewer); capsViewer.setLabelProvider(new CapabilityLabelProvider(true)); capsViewer.setContentProvider(new CapReqMapContentProvider()); capsViewer.setFilters(new ViewerFilter[] { new ViewerFilter() { @Override public boolean select(Viewer viewer, Object parent, Object element) { return !filteredCapabilityNamespaces.contains(((Capability) element).getNamespace()); } } }); hideSelfImportsFilter = new ViewerFilter() { @Override public boolean select(Viewer viewer, Object parentElement, Object element) { if (element instanceof RequirementWrapper) { RequirementWrapper rw = (RequirementWrapper) element; return !rw.resolved; } return true; } }; reqsViewer.setFilters(new ViewerFilter[] { hideSelfImportsFilter }); reqsViewer.addDragSupport(DND.DROP_MOVE | DND.DROP_COPY, new Transfer[] { LocalSelectionTransfer.getTransfer() }, new LocalTransferDragListener(reqsViewer)); capsViewer.addDragSupport(DND.DROP_MOVE | DND.DROP_COPY, new Transfer[] { LocalSelectionTransfer.getTransfer() }, new LocalTransferDragListener(capsViewer)); reqsViewer.addOpenListener(new IOpenListener() { @Override public void open(OpenEvent event) { IStructuredSelection selection = (IStructuredSelection) event.getSelection(); for (Iterator< ? > iter = selection.iterator(); iter.hasNext();) { Object item = iter.next(); if (item instanceof Clazz) { Clazz clazz = (Clazz) item; String className = clazz.getFQN(); IType type = null; if (!loaders.isEmpty()) { IWorkspaceRoot wsroot = ResourcesPlugin.getWorkspace().getRoot(); for (CapReqLoader loader : loaders) { if (loader instanceof BndBuilderCapReqLoader) { File loaderFile = ((BndBuilderCapReqLoader) loader).getFile(); IFile[] wsfiles = wsroot.findFilesForLocationURI(loaderFile.toURI()); for (IFile wsfile : wsfiles) { IJavaProject javaProject = JavaCore.create(wsfile.getProject()); try { type = javaProject.findType(className); if (type != null) break; } catch (JavaModelException e) { ErrorDialog.openError(getSite().getShell(), "Error", "", new Status(IStatus.ERROR, Plugin.PLUGIN_ID, 0, MessageFormat.format("Error opening Java class '{0}'.", className), e)); } } } } } try { if (type != null) JavaUI.openInEditor(type, true, true); } catch (PartInitException e) { ErrorDialog.openError(getSite().getShell(), "Error", "", new Status(IStatus.ERROR, Plugin.PLUGIN_ID, 0, MessageFormat.format("Error opening Java editor for class '{0}'.", className), e)); } catch (JavaModelException e) { ErrorDialog.openError(getSite().getShell(), "Error", "", new Status(IStatus.ERROR, Plugin.PLUGIN_ID, 0, MessageFormat.format("Error opening Java class '{0}'.", className), e)); } } } } }); fillActionBars(); getSite().getPage().addPostSelectionListener(this); getSite().getPage().addPartListener(partAdapter); ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE); // Current selection & part IWorkbenchPart activePart = getSite().getPage().getActivePart(); ISelection activeSelection = getSite().getWorkbenchWindow().getSelectionService().getSelection(); selectionChanged(activePart, activeSelection); } void fillActionBars() { IAction toggleShowSelfImports = new Action("showSelfImports", IAction.AS_CHECK_BOX) { @Override public void runWithEvent(Event event) { if (isChecked()) { reqsViewer.removeFilter(hideSelfImportsFilter); } else { reqsViewer.addFilter(hideSelfImportsFilter); } } }; toggleShowSelfImports.setChecked(false); toggleShowSelfImports.setImageDescriptor(AbstractUIPlugin.imageDescriptorFromPlugin(Plugin.PLUGIN_ID, "/icons/package_folder_impexp.gif")); toggleShowSelfImports.setToolTipText("Show resolved requirements.\n\nInclude requirements that are resolved within the set of selected bundles."); IAction toggleLockInput = new Action("lockInput", IAction.AS_CHECK_BOX) { @Override public void runWithEvent(Event event) { inputLocked = isChecked(); if (!inputLocked) { executeAnalysis(); } } }; toggleLockInput.setChecked(false); toggleLockInput.setImageDescriptor(Icons.desc("lock")); toggleLockInput.setToolTipText("Lock to current selection"); IToolBarManager toolBarManager = getViewSite().getActionBars().getToolBarManager(); toolBarManager.add(toggleShowSelfImports); toolBarManager.add(toggleLockInput); } @Override public void setFocus() {} @Override public void dispose() { getSite().getPage().removeSelectionListener(this); ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); getSite().getPage().removePartListener(partAdapter); setLoaders(Collections.<CapReqLoader> emptySet()); super.dispose(); } public void setInput(Set<CapReqLoader> sourceLoaders, Map<String,List<Capability>> capabilities, Map<String,List<RequirementWrapper>> requirements) { setLoaders(sourceLoaders); sourceLoaders = loaders; if (reqsTree != null && !reqsTree.isDisposed() && capsTable != null && !capsTable.isDisposed()) { reqsViewer.setInput(requirements); capsViewer.setInput(capabilities); String label; if (!sourceLoaders.isEmpty()) { StringBuilder builder = new StringBuilder(); String delim = ""; boolean shortLabel = sourceLoaders.size() > 1; for (CapReqLoader l : sourceLoaders) { builder.append(delim); builder.append(shortLabel ? l.getShortLabel() : l.getLongLabel()); delim = ", "; } label = builder.toString(); } else { label = "<no input>"; } setContentDescription(label); } } @Override public void selectionChanged(IWorkbenchPart part, ISelection selection) { if (selection == null || !(selection instanceof IStructuredSelection)) return; Set<CapReqLoader> loaders = getLoadersFromSelection((IStructuredSelection) selection); if (setLoaders(loaders)) { if (getSite().getPage().isPartVisible(this)) { executeAnalysis(); } else { outOfDate = true; } } } private Set<CapReqLoader> getLoadersFromSelection(IStructuredSelection structSel) { Set<CapReqLoader> result = new LinkedHashSet<CapReqLoader>(); Iterator< ? > iter = structSel.iterator(); while (iter.hasNext()) { Object element = iter.next(); CapReqLoader loader = null; File file = SelectionUtils.adaptObject(element, File.class); if (file != null) { loader = getLoaderForFile(file); } else { IResource eresource = SelectionUtils.adaptObject(element, IResource.class); if (eresource != null) { IPath location = eresource.getLocation(); if (location != null) { loader = getLoaderForFile(location.toFile()); } } else if (element instanceof RepositoryResourceElement) { Resource resource = ((RepositoryResourceElement) element).getResource(); loader = new ResourceCapReqLoader(resource); } } if (loader != null) result.add(loader); } return result; } void executeAnalysis() { if (inputLocked) return; outOfDate = false; synchronized (this) { Job oldJob = analysisJob; if (oldJob != null && oldJob.getState() != Job.NONE) oldJob.cancel(); if (!loaders.isEmpty()) { final AnalyseBundleResolutionJob job = new AnalyseBundleResolutionJob("importExportAnalysis", loaders); job.setSystem(true); job.addJobChangeListener(new JobChangeAdapter() { @Override public void aboutToRun(IJobChangeEvent event) { if (display != null && !display.isDisposed()) { Runnable update = new Runnable() { @Override public void run() { setContentDescription("Working..."); } }; if (display.getThread() == Thread.currentThread()) update.run(); else display.asyncExec(update); } } @Override public void done(IJobChangeEvent event) { IStatus result = job.getResult(); if (result != null && result.isOK()) { if (display != null && !display.isDisposed()) display.asyncExec(new Runnable() { @Override public void run() { setInput(loaders, job.getCapabilities(), job.getRequirements()); } }); } } }); analysisJob = job; analysisJob.schedule(500); } else { analysisJob = null; } } } @Override public void resourceChanged(IResourceChangeEvent event) { if (!loaders.isEmpty()) { IWorkspaceRoot wsroot = ResourcesPlugin.getWorkspace().getRoot(); for (CapReqLoader loader : loaders) { if (loader instanceof BndBuilderCapReqLoader) { File file = ((BndBuilderCapReqLoader) loader).getFile(); IFile[] wsfiles = wsroot.findFilesForLocationURI(file.toURI()); for (IFile wsfile : wsfiles) { if (event.getDelta().findMember(wsfile.getFullPath()) != null) { executeAnalysis(); break; } } } } } } static class LocalTransferDragListener implements DragSourceListener { private final Viewer viewer; public LocalTransferDragListener(Viewer viewer) { this.viewer = viewer; } @Override public void dragStart(DragSourceEvent event) {} @Override public void dragSetData(DragSourceEvent event) { LocalSelectionTransfer transfer = LocalSelectionTransfer.getTransfer(); if (transfer.isSupportedType(event.dataType)) transfer.setSelection(viewer.getSelection()); } @Override public void dragFinished(DragSourceEvent event) {} } }