/**
* Copyright (c) 2006 Eclipse.org
*
* 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: dvorak - initial API and implementation
*/
package org.eclipse.gmf.internal.bridge.transform;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
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.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.emf.common.ui.MarkerHelper;
import org.eclipse.emf.common.util.BasicDiagnostic;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.DiagnosticChain;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClass;
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.Diagnostician;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.provider.IItemLabelProvider;
import org.eclipse.emf.edit.ui.util.EditUIMarkerHelper;
import org.eclipse.gmf.internal.bridge.ui.Plugin;
/**
* Provides various functinality useful for model validation, for instances
* progress monitoring, resource markers creation.
*/
public class ValidationHelper {
/**
* Enhanced diagnostician class supporting progress monitoring and object
* labels.
*/
private static class SmartDiagnostician extends Diagnostician {
private static ComposedAdapterFactory adapterFactory = new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE);
private IProgressMonitor monitor;
/**
* Constructs diagnostician with progress monitor.
*
* @param progressMonitor
* non-null progress monitor to track validation progress
*/
SmartDiagnostician(IProgressMonitor progressMonitor) {
this.monitor = progressMonitor;
}
/*
* Utilizes the adapter factory registry to provide labels.
*/
@Override
public String getObjectLabel(EObject eObject) {
if (eObject != null) {
IItemLabelProvider itemLabelProvider = (IItemLabelProvider) adapterFactory.adapt(eObject, IItemLabelProvider.class);
if (itemLabelProvider != null) {
return itemLabelProvider.getText(eObject);
}
} else {
return ""; //$NON-NLS-1$
}
return super.getObjectLabel(eObject);
}
/*
* Notifies the monitor about the unit of work done.
*/
@Override
public boolean validate(EClass eClass, EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context) {
if(monitor.isCanceled()) {
return true;
}
monitor.worked(1);
return super.validate(eClass, eObject, diagnostics, context);
}
}
/**
* Helper class for binding validation diagnostics to corresponding problem
* markers.
* <p>
* This class ensures that markers are managed for both {@link Diagnostic}}
* and {@link Resource.Diagnostic} diagnostic types.
*/
private static class GMFMarkerHelper extends EditUIMarkerHelper {
private LinkedHashMap<Diagnostic, IMarker> diagnostic2Marker;
private String markerID;
/**
* Constructs marker helper with default markerID taken from its super-type.
*/
GMFMarkerHelper() {
super();
}
/**
* Constructs marker helper with given marker ID.
*
* @param markerID
* string ID used as IMarker.TYPE.
* <p>
* If <code>null</code>, the default is
* <code>resource-problem-marker</code> taken from
* {@link MarkerHelper#getMarkerID()}
*/
GMFMarkerHelper(String markerID) {
this.markerID = markerID;
}
public IFile getFileFromDiagnostic(Diagnostic diagnostic) {
return getFile(diagnostic);
}
@Override
protected void adjustMarker(IMarker marker, Diagnostic diagnostic, Diagnostic parentDiagnostic) throws CoreException {
getDiagnostic2MarkerMap().put(diagnostic, marker);
// adjust marker to support IGotoMarker for standard EMF generated editors
List<?> data = diagnostic.getData();
if (data != null && !data.isEmpty()) {
Object target = data.get(0);
if (target instanceof EObject) {
marker.setAttribute(EValidator.URI_ATTRIBUTE, EcoreUtil.getURI((EObject) target).toString());
}
}
super.adjustMarker(marker, diagnostic, parentDiagnostic);
}
@Override
protected IFile getFile(Object datum) {
if (datum instanceof EObject) {
EObject eObject = (EObject) datum;
if(eObject.eResource() != null) {
URI uri = eObject.eResource().getURI();
IFile file = getFile(uri);
if(file != null) {
return file;
}
}
}
return super.getFile(datum);
}
@Override
protected String getMarkerID() {
return markerID != null ? markerID : super.getMarkerID();
}
LinkedHashMap<Diagnostic, IMarker> getDiagnostic2MarkerMap() {
if(diagnostic2Marker == null) {
diagnostic2Marker = new LinkedHashMap<Diagnostic, IMarker>();
}
return diagnostic2Marker;
}
}
/**
* Diagnostic to marker map data holder class.
* <p>
* Note: The map iterators respect the order of marker creation.
*/
public static class DiagnosticMarkerMap {
private Map<Diagnostic, IMarker> map;
/**
* Constraint diagnostic marker from the given map data.
* @param markerMap map data to be encapsulated by the resulting object.
*/
DiagnosticMarkerMap(LinkedHashMap<Diagnostic, IMarker> markerMap) {
this.map = Collections.unmodifiableMap(markerMap);
}
/**
* Gets the map data for this diagnostic marker map.
* @return read-only map object
*/
public Map<Diagnostic, IMarker> getMap() {
return map;
}
}
/**
* No instances, just an utility class.
*/
private ValidationHelper() {
super();
}
/**
* Indicates whether the severiti of the given diagnostic matches the given
* bitmask.<p>
* Note that a diagnostic with severity <code>OK</code> will never match;
* use <code>isOK</code> instead to detect a diagnostic with a severity of <code>OK</code>.
*
* @param diagnostic a diagnostic to test for severity match
* @param severityBitMask a mask formed by bitwise or'ing severity mask constants
* (<code>ERROR</code>, <code>WARNING</code>, <code>INFO</code>, <code>CANCEL</code>)
*
* @return <code>true</code> if there is at least one match, <code>false</code> otherwise
*
* @see Diagnostic#ERROR
* @see Diagnostic#WARNING
* @see Diagnostic#INFO
* @see Diagnostic#CANCEL
*/
public static boolean matches(Diagnostic diagnostic, int severityBitMask) {
return (diagnostic.getSeverity() & severityBitMask) != 0;
}
/**
* Returns whether this diagnostic indicates everything is OK. (neither
* info, warning, nor error).
*
* @return <code>true</code> if this diagnostic has severity
* <code>OK</code>, and <code>false</code> otherwise
*/
public static boolean isOK(Diagnostic diagnostic) {
return diagnostic.getSeverity() == Diagnostic.OK;
}
/**
* Validates the given <code>EObject</code> and its all contents.
* <p>
* If the EMF basic validation results in <code>Diagnostic.CANCEL</code>
* severity, no problem markers are created at all.
*
* The problem markers created by this operation are added as extra-data to
* the returned diagnostic and are accessible via
* {@link #getDiagnosticMarkerMap(Diagnostic)}.
*
* @param createMarkers
* if <code>true</code> this operation produces validation
* problems markers provided that eObject.eResource() represents
* a file existing in the workspace. If <code>true</code> no
* attempt to create markers is performed.
*
* @param progressMonitor
* the progress monitor to track validation progress, or
* <code>null</code> if no progress monitoring is required.
* The implementation creates {@link SubProgressMonitor} as
* a sub-task of the given parent <code>progressMonitor</code>
* allocating 1 tick from the parent. #beginTask and #done operation
* on the subprogress monitor is called.
*
* @return the validation result diagnostic
*/
public static Diagnostic validate(EObject eObject, boolean createMarkers, IProgressMonitor progressMonitor) {
IProgressMonitor monitor = null;
try {
int count = IProgressMonitor.UNKNOWN;
if(progressMonitor != null) {
for (Iterator<EObject> i = eObject.eAllContents(); i.hasNext(); i.next()) {
++count;
}
}
monitor = (progressMonitor != null) ? new SubProgressMonitor(progressMonitor, 1, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK) : new NullProgressMonitor();
monitor.beginTask("", count); //$NON-NLS-1$
monitor.subTask(Messages.ValidationHelper_task_validate);
Diagnostic validationStatus = new SmartDiagnostician(monitor).validate(eObject);
if(validationStatus.getSeverity() == Diagnostic.CANCEL) {
return validationStatus;
}
if(createMarkers) {
return createMarkers(validationStatus, EValidator.MARKER);
}
return validationStatus;
} finally {
monitor.done();
}
}
/**
* Validates the given <code>EObject</code> and its all contents.
* <p>
* This method is progress monitoring ignorant shorthand of {@link #validate(EObject, boolean, IProgressMonitor)}
*
* @param createMarkers
* if <code>true</code> this operation produces validation
* problems markers provided that eObject.eResource() represents
* a file existing in the workspace. If <code>true</code> no
* attempt to create markers is performed.
*
* @return the validation result diagnostic
*
* @see #validate(EObject, boolean, IProgressMonitor)
*/
public static Diagnostic validate(EObject eObject, boolean createMarkers) {
return validate(eObject, createMarkers, null);
}
/**
* Gets the diagnostic marker map associated with the given validation
* diagnostic.
*
* @param diagnostic
* a non-null validation diagnostic which resulted from calling
* {@link #validate(EObject, boolean, IProgressMonitor)}}
*
* @see #validate(EObject, boolean, IProgressMonitor)
*/
public static DiagnosticMarkerMap getDiagnosticMarkerMap(Diagnostic diagnostic) {
List<?> data = (diagnostic.getData() != null) ? diagnostic.getData() : Collections.EMPTY_LIST;
for (Object dataItem : data) {
if (dataItem instanceof DiagnosticMarkerMap) {
return (DiagnosticMarkerMap) dataItem;
}
}
return null;
}
/**
* Extracts the file of the validated model instance referenced by the given
* diagnostic.
*
* @param diagnostic
* a non-null validation diagnostic
* @return the file object or <code>null</code> in case the resource
* associated with the validated object does not represents a file
* existing in the workspace.
*/
public static IFile getFileFromDiagnostic(Diagnostic diagnostic) {
return new GMFMarkerHelper(EValidator.MARKER).getFileFromDiagnostic(diagnostic);
}
/**
* Creates resource problem markers {@link Resource.Diagnostic} encapsualted
* in the given diagnostic object.
*
* @param diagnostic
* non-null diagnostic eventually containing a hiearchy of
* diagnostics for which markers are to be created.
*
* @see #getDiagnosticMarkerMap(Diagnostic)
* @see Resource.Diagnostic
*/
public static Diagnostic createResourceProblemMarkers(Diagnostic diagnostic) {
return createMarkers(diagnostic, null /* resource problem markers*/);
}
private static Diagnostic createMarkers(Diagnostic diagnostic, String markerID) {
GMFMarkerHelper markerHelper = new GMFMarkerHelper(markerID);
try {
markerHelper.deleteMarkers(diagnostic, false, IResource.DEPTH_ZERO);
if(!diagnostic.getChildren().isEmpty() && !isOK(diagnostic)) {
markerHelper.createMarkers(diagnostic);
}
} catch (CoreException e) {
IStatus status = Plugin.createError(Messages.ValidationHelper_e_marker_creation, e);
Plugin.log(status);
}
DiagnosticMarkerMap markerMap = new DiagnosticMarkerMap(markerHelper.getDiagnostic2MarkerMap());
List<Object> data = new ArrayList<Object>(diagnostic.getData() != null ? diagnostic.getData() : Collections.emptyList());
data.add(markerMap);
BasicDiagnostic result = new BasicDiagnostic(
diagnostic.getSource(), diagnostic.getCode(),
diagnostic.getMessage(), data.toArray());
result.addAll(diagnostic);
return result;
}
}