/** * <copyright> * </copyright> * * */ package org.dresdenocl.language.ocl.resource.ocl.mopp; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.impl.BasicEObjectImpl; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.util.EcoreValidator; /** * Helper class to add markers to text files based on EMF's * <code>ResourceDiagnostic</code>. If a resource contains * <code>org.dresdenocl.language.ocl.resource.ocl.IOclTextDiagnostic</code>s it * uses the more precise information of this extended diagnostic type. */ public class OclMarkerHelper { /** * The extension id of the custom marker type that is used by this text resource. */ public static final String MARKER_TYPE = org.dresdenocl.language.ocl.resource.ocl.mopp.OclPlugin.PLUGIN_ID + ".problem"; /** * The total number of markers per file is restricted with this constant. * Restriction is needed because the performance of Eclipse decreases drastically * if large amounts of markers are added to files. */ public static int MAXIMUM_MARKERS = 500; /** * We use a queue to aggregate commands that create or remove markers. This is * basically for performance reasons. Without the queue we would need to create a * job for each marker creation/removal, which creates tons of threads and takes * very long time. */ private final static MarkerCommandQueue COMMAND_QUEUE = new MarkerCommandQueue(); public static class MutexRule implements ISchedulingRule { public boolean isConflicting(ISchedulingRule rule) { return rule == this; } public boolean contains(ISchedulingRule rule) { return rule == this; } } private static class MarkerCommandQueue { private List<org.dresdenocl.language.ocl.resource.ocl.IOclCommand<Object>> commands = new ArrayList<org.dresdenocl.language.ocl.resource.ocl.IOclCommand<Object>>(); private MutexRule schedulingRule = new MutexRule(); public void addCommand(org.dresdenocl.language.ocl.resource.ocl.IOclCommand<Object> command) { synchronized(commands) { commands.add(command); // we only need to schedule a job, if the queue was empty if (commands.size() == 1) { scheduleRunCommandsJob(); } } } private void scheduleRunCommandsJob() { Job job = new Job(org.dresdenocl.language.ocl.resource.ocl.OclResourceBundle.UPDATING_MARKERS_JOB_NAME) { @Override protected IStatus run(IProgressMonitor monitor) { runCommands(); return Status.OK_STATUS; } }; job.setRule(schedulingRule); job.schedule(); } public void runCommands() { List<org.dresdenocl.language.ocl.resource.ocl.IOclCommand<Object>> commandsToProcess = new ArrayList<org.dresdenocl.language.ocl.resource.ocl.IOclCommand<Object>>(); synchronized(commands) { commandsToProcess.addAll(commands); commands.clear(); } for (org.dresdenocl.language.ocl.resource.ocl.IOclCommand<Object> command : commandsToProcess) { command.execute(null); } } } /** * <p> * Creates a marker from the given diagnostics object and attaches the marker to * the resource. Markers are created and removed asynchronously. Thus, they may * not appear when calls to this method return. But, the order of marker additions * and removals is preserved. * </p> * * @param resource The resource that is the file to mark. * @param diagnostic The diagnostic with information for the marker. */ public void mark(Resource resource, org.dresdenocl.language.ocl.resource.ocl.IOclTextDiagnostic diagnostic) { final IFile file = getFile(resource); if (file == null) { return; } createMarkerFromDiagnostic(file, diagnostic); } protected void createMarkerFromDiagnostic(final IFile file, final org.dresdenocl.language.ocl.resource.ocl.IOclTextDiagnostic diagnostic) { final org.dresdenocl.language.ocl.resource.ocl.IOclProblem problem = diagnostic.getProblem(); org.dresdenocl.language.ocl.resource.ocl.OclEProblemType problemType = problem.getType(); final String markerID = getMarkerID(problemType); COMMAND_QUEUE.addCommand(new org.dresdenocl.language.ocl.resource.ocl.IOclCommand<Object>() { public boolean execute(Object context) { try { // if there are too many markers, we do not add new ones if (file.findMarkers(markerID, false, IResource.DEPTH_ZERO).length >= MAXIMUM_MARKERS) { return true; } IMarker marker = file.createMarker(markerID); if (problem.getSeverity() == org.dresdenocl.language.ocl.resource.ocl.OclEProblemSeverity.ERROR) { marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR); } else { marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_WARNING); } marker.setAttribute(IMarker.MESSAGE, diagnostic.getMessage()); org.dresdenocl.language.ocl.resource.ocl.IOclTextDiagnostic textDiagnostic = (org.dresdenocl.language.ocl.resource.ocl.IOclTextDiagnostic) diagnostic; marker.setAttribute(IMarker.LINE_NUMBER, textDiagnostic.getLine()); marker.setAttribute(IMarker.CHAR_START, textDiagnostic.getCharStart()); marker.setAttribute(IMarker.CHAR_END, textDiagnostic.getCharEnd() + 1); if (diagnostic instanceof org.dresdenocl.language.ocl.resource.ocl.mopp.OclResource.ElementBasedTextDiagnostic) { EObject element = ((org.dresdenocl.language.ocl.resource.ocl.mopp.OclResource.ElementBasedTextDiagnostic) diagnostic).getElement(); String elementURI = getObjectURI(element); if (elementURI != null) { marker.setAttribute(EcoreValidator.URI_ATTRIBUTE, elementURI); } } Collection<org.dresdenocl.language.ocl.resource.ocl.IOclQuickFix> quickFixes = textDiagnostic.getProblem().getQuickFixes(); Collection<Object> sourceIDs = new ArrayList<Object>(); if (quickFixes != null) { for (org.dresdenocl.language.ocl.resource.ocl.IOclQuickFix quickFix : quickFixes) { if (quickFix != null) { sourceIDs.add(quickFix.getContextAsString()); } } } if (!sourceIDs.isEmpty()) { marker.setAttribute(IMarker.SOURCE_ID, org.dresdenocl.language.ocl.resource.ocl.util.OclStringUtil.explode(sourceIDs, "|")); } } catch (CoreException ce) { handleException(ce); } return true; } }); } /** * <p> * Removes all markers from the given resource regardless of their type. Markers * are created and removed asynchronously. Thus, they may not appear when calls to * this method return. But, the order of marker additions and removals is * preserved. * </p> * * @param resource The resource where to delete markers from */ public void unmark(Resource resource) { for (org.dresdenocl.language.ocl.resource.ocl.OclEProblemType nextType : org.dresdenocl.language.ocl.resource.ocl.OclEProblemType.values()) { unmark(resource, nextType); } } /** * <p> * Removes all markers of the given type from the given resource. Markers are * created and removed asynchronously. Thus, they may not appear when calls to * this method return. But, the order of marker additions and removals is * preserved. * </p> * * @param resource The resource where to delete markers from * @param problemType The type of problem to remove */ public void unmark(Resource resource, org.dresdenocl.language.ocl.resource.ocl.OclEProblemType problemType) { final IFile file = getFile(resource); if (file == null) { return; } final String markerType = getMarkerID(problemType); COMMAND_QUEUE.addCommand(new org.dresdenocl.language.ocl.resource.ocl.IOclCommand<Object>() { public boolean execute(Object context) { try { file.deleteMarkers(markerType, false, IResource.DEPTH_ZERO); } catch (CoreException ce) { handleException(ce); } return true; } }); } /** * <p> * Removes all markers that were caused by the given object from the resource. * Markers are created and removed asynchronously. Thus, they may not appear when * calls to this method return. But, the order of marker additions and removals is * preserved. * </p> * * @param resource The resource where to delete markers from * @param causingObject The cause of the problems to remove */ public void unmark(Resource resource, final EObject causingObject) { final IFile file = getFile(resource); if (file == null) { return; } final String markerID = getMarkerID(org.dresdenocl.language.ocl.resource.ocl.OclEProblemType.UNKNOWN); final String causingObjectURI = getObjectURI(causingObject); if (causingObjectURI == null) { return; } COMMAND_QUEUE.addCommand(new org.dresdenocl.language.ocl.resource.ocl.IOclCommand<Object>() { public boolean execute(Object context) { try { IMarker[] markers = file.findMarkers(markerID, true, IResource.DEPTH_ZERO); for (IMarker marker : markers) { if (causingObjectURI.equals(marker.getAttribute(EcoreValidator.URI_ATTRIBUTE))) { marker.delete(); } } } catch (CoreException ce) { handleException(ce); } return true; } }); } /** * Returns the ID of the marker type that is used to indicate problems of the * given type. */ public String getMarkerID(org.dresdenocl.language.ocl.resource.ocl.OclEProblemType problemType) { String markerID = MARKER_TYPE; String typeID = problemType.getID(); if (!"".equals(typeID)) { markerID += "." + typeID; } return markerID; } /** * Tries to determine the file for the given resource. If the platform is not * running, the resource is not a platform resource, or the resource cannot be * found in the workspace, this method returns <code>null</code>. */ protected IFile getFile(Resource resource) { if (resource == null || !Platform.isRunning()) { return null; } String platformString = resource.getURI().toPlatformString(true); if (platformString == null) { return null; } IFile file = (IFile) ResourcesPlugin.getWorkspace().getRoot().findMember(platformString); return file; } /** * Returns an URI that identifies the given object. */ protected String getObjectURI(EObject object) { if (object == null) { return null; } if (object.eIsProxy() && object instanceof BasicEObjectImpl) { return ((BasicEObjectImpl) object).eProxyURI().toString(); } Resource eResource = object.eResource(); if (eResource == null) { return null; } return eResource.getURI().toString() + "#" + eResource.getURIFragment(object); } protected void handleException(CoreException ce) { if (ce.getMessage().matches("Marker.*not found.")) { // ignore }else if (ce.getMessage().matches("Resource.*does not exist.")) { // ignore } else { new org.dresdenocl.language.ocl.resource.ocl.util.OclRuntimeUtil().logError("Error while removing markers from resource:", ce); } } /** * <p> * Removes all markers of the given type from the given resource. Markers are * created and removed asynchronously. Thus, they may not appear when calls to * this method return. But, the order of marker additions and removals is * preserved. * </p> * * @param resource The resource where to delete markers from * @param markerId The id of the marker type to remove */ public void removeAllMarkers(final IResource resource, final String markerId) { if (resource == null) { return; } COMMAND_QUEUE.addCommand(new org.dresdenocl.language.ocl.resource.ocl.IOclCommand<Object>() { public boolean execute(Object context) { try { resource.deleteMarkers(markerId, false, IResource.DEPTH_ZERO); } catch (CoreException ce) { handleException(ce); } return true; } }); } public void createMarker(final IResource resource, final String markerId, final Map<String, Object> markerAttributes) { if (resource == null) { return; } COMMAND_QUEUE.addCommand(new org.dresdenocl.language.ocl.resource.ocl.IOclCommand<Object>() { public boolean execute(Object context) { try { IMarker marker = resource.createMarker(markerId); for (String key : markerAttributes.keySet()) { marker.setAttribute(key, markerAttributes.get(key)); } return true; } catch (CoreException e) { org.dresdenocl.language.ocl.resource.ocl.mopp.OclPlugin.logError("Can't create marker.", e); return false; } } }); } public void beginDeferMarkerUpdates() { } public void endDeferMarkerUpdates() { } public void runCommands() { COMMAND_QUEUE.runCommands(); } }