package org.jboss.tools.windup.ui.internal.services; import static org.jboss.tools.windup.model.domain.WindupMarker.CLASSIFICATION; import static org.jboss.tools.windup.model.domain.WindupMarker.CONFIGURATION_ID; import static org.jboss.tools.windup.model.domain.WindupMarker.DESCRIPTION; import static org.jboss.tools.windup.model.domain.WindupMarker.EFFORT; import static org.jboss.tools.windup.model.domain.WindupMarker.ELEMENT_ID; import static org.jboss.tools.windup.model.domain.WindupMarker.HINT; import static org.jboss.tools.windup.model.domain.WindupMarker.RULE_ID; import static org.jboss.tools.windup.model.domain.WindupMarker.SEVERITY; import static org.jboss.tools.windup.model.domain.WindupMarker.SOURCE_SNIPPET; import static org.jboss.tools.windup.model.domain.WindupMarker.TITLE; import static org.jboss.tools.windup.model.domain.WindupMarker.URI_ID; import static org.jboss.tools.windup.model.domain.WindupMarker.WINDUP_CLASSIFICATION_MARKER_ID; import static org.jboss.tools.windup.model.domain.WindupMarker.WINDUP_HINT_MARKER_ID; import static org.jboss.tools.windup.model.domain.WindupMarker.WINDUP_QUICKFIX_ID; import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.Map; import javax.annotation.PostConstruct; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.e4.core.di.annotations.Creatable; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.dialogs.ProgressMonitorDialog; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.actions.WorkspaceModifyOperation; import org.jboss.tools.windup.model.domain.ModelService; import org.jboss.tools.windup.model.domain.WindupMarker; import org.jboss.tools.windup.model.domain.WorkspaceResourceUtils; import org.jboss.tools.windup.ui.WindupUIPlugin; import org.jboss.tools.windup.ui.internal.Messages; import org.jboss.tools.windup.ui.internal.explorer.IssueExplorer; import org.jboss.tools.windup.ui.internal.explorer.MarkerUtil; import org.jboss.tools.windup.windup.Classification; import org.jboss.tools.windup.windup.ConfigurationElement; import org.jboss.tools.windup.windup.Hint; import org.jboss.tools.windup.windup.Input; import org.jboss.tools.windup.windup.Issue; import org.jboss.tools.windup.windup.MarkerElement; import org.jboss.tools.windup.windup.QuickFix; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; /** * IMarkers get cached at three points of time: * * case 1.) At Eclipse startup, if there was a previous execution of Windup, * we will read and cache all Windup markers from the workspace. * * case 2.) After Windup has been executed, any current markers from a previous * execution of Windup will be deleted, and the current cache will be * cleared. Then, all newly created markers resulting from the * execution of Windup will be cached. * * case 3.) Windup markers can be deleted or they can be deleted and then re-created * as a 'fixed' or 'stale' marker type. 'fixed' occurs when the user * either manually modifies the line of code associated with the marker, and * then marks the issue as fixed, or applies a quickfix associated with * the markers' hint. 'stale' occurs if the line of code corresponding to * markers' hint is changed and the markers' hint contains a quickfix. * In either case, those specific markers will be updated in the cache. * * Thoughts: * - What happens if a file is deleted? Should we update the cache? * * * The consequence of not updating the cache when a file is deleted is that * we will need to always check to see if the IMarker exists after getting it * from the cache. * * * - What happens if a file is edited? Should we listen for resource changes and update update the cache? * * * Lines of code that contain a migration issue could be edited or deleted, * in which case we should delete the marker if the line of code is deleted, * */ @Creatable @Singleton public class MarkerService { private BiMap<MarkerElement, IMarker> elementToMarkerMap = HashBiMap.create(); private BiMap<IMarker, MarkerElement> markerToElementMap = elementToMarkerMap.inverse(); private Multimap<IResource, MarkerElement> resourceElementsMap = ArrayListMultimap.create(); @Inject private ModelService modelService; private void cache(IMarker marker, MarkerElement element) { elementToMarkerMap.put(element, marker); resourceElementsMap.put(marker.getResource(), element); } public IMarker findMarker(EObject element) { return elementToMarkerMap.get(element); } public List<MarkerElement> find(IResource resource) { return Lists.newArrayList(resourceElementsMap.get(resource)); } @SuppressWarnings("unchecked") public <T extends MarkerElement> T find(IMarker marker) { return (T)markerToElementMap.get(marker); } private IssueExplorer getExplorer() { return (IssueExplorer)PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().findView(IssueExplorer.VIEW_ID); } private void notifyMarkersDeleted() { IssueExplorer explorer = getExplorer(); if (explorer != null) { Display.getDefault().asyncExec(() -> { explorer.clear(); }); } } private void notifyMarkersCreated() { IssueExplorer explorer = getExplorer(); if (explorer != null) { explorer.buildTree(); } } private void notifyDeleted(IMarker marker, EObject element) { if (element instanceof Issue) { IssueExplorer explorer = getExplorer(); if (explorer != null) { explorer.delete((Issue)element); } } } public void setFixed(Issue issue) { issue.setFixed(true); IMarker oldMarker = (IMarker)issue.getMarker(); IMarker fixedMarker = createMarker(issue, oldMarker.getResource()); try { fixedMarker.setAttributes(oldMarker.getAttributes()); fixedMarker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO); oldMarker.delete(); getExplorer().update(issue, oldMarker); } catch (CoreException e) { WindupUIPlugin.log(e); } } public void setStale(Issue issue) { issue.setStale(true); IMarker oldMarker = (IMarker)issue.getMarker(); IMarker fixedMarker = createMarker(issue, oldMarker.getResource()); try { fixedMarker.setAttributes(oldMarker.getAttributes()); fixedMarker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO); oldMarker.delete(); getExplorer().update(issue, oldMarker); } catch (CoreException e) { WindupUIPlugin.log(e); } } /** * Case 1. Upon Eclipse startup, load cache from previous execution of Windup. */ private void loadCache() { List<IMarker> markers = MarkerUtil.collectAllWindupMarkers(); for (IMarker marker : markers) { MarkerElement element = findElement(marker); if (element != null) { cache(marker, element); } else { deleteMarker(marker); } } } @SuppressWarnings("unchecked") private <T extends MarkerElement> T findElement(IMarker marker) { String elementUri = marker.getAttribute(WindupMarker.URI_ID, null); URI uri = URI.createURI(elementUri); return (T)modelService.getModel().eResource().getEObject(uri.fragment()); } public void delete(IMarker marker, EObject element) { elementToMarkerMap.remove(element); resourceElementsMap.remove(marker.getResource(), element); deleteMarker(marker); notifyDeleted(marker, element); } public void clear() { for (Map.Entry<MarkerElement, IMarker> entry : elementToMarkerMap.entrySet()) { deleteMarker(entry.getValue()); } elementToMarkerMap.clear(); resourceElementsMap.clear(); Display.getDefault().syncExec(() -> { notifyMarkersDeleted(); }); } public void clear(IResource resource) { List<MarkerElement> elements = Lists.newArrayList(resourceElementsMap.get(resource)); for (MarkerElement element : elements) { IMarker marker = findMarker(element); delete(marker, element); } } private void deleteMarker(IMarker marker) { try { marker.delete(); } catch (CoreException e) { WindupUIPlugin.log(e); } } /** * Creates the markers corresponding to provided ConfigurationElement. */ public void generateMarkersForConfiguration(ConfigurationElement configuration) { clear(); Display.getDefault().syncExec(() -> { try { WorkspaceModifyOperation op = new WorkspaceModifyOperation() { @Override protected void execute(IProgressMonitor monitor) throws CoreException, InvocationTargetException, InterruptedException { monitor.beginTask(Messages.generateIssues, getTotalMarkerCount(configuration)); monitor.subTask(Messages.generateIssues); createMarkers(configuration, monitor); } }; new ProgressMonitorDialog(Display.getDefault().getActiveShell()).run(false, false, op); notifyMarkersCreated(); } catch (InvocationTargetException | InterruptedException e) { Display.getDefault().syncExec(() -> { MessageDialog.openError(Display.getDefault().getActiveShell(), Messages.launchErrorTitle, Messages.markersCreateError); }); WindupUIPlugin.log(e); } }); } private void createMarkers(ConfigurationElement configuration, IProgressMonitor monitor) throws CoreException { int count = 0; for (Input input : configuration.getInputs()) { if (input.getWindupResult() != null) { for (Issue issue : input.getWindupResult().getIssues()) { IFile resource = WorkspaceResourceUtils.getResource(issue.getFileAbsolutePath()); if (resource == null) { WindupUIPlugin.logErrorMessage("MarkerService:: No resource associated with issue file: " + issue.getFileAbsolutePath()); //$NON-NLS-1$ continue; } createWindupMarker(issue, configuration, resource); monitor.worked(++count); } } } } /** * Helper method that actually creates the marker on the specified resource for the specified Windup migration issue. */ private void createWindupMarker(Issue issue, ConfigurationElement configuration, IResource resource) throws CoreException { IMarker marker = createMarker(issue, resource); issue.setMarker(marker); if (issue instanceof Hint) { Hint hint = (Hint)issue; marker.setAttribute(IMarker.MESSAGE, hint.getTitle()); marker.setAttribute(IMarker.LINE_NUMBER, hint.getLineNumber()); marker.setAttribute(TITLE, hint.getTitle()); marker.setAttribute(HINT, hint.getHint()); marker.setAttribute(IMarker.LINE_NUMBER, hint.getLineNumber()); //marker.setAttribute(IMarker.CHAR_START, hint.getColumn()); //marker.setAttribute(IMarker.CHAR_END, hint.getLength()); marker.setAttribute(SOURCE_SNIPPET, hint.getSourceSnippet()); } else { Classification classification = (Classification)issue; marker.setAttribute(IMarker.MESSAGE, classification.getClassification()); marker.setAttribute(CLASSIFICATION, classification.getClassification()); marker.setAttribute(DESCRIPTION, classification.getDescription()); marker.setAttribute(IMarker.LINE_NUMBER, 1); marker.setAttribute(IMarker.CHAR_START, 0); marker.setAttribute(IMarker.CHAR_END, 0); } IJavaElement element = JavaCore.create(resource); if (element != null) { marker.setAttribute(ELEMENT_ID, element.getHandleIdentifier()); } marker.setAttribute(URI_ID, EcoreUtil.getURI(issue).toString()); marker.setAttribute(CONFIGURATION_ID, configuration.getName()); marker.setAttribute(IMarker.SEVERITY, MarkerUtil.convertSeverity(issue.getSeverity())); marker.setAttribute(SEVERITY, issue.getSeverity()); marker.setAttribute(RULE_ID, issue.getRuleId()); marker.setAttribute(EFFORT, issue.getEffort()); marker.setAttribute(IMarker.USER_EDITABLE, false); createQuickfixMarkers(issue); } private void createQuickfixMarkers(Issue issue) throws CoreException { for (QuickFix quickfix : issue.getQuickFixes()) { IFile resource = WorkspaceResourceUtils.getResource(quickfix.getFile()); IMarker marker = createMarker(quickfix, resource); quickfix.setMarker(marker); marker.setAttribute(URI_ID, EcoreUtil.getURI(quickfix).toString()); } } public IMarker createMarker(MarkerElement element, IResource resource) { String type = ""; if (element instanceof Hint) { type = WINDUP_HINT_MARKER_ID; } else if (element instanceof Classification) { type = WINDUP_CLASSIFICATION_MARKER_ID; } else if (element instanceof QuickFix) { type = WINDUP_QUICKFIX_ID; } try { IMarker marker = resource.createMarker(type); cache(marker, element); return marker; } catch (CoreException e) { WindupUIPlugin.log(e); } return null; } /** * Returns the total number of markers that will be created. Used for reporting progress. */ private int getTotalMarkerCount(ConfigurationElement configuration) { int count = 0; for (Input input : configuration.getInputs()) { if (input.getWindupResult() != null) { for (Issue issue : input.getWindupResult().getIssues()) { count++; if (issue instanceof Hint) { Hint hint = (Hint)issue; count += hint.getQuickFixes().size(); } } } } return count; } @PostConstruct private void init() { loadCache(); } }