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();
}
}