+package eu.etaxonomy.taxeditor.editor.validation;\r
+\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Set;\r
+\r
+import javax.xml.validation.Validator;\r
+\r
+import org.eclipse.core.resources.IMarker;\r
+import org.eclipse.core.resources.IResource;\r
+import org.eclipse.core.resources.IWorkspaceRoot;\r
+import org.eclipse.core.resources.ResourcesPlugin;\r
+import org.eclipse.core.runtime.CoreException;\r
+\r
+import eu.etaxonomy.cdm.model.validation.EntityConstraintViolation;\r
+import eu.etaxonomy.cdm.model.validation.EntityValidationResult;\r
+import eu.etaxonomy.cdm.model.validation.Severity;\r
+\r
+/**\r
+ * A class responsible for refreshing problem markers coming from the CVI\r
+ * (Cdmlib Validation Infrastructure).\r
+ * \r
+ * @author ayco_holleman\r
+ * \r
+ */\r
+/*\r
+ * See following for problems with icons in Problems view\r
+ * http://stackoverflow.com\r
+ * /questions/13497258/markers-view-shows-my-marker-with-\r
+ * a-red-square-insted-of-error-warning-icon\r
+ * http://stackoverflow.com/questions/2888207\r
+ * /eclipse-plugin-custom-icon-for-a-marker\r
+ * http://www.eclipse.org/articles/Article-Mark%20My%20Words/mark-my-words.html\r
+ * http\r
+ * ://cubussapiens.hu/2010/11/markers-and-annotations-in-eclipse-for-error-feedback\r
+ * /\r
+ * \r
+ * See here for difficulty of attaching markers to non-resources (files,\r
+ * folders):\r
+ * http://stackoverflow.com/questions/12493179/eclipse-virtual-resources\r
+ */\r
+public class MarkerManager {\r
+\r
+ public static final String MARKER_TYPE_ID = "eu.etaxonomy.taxeditor.markers.validationerror";\r
+\r
+ /**\r
+ * The primary key (id) of the EntityValidationResult record\r
+ */\r
+ public static final String ATTRIB_DATABASE_ID = "databaseId";\r
+\r
+ // The values of the following constants must correspond to the attributes\r
+ // defined for the org.eclipse.core.resources.markers extension point in\r
+ // plugin.xml\r
+\r
+ /**\r
+ * A user-friendly description of the type of the entity\r
+ */\r
+ public static final String ATTRIB_USER_FRIENDLY_TYPE_NAME = "userFriendlyTypeName";\r
+ /**\r
+ * A user-friendly description of the entity\r
+ */\r
+ public static final String ATTRIB_USER_FRIENDLY_DESCRIPTION = "userFriendlyDescription";\r
+ /**\r
+ * The field whose value violated a constraint\r
+ */\r
+ public static final String ATTRIB_USER_FRIENDLY_FIELD_NAME = "userFriendlyFieldName";\r
+ /**\r
+ * The value violating a constraint\r
+ */\r
+ public static final String ATTRIB_INVALID_VALUE = "invalidValue";\r
+ /**\r
+ * The message from the {@link Validator} about what was wrong.\r
+ */\r
+ public static final String ATTRIB_VALIDATOR_MESSAGE = "validatorMessage";\r
+ /**\r
+ * The class of the {@link Validator} coding for the constraint\r
+ */\r
+ public static final String ATTRIB_VALIDATOR_CLASS = "validatorClass";\r
+ /**\r
+ * The class of the validated entity\r
+ */\r
+ public static final String ATTRIB_ENTITY_CLASS = "entityClass";\r
+ /**\r
+ * The id of the validated entity\r
+ */\r
+ public static final String ATTRIB_ENTITY_ID = "entityId";\r
+\r
+ private final IWorkspaceRoot root;\r
+ private final IMarker[] markers;\r
+ private final List<EntityValidationResult> results;\r
+ private final HashMap<Integer, EntityValidationResult> resultMap;\r
+ private final HashMap<Integer, IMarker> markerMap;\r
+\r
+\r
+ MarkerManager(List<EntityValidationResult> results) throws CoreException\r
+ {\r
+ this.root = ResourcesPlugin.getWorkspace().getRoot();\r
+ this.markers = root.findMarkers(MARKER_TYPE_ID, true, IResource.DEPTH_INFINITE);\r
+ this.markerMap = new HashMap<Integer, IMarker>();\r
+ for (IMarker marker : markers) {\r
+ markerMap.put(getDatabaseId(marker), marker);\r
+ }\r
+ this.results = results;\r
+ this.resultMap = new HashMap<Integer, EntityValidationResult>();\r
+ for (EntityValidationResult result : results) {\r
+ resultMap.put(result.getId(), result);\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * Delete all markers that refer to errors that do not exist any longer\r
+ * (i.e. the corresponding database record has been deleted).\r
+ * \r
+ * @return The number of deleted markers\r
+ * \r
+ * @throws CoreException\r
+ */\r
+ int deleteObsoleteMarkers() throws CoreException\r
+ {\r
+ int i = 0;\r
+ IMarker[] markers = root.findMarkers(MARKER_TYPE_ID, true, IResource.DEPTH_INFINITE);\r
+ for (IMarker marker : markers) {\r
+ if (isObsoleteMarker(marker)) {\r
+ ++i;\r
+ marker.delete();\r
+ }\r
+ }\r
+ return i;\r
+ }\r
+\r
+\r
+ /**\r
+ * Create markers for new errors (i.e. no marker has been created for them\r
+ * yet).\r
+ * \r
+ * @return The number of new markers\r
+ * \r
+ * @throws CoreException\r
+ */\r
+ int createMarkers() throws CoreException\r
+ {\r
+ int i = 0;\r
+ IMarker[] markers = root.findMarkers(MARKER_TYPE_ID, true, IResource.DEPTH_INFINITE);\r
+ for (EntityValidationResult result : results) {\r
+ if (!isNewResult(result)) {\r
+ continue;\r
+ }\r
+ Set<EntityConstraintViolation> problems = result.getEntityConstraintViolations();\r
+ for (EntityConstraintViolation problem : problems) {\r
+ if (markerExistsForProblem(problem, markers)) {\r
+ continue;\r
+ }\r
+ IMarker marker = root.createMarker(MARKER_TYPE_ID);\r
+ ++i;\r
+ if (problem.getSeverity() == Severity.ERROR) {\r
+ marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);\r
+ }\r
+ else if (problem.getSeverity() == Severity.WARNING) {\r
+ marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_WARNING);\r
+ }\r
+ else {\r
+ marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO);\r
+ }\r
+ marker.setAttribute(IMarker.MESSAGE, problem.getMessage());\r
+ marker.setAttribute(ATTRIB_DATABASE_ID, result.getId());\r
+ marker.setAttribute(ATTRIB_USER_FRIENDLY_TYPE_NAME, result.getUserFriendlyTypeName());\r
+ marker.setAttribute(ATTRIB_USER_FRIENDLY_DESCRIPTION, result.getUserFriendlyDescription());\r
+ marker.setAttribute(ATTRIB_USER_FRIENDLY_FIELD_NAME, problem.getUserFriendlyFieldName());\r
+ marker.setAttribute(ATTRIB_VALIDATOR_MESSAGE, problem.getMessage());\r
+ marker.setAttribute(ATTRIB_INVALID_VALUE, problem.getInvalidValue());\r
+ marker.setAttribute(ATTRIB_VALIDATOR_CLASS, problem.getValidator());\r
+ marker.setAttribute(ATTRIB_ENTITY_CLASS, result.getValidatedEntityClass());\r
+ marker.setAttribute(ATTRIB_ENTITY_ID, result.getValidatedEntityId());\r
+ }\r
+ }\r
+ return i;\r
+ }\r
+\r
+\r
+ /**\r
+ * Is there a problem marker that captures the specified\r
+ * {@link EntityConstraintViolation}? See\r
+ * {@link #markerCapturesProblem(IMarker, EntityConstraintViolation)}.\r
+ * \r
+ * @param problem\r
+ * @param markers\r
+ * @return\r
+ * @throws CoreException\r
+ */\r
+ private boolean markerExistsForProblem(EntityConstraintViolation problem, IMarker[] markers) throws CoreException\r
+ {\r
+ for (IMarker marker : markers) {\r
+ if (markerCapturesProblem(marker, problem)) {\r
+ return true;\r
+ }\r
+ }\r
+ return false;\r
+ }\r
+\r
+\r
+ /**\r
+ * <p>\r
+ * This method determines whether the problem exposed by the specified\r
+ * marker is <b>de facto</b> equivalent to the specified\r
+ * {@code EntityConstraintViolation}. When the CVI validates an entity, it\r
+ * first deletes previous validation results for that entity and only then\r
+ * saves the new validation result. Thus you cannot rely on the database id\r
+ * of the {@code EntityConstraintViolation} to determine equivalence. Maybe\r
+ * later we can make the CVI more sophisticated in this respect. Or maybe\r
+ * see if solving it through the equals() method of\r
+ * {@code EntityValidationResult} and/or {@code EntityConstraintViolation}\r
+ * is possible. But for now this is the easiest solution.\r
+ * </p>\r
+ * <p>\r
+ * The reason we check for equivalence, is that we don't want to\r
+ * unnecessarily update the Problems view. If a marker is there, we don't\r
+ * want to replace it with an equivalent marker, because that might lead to\r
+ * strange click behaviour for end users (e.g. selected problems will\r
+ * disappear and re-appear unselected).\r
+ * </p>\r
+ * \r
+ * @param marker\r
+ * @param problem\r
+ * @return\r
+ * @throws CoreException\r
+ */\r
+ private static boolean markerCapturesProblem(IMarker marker, EntityConstraintViolation problem) throws CoreException\r
+ {\r
+ EntityValidationResult result = problem.getEntityValidationResult();\r
+ if (!marker.getAttribute(ATTRIB_ENTITY_CLASS).equals(result.getValidatedEntityClass())) {\r
+ return false;\r
+ }\r
+ if (!marker.getAttribute(ATTRIB_ENTITY_ID).equals(result.getValidatedEntityId())) {\r
+ return false;\r
+ }\r
+ if (!marker.getAttribute(ATTRIB_USER_FRIENDLY_FIELD_NAME).equals(problem.getPropertyPath())) {\r
+ return false;\r
+ }\r
+ if (!marker.getAttribute(ATTRIB_INVALID_VALUE).equals(problem.getInvalidValue())) {\r
+ return false;\r
+ }\r
+ if (!marker.getAttribute(ATTRIB_VALIDATOR_CLASS).equals(problem.getValidator())) {\r
+ return false;\r
+ }\r
+ return true;\r
+ }\r
+\r
+\r
+ /**\r
+ * Is this a marker without a corresponding database record (\r
+ * {@link EntityValidationResult})?\r
+ * \r
+ * @param marker\r
+ * @return\r
+ * @throws CoreException\r
+ */\r
+ private boolean isObsoleteMarker(IMarker marker) throws CoreException\r
+ {\r
+ return resultMap.get(getDatabaseId(marker)) == null;\r
+ }\r
+\r
+\r
+ /**\r
+ * Is this an {@link EntityValidationResult} for which no marker has been\r
+ * created yet?\r
+ * \r
+ * @param result\r
+ * @return\r
+ */\r
+ private boolean isNewResult(EntityValidationResult result)\r
+ {\r
+ return markerMap.get(result.getId()) == null;\r
+ }\r
+\r
+\r
+ /**\r
+ * Get the id of the {@link EntityValidationResult} that was stored as one\r
+ * of the marker's attributes.\r
+ * \r
+ * @param marker\r
+ * @return\r
+ * @throws CoreException\r
+ */\r
+ private static Integer getDatabaseId(IMarker marker) throws CoreException\r
+ {\r
+ return (Integer) marker.getAttribute(ATTRIB_DATABASE_ID);\r
+ }\r
+\r
+}\r