blob: 28436ef582c17b7344f9b50717b0b5e0f8b71d16 [file] [log] [blame]
/**
* <copyright>
*
* Copyright (c) 2008-2017 See4sys, itemis 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:
* See4sys - Initial API and implementation
* itemis - [418902] ValidationMarkerManager does not distinguish objects with identical URI
* itemis - [458509] NPE in ValidationMarkerManager
* itemis - [480135] Introduce metamodel and view content agnostic problem decorator for model elements
* itemis - [511105] Ensure Mars compatibility & fix compilation errors under Oxygen due to internal API usage
*
* </copyright>
*/
package org.eclipse.sphinx.emf.validation.markers;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.EventObject;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EValidator;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.osgi.util.NLS;
import org.eclipse.sphinx.emf.resource.IXMLMarker;
import org.eclipse.sphinx.emf.util.EObjectUtil;
import org.eclipse.sphinx.emf.util.EcorePlatformUtil;
import org.eclipse.sphinx.emf.validation.Activator;
import org.eclipse.sphinx.emf.validation.diagnostic.ExtendedDiagnostic;
import org.eclipse.sphinx.emf.validation.markers.util.FeatureAttUtil;
import org.eclipse.sphinx.emf.validation.preferences.IValidationPreferences;
import org.eclipse.sphinx.emf.validation.stats.ValidationPerformanceStats;
import org.eclipse.sphinx.emf.validation.util.Messages;
import org.eclipse.sphinx.emf.validation.util.ValidationUtil;
import org.eclipse.sphinx.platform.util.PlatformLogUtil;
/**
* This singleton class manage the validation marker
*/
public class ValidationMarkerManager {
/**
* The singleton instance
*/
private static ValidationMarkerManager markerManager = null;
/**
* The singleton accessor
*
* @return singleton
*/
public static ValidationMarkerManager getInstance() {
if (markerManager == null) {
markerManager = new ValidationMarkerManager();
}
return markerManager;
}
protected ListenerList validationProblemMarkersChangeListenerList = new ListenerList();
/**
* constructor
*/
private ValidationMarkerManager() {
}
/**
* Translate diagnostic in markers on the {@link Resource} resource
*
* @param resource
* the target resource
* @param diagnostic
* the connected {@link Diagnostic}
* @throws CoreException
*/
public void addMarkers(Resource resource, Diagnostic diagnostic) throws CoreException {
Assert.isNotNull(resource);
Assert.isNotNull(diagnostic);
List<IMarker> markerResultList = new ArrayList<IMarker>();
IResource iResource = EcorePlatformUtil.getFile(resource);
if (iResource == null || !iResource.exists()) {
return;
}
// Let's check if the diagnostic data are ok
List<?> diagnosticData = diagnostic.getData();
if (diagnosticData == null || diagnosticData.size() == 0) {
return;
}
// The framework is too slow when there are too many markers. Hence we limitate the number of
// created markers
int max_err = Platform.getPreferencesService().getInt(org.eclipse.sphinx.emf.validation.Activator.PLUGIN_ID,
IValidationPreferences.PREF_MAX_NUMBER_OF_ERRORS, IValidationPreferences.PREF_MAX_NUMBER_OF_ERRORS_DEFAULT, null);
IMarker[] markers = iResource.getWorkspace().getRoot().findMarkers(IValidationMarker.MODEL_VALIDATION_PROBLEM, true,
IResource.DEPTH_INFINITE);
int nb_err = markers.length;
// compute nb markers to be added
int to_be_added = 0;
for (Diagnostic childDiagnostic : diagnostic.getChildren()) {
if (!childDiagnostic.getData().isEmpty()) {
++to_be_added;
}
}
// if we are about to overflow, remove oldest markers to ensure that some of the new markers will be added
if (max_err > 0 && nb_err + to_be_added > max_err) {
int nb_removed = 0;
Arrays.sort(markers, new Comparator<IMarker>() {
@Override
public int compare(IMarker o1, IMarker o2) {
try {
return Long.valueOf(o1.getCreationTime()).compareTo(Long.valueOf(o2.getCreationTime()));
} catch (CoreException ex) {
return 0;
}
}
});
// do not remove more than half of max_err or total number of err
int remove_threshold = (nb_err > max_err ? nb_err : max_err) / 2;
for (int i = 0; i < markers.length && nb_err + to_be_added > max_err; ++i) {
markers[i].delete();
--nb_err;
++nb_removed;
if (nb_removed >= remove_threshold) {
break;
}
}
}
EObject validatedObject = (EObject) diagnosticData.get(0);
try {
// Add new markers
int markerSeverity = 0;
for (Diagnostic childDiagnostic : diagnostic.getChildren()) {
if (max_err > 0 && nb_err > max_err) {
break;
}
if (!childDiagnostic.getData().isEmpty()) {
Map<String, Object> attributes = new HashMap<String, Object>();
IMarker marker = iResource.createMarker(IValidationMarker.MODEL_VALIDATION_PROBLEM);
++nb_err;
EObject tgtObject = (EObject) childDiagnostic.getData().get(0);
attributes.put(EValidator.URI_ATTRIBUTE, EcoreUtil.getURI(tgtObject).toString());
attributes.put(IValidationMarker.HASH_ATTRIBUTE, tgtObject.hashCode());
int severity = childDiagnostic.getSeverity();
if (severity < Diagnostic.WARNING) {
markerSeverity = IMarker.SEVERITY_INFO;
} else if (severity < Diagnostic.ERROR) {
markerSeverity = IMarker.SEVERITY_WARNING;
} else {
markerSeverity = IMarker.SEVERITY_ERROR;
}
attributes.put(IMarker.SEVERITY, markerSeverity);
String message = childDiagnostic.getMessage();
if (message == null) {
message = Messages.noMessageAvailableForThisMarker;
}
attributes.put(IMarker.MESSAGE, message);
EObject eTgt = (EObject) childDiagnostic.getData().get(0);
if (childDiagnostic instanceof ExtendedDiagnostic) {
String ruleId = ((ExtendedDiagnostic) childDiagnostic).getConstraintId();
// The features attribute.
Set<String> features = FeatureAttUtil.getRulesFeaturesForEObj(ruleId, eTgt);
String packedFeaturesStr = FeatureAttUtil.packFeaturesAsString(marker, features);
attributes.put(IValidationMarker.FEATURES_ATTRIBUTE, packedFeaturesStr);
// The rule id attribute.
attributes.put(IValidationMarker.RULE_ID_ATTRIBUTE, ruleId);
}
// Let's set all attributes
marker.setAttributes(attributes);
// Finally notify IValidationProblemMarkerChangedListeners
// SHOULD BE FULLY TESTED, LATER
// fireValidationProblemMarkersChanged(tgtObject);
markerResultList.add(marker);
}
}
// Notify IValidationProblemMarkerChangedListeners
fireValidationProblemMarkersChanged(validatedObject);
} catch (CoreException ex) {
PlatformLogUtil.logAsWarning(Activator.getDefault(),
NLS.bind(Messages.warningProblemWithMarkerOperationOnResource, iResource.getLocationURI().toString()));
}
}
/**
* Remove all markers of type {@link IValidationMarker.MODEL_VALIDATION_PROBLEM} present on the resource.
*
* @param resource
* the target {@link IResource}
* @see IMarker#getType()
*/
private void removeSphinxValidationProblemMarker(final IResource resource) throws CoreException {
Assert.isNotNull(resource);
resource.deleteMarkers(IValidationMarker.MODEL_VALIDATION_PROBLEM, true, IResource.DEPTH_INFINITE);
}
/**
* Remove all markers of type {@linkplain IXMLMarker.XML_INTEGRITY_PROBLEM} present on the resource.
*
* @param resource
* the target {@link IResource}
* @see IMarker#getType()
*/
private void removeXMLIntegrityProblemMarker(final IResource resource) throws CoreException {
Assert.isNotNull(resource);
resource.deleteMarkers(IXMLMarker.XML_INTEGRITY_PROBLEM, true, IResource.DEPTH_INFINITE);
}
/**
* Remove all markers of type {@linkplain IXMLMarker.XML_VALIDITY_PROBLEM} present on the resource.
*
* @param resource
* the target {@link IResource}
* @see IMarker#getType()
*/
private void removeXSDDiagnosticMarker(final IResource resource) throws CoreException {
Assert.isNotNull(resource);
resource.deleteMarkers(IXMLMarker.XML_VALIDITY_PROBLEM, true, IResource.DEPTH_INFINITE);
}
/**
* Remove all markers of type {@link IXMLMarker.XML_WELLFORMEDNESS_PROBLEM} present on the resource.
*
* @param resource
* the target {@link IResource}
* @see IMarker#getType()
*/
private void removeXMLWellFormednessProblemMarker(final IResource resource) throws CoreException {
Assert.isNotNull(resource);
resource.deleteMarkers(IXMLMarker.XML_WELLFORMEDNESS_PROBLEM, true, IResource.DEPTH_INFINITE);
}
/**
* remove all markers of Type {@link IValidationMarker#MODEL_VALIDATION_PROBLEM} directly attached to this eObject
* (depth set to IValidationMarkerManager.DEPTH_ZERO), to its direct children (depth set to
* IValidationMarkerManager.DEPTH_ONE), or to itself and all its children (depth set to
* IValidationMarkerManager.DEPTH_INFINITE)
*
* @param eObject
* the target eObject
* @param depth
* see {@link IValidationMarkerManager}
* @throws CoreException
*/
public void removeMarkers(EObject eObject, int depth) throws CoreException {
removeMarkers(eObject, depth, IValidationMarker.MODEL_VALIDATION_PROBLEM);
}
/**
* Remove all markers directly attached to this eObject (depth set to IValidationMarkerManager.DEPTH_ZERO), to its
* direct children (depth set to IValidationMarkerManager.DEPTH_ONE), or to itself and all its children (depth set
* to IValidationMarkerManager.DEPTH_INFINITE)
*
* @param eObject
* the target eObject
* @param depth
* see {@link IValidationMarkerManager}
* @param markerType
* type of marker to remove
* @throws CoreException
*/
public void removeMarkers(EObject eObject, int depth, String markerType) throws CoreException {
IResource file = EcorePlatformUtil.getFile(eObject);
// Let's test is the target eObject is a root object of the resource => we can use
// a faster algorithm.
Resource resource = eObject.eResource();
if (resource != null && resource.getContents() != null && resource.getContents().size() >= 1) {
EObject rootObject = resource.getContents().get(0);
if (rootObject == eObject && depth == EObjectUtil.DEPTH_INFINITE) {
removeSphinxValidationProblemMarker(file);
removeXMLWellFormednessProblemMarker(file);
removeXSDDiagnosticMarker(file);
removeXMLIntegrityProblemMarker(file);
// Notify IValidationProblemMarkerChangedListeners
fireValidationProblemMarkersChanged(rootObject);
return;
}
}
// Let's get the model validation problem markers
IMarker[] markers = getValidationMarkersList(eObject, depth, markerType);
if (markers == null || markers.length == 0) {
return;
}
for (IMarker marker : markers) {
marker.delete();
}
// Notify IValidationProblemMarkerChangedListeners
fireValidationProblemMarkersChanged(eObject);
}
/**
* Modify the URI of markers connected with the given {@link IResource} and its children
*
* @param resource
* the target {@link IResource}
* @param oldUri
* the old URI
* @param newUri
* the new URI
* @throws CoreException
*/
public void modifyMarkersURI(IResource resource, String oldUri, String newUri) throws CoreException {
if (resource == null) {
return;
}
String[] oldInfo = ValidationUtil.splitURI(oldUri);
String[] newInfo = ValidationUtil.splitURI(newUri);
if (oldInfo != null && oldInfo.length == 2 && newInfo != null && newInfo.length == 2) {
IMarker[] markers = resource.findMarkers(IValidationMarker.MODEL_VALIDATION_PROBLEM, true, IResource.DEPTH_INFINITE);
if (markers == null || markers.length == 0) {
return;
}
String currentUri = null;
try {
for (IMarker marker : markers) {
currentUri = marker.getAttribute(EValidator.URI_ATTRIBUTE, "--"); //$NON-NLS-1$
if (currentUri.contains(oldInfo[0])) {
String newURI = currentUri.replace(oldInfo[0], newInfo[0]);
marker.setAttribute(EValidator.URI_ATTRIBUTE, newURI);
}
}
} catch (CoreException ex) {
PlatformLogUtil.logAsWarning(Activator.getDefault(),
NLS.bind(Messages.warningProblemWithMarkerOperationOnResource, resource.getLocationURI().toString()));
}
}
}
/**
* Update the uri attribute of the problem marker with the URI of the file
*
* @param resource
* an IResource
* @throws CoreException
*/
public void updateMarkersURI(final IResource resource) throws CoreException {
Assert.isNotNull(resource);
if (resource.isAccessible()) {
ValidationPerformanceStats.INSTANCE.startNewEvent(ValidationPerformanceStats.ValidationEvent.EVENT_UPDATE_PROBLEM_MARKERS,
resource.getFullPath());
IWorkspaceRunnable runnable = new IWorkspaceRunnable() {
@Override
public void run(IProgressMonitor monitor) throws CoreException {
String workspacePath = ResourcesPlugin.getWorkspace().getRoot().getLocation().toString();
String newURIPath = resource.getLocation().toString().replace(workspacePath, "platform:/resource"); //$NON-NLS-1$
IMarker[] markers = resource.findMarkers(IValidationMarker.MODEL_VALIDATION_PROBLEM, true, IResource.DEPTH_INFINITE);
for (IMarker marker : markers) {
String currentUri = marker.getAttribute(EValidator.URI_ATTRIBUTE, "#"); //$NON-NLS-1$
String fragment = currentUri.substring(currentUri.lastIndexOf("#")); //$NON-NLS-1$
marker.setAttribute(EValidator.URI_ATTRIBUTE, newURIPath + fragment);
}
}
};
ResourcesPlugin.getWorkspace().run(runnable, resource, IWorkspace.AVOID_UPDATE, new NullProgressMonitor());
ValidationPerformanceStats.INSTANCE.endEvent(ValidationPerformanceStats.ValidationEvent.EVENT_UPDATE_PROBLEM_MARKERS,
resource.getFullPath());
}
}
/**
* Return an array of {IMarker}. This one is composed with markers of Type
* {@link IValidationMarker#MODEL_VALIDATION_PROBLEM} directly attached to this eObject, from its direct children
* only, or itself and also to its children, according to the depth value (respectively
* IValidationMarkerManager.DEPTH_ZERO, IValidationMarkerManager.DEPTH_ONE and
* IValidationMarkerManager.DET_INFINITE.)
*
* @param eObject
* @param depth
* see {@link IValidationMarkerManager}
* @return an array of {@link IMarker}
* @throws CoreException
*/
public IMarker[] getValidationMarkersList(EObject eObject, int depth) throws CoreException {
return getValidationMarkersList(eObject, depth, IValidationMarker.MODEL_VALIDATION_PROBLEM);
}
/**
* Return an array of {IMarker}. This one is composed with markers directly attached to this eObject, from its
* direct children only, or itself and also to its children, according to the depth value (respectively
* IValidationMarkerManager.DEPTH_ZERO, IValidationMarkerManager.DEPTH_ONE and
* IValidationMarkerManager.DET_INFINITE.)
*
* @param eObject
* @param depth
* see {@link IValidationMarkerManager}
* @param markerType
* : type of marker
* @return an array of {@link IMarker}
* @throws CoreException
*/
public IMarker[] getValidationMarkersList(EObject eObject, int depth, String markerType) throws CoreException {
IResource resource = EcorePlatformUtil.getFile(eObject);
if (resource == null || !resource.exists()) {
return new IMarker[0];
}
// All the Markers connected with this resource
IMarker[] allMarkers = resource.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE);
// URI of eObj
String[] tmp = ValidationUtil.splitURI(eObject);
if (tmp == null || tmp.length == 0) {
return new IMarker[0];
}
String eObjURI = tmp[0];
String eObjType = tmp.length > 1 ? tmp[1] : eObject.eClass().getName();
String markerURI, markerEObjType;
List<IMarker> result = new ArrayList<IMarker>();
for (IMarker marker : allMarkers) {
if (marker != null && marker.exists()) {
tmp = ValidationUtil.splitURI((String) marker.getAttribute(EValidator.URI_ATTRIBUTE));
if (tmp != null && tmp.length > 0) {
markerURI = tmp[0];
markerEObjType = tmp.length > 1 ? tmp[1] : null;
Integer hash = (Integer) marker.getAttribute(IValidationMarker.HASH_ATTRIBUTE);
switch (depth) {
case EObjectUtil.DEPTH_ZERO:
// FIXME Remove hash code comparison by ResourceSetListener that deletes associated problem
// markers when model file gets deleted or reloaded (see ResourceProblemHandler for details)
if (URI.createURI(markerURI).fragment().equals(URI.createURI(eObjURI).fragment())
&& (markerEObjType == null || eObjType.equals(markerEObjType))) {
result.add(marker);
}
break;
case EObjectUtil.DEPTH_ONE: // same treatment, let's modify it later if necessary
boolean shouldbeDeleted = true;
shouldbeDeleted &= markerURI.contains(eObjURI);
if (shouldbeDeleted) {
String subPart = markerURI.substring(eObjURI.length());
shouldbeDeleted &= subPart.indexOf('/') == subPart.lastIndexOf('/');
}
if (shouldbeDeleted) {
result.add(marker);
}
break;
case EObjectUtil.DEPTH_INFINITE:
if (markerURI.contains(eObjURI)) {
result.add(marker);
}
break;
default:
// Something is rotten in my kingdom, do nothing
break;
}
}
} else {
String msg = NLS.bind(Messages.warningNoSuchMarker, marker == null ? "???" : marker.getId()); //$NON-NLS-1$
PlatformLogUtil.logAsWarning(Activator.getDefault(), msg);
}
}
return result.toArray(new IMarker[result.size()]);
}
/**
* check if an array of IMarker contains a marker with the status IStatus.ERROR
*
* @param markers
* array of {@link IMarker}
* @return true if the status IMarker.SEVERITY_ERROR is found, false otherwise
*/
public boolean isError(IMarker[] markers) {
return isCodePresent(IMarker.SEVERITY_ERROR, markers);
}
/**
* check if an array of IMarker contains a marker with the status IStatus.WARNING
*
* @param markers
* array of {@link IMarker}
* @return true if the status IMarker.SEVERITY_WARNING is found, false otherwise
*/
public boolean isWarning(IMarker[] markers) {
return isCodePresent(IMarker.SEVERITY_WARNING, markers);
}
/**
* look for markers with severity code
*
* @param status
* @see {@link org.eclipse.core.runtime.IStatus}
* @param markers
* @return true if the status is found, false otherwise
*/
public boolean isCodePresent(int status, IMarker[] markers) {
boolean result = false;
int i = -1;
int len = markers.length;
while (++i < len) {
if (markers[i].getAttribute(IMarker.SEVERITY, -1) == status) {
result = true;
break;
}
}
return result;
}
/**
* Get the greater severity code from a set of {@link org.eclipse.core.resources.IMarker IMarkers}.
*
* @param markers
* An array of {@link org.eclipse.core.resources.IMarker IMarkers}.
* @return The greater severity code from the {@link org.eclipse.core.resources.IMarker IMarker} array
* @see org.eclipse.core.resources.IMarker
*/
public int getUpperSeverity(IMarker[] markers) {
int currentSeverity = -1;
int i = -1;
int len = markers.length;
while (++i < len) {
if (markers[i].getAttribute(IMarker.SEVERITY, -1) > currentSeverity) {
currentSeverity = markers[i].getAttribute(IMarker.SEVERITY, -1);
}
if (currentSeverity == IMarker.SEVERITY_ERROR) {
break;
}
}
return currentSeverity;
}
/**
* return the validation severity code of the {@link EObject} {@link EObject}.
*
* @param eObject
* the target {@link EObject}
* @param depth
* of the search
* @return the Marker severity Status code associated to this {@link EObject}
* @throws CoreException
* @see {@link IMarker}
*/
public int getEObjectErrorStatus(EObject eObject, int depth) throws CoreException {
IMarker[] markers = getValidationMarkersList(eObject, depth, IValidationMarker.MODEL_VALIDATION_PROBLEM);
return getUpperSeverity(markers);
}
/**
* Handles the given diagnostic and update markers on the concerned {@link EObject} with infinite depth
*
* @param diagnostic
* The validation diagnostic to handle.
*/
public void handleDiagnostic(Diagnostic diagnostic) {
handleDiagnostic(diagnostic, EObjectUtil.DEPTH_INFINITE);
}
/**
* Handles the given diagnostic and update markers on the concerned {@link EObject}.
*
* @param diagnostic
* The validation diagnostic to handle.
* @param depth
* depth of the diagnostic, see {@link EObjectUtil}
*/
public void handleDiagnostic(Diagnostic diagnostic, int depth) {
Assert.isNotNull(diagnostic);
if (diagnostic.getData() == null) {
return;
}
List<?> diagnosticData = diagnostic.getData();
if (diagnosticData.isEmpty() || !(diagnosticData.get(0) instanceof EObject)) {
return;
}
/* The top level object validated. */
EObject eObject = (EObject) diagnosticData.get(0);
if (diagnostic.getSeverity() == Diagnostic.OK) {
// Everything is OK; no error nor warning raised during validation. Let's clean old markers if any.
try {
// Remove every existing markers.
markerManager.removeMarkers(eObject, depth);
} catch (CoreException ex) {
PlatformLogUtil.logAsWarning(Activator.getDefault(), ex);
}
} else {
/*
* At least one error or warning has been found during validation. Existing markers must be updated or
* removed and new ones must be created.
*/
Resource resource = eObject.eResource();
if (resource != null) {
try {
// On a first hand, Let's remove every existing markers.
markerManager.removeMarkers(eObject, depth);
// Then, Let's add the new markers from the given diagnostic.
markerManager.addMarkers(resource, diagnostic);
} catch (CoreException ex) {
PlatformLogUtil.logAsWarning(Activator.getDefault(), ex);
}
}
}
}
public void addValidationProblemMarkersChangeListener(IValidationProblemMarkersChangeListener listener) {
validationProblemMarkersChangeListenerList.add(listener);
}
public void removeValidationProblemMarkersChangeListener(IValidationProblemMarkersChangeListener listener) {
validationProblemMarkersChangeListenerList.remove(listener);
}
protected void fireValidationProblemMarkersChanged(EObject object) {
for (Object listener : validationProblemMarkersChangeListenerList.getListeners()) {
if (listener instanceof IValidationProblemMarkersChangeListener) {
((IValidationProblemMarkersChangeListener) listener).validationProblemMarkersChanged(new EventObject(object));
}
}
}
}