Section 14.2. Markers


14.2. Markers

Markers are used to annotate specific locations within a resource. For example, the Eclipse Java compiler not only produces class files from source files, but it also annotates the source files by adding markers to indicate compilation errors, deprecated code usage, and so on. Markers do not modify the resources they annotate, but instead are stored in the workspace metadata area. Markers are automatically updated by editors so that when a user edits a file, they are repositioned or deleted appropriately. Rather than sending messages to the console, you want the PropertiesFileAuditor to create a marker indicating where a problem exists (see Figure 14-6).

Figure 14-6. Marker declaration and data structures.


14.2.1. Marker types

Markers are grouped by marker type. Every marker type has an identifier and zero or more supermarker types but no behavior. New marker types are declared in terms of existing ones. Marker types added by the org.eclipse.core.resources plug-in appear as constants in IMarker and include:

org.eclipse.core.resources.bookmarkIMarker.BOOKMARK The super type of marker that appears in the Bookmarks view.

org.eclipse.core.resources.markerIMarker.MARKER The root supertype of all markers.

org.eclipse.core.resources.problemmarker IMarker.PROBLEM The supertype of marker that appears in the Problems view.

org.eclipse.core.resources.taskmarkerIMarker.TASK The supertype of marker that appears in the Tasks view.

org.eclipse.core.resources.textmarkerIMarker.TEXT The supertype of all text-based markers.

For the purposes here, you want to introduce a new marker type for the plug-in manifest audit results. Switch to the Extensions page of the plug-in manifest editor and click the Add... button to add an org.eclipse.core.resources.markers extension. Select the newly added extension in the tree on the left and specify "auditmarker" as the ID and "Properties Auditor Marker" as the name in the fields on the right (see Figure 14-7).

Figure 14-7. The New Extension wizard showing the markers extension point selected.


You want your markers to appear in the Problems view, so specify org.eclipse.core.resources.problemmarker as a supertype by right-clicking on the markers' extension and selecting New > super. Click on the new super element and enter "org.eclipse.core.resources.problemmarker" for the type attribute. The markers relate to a range of sources in the plug-in manifest or plug-in properties files, so specify org.eclipse.core.resources.textmarker as well using the same procedure.

You want your markers to persist across multiple sessions, so right-click on the markers declaration and select New > persistent. Click on the new persistent element and enter "true" for the value attribute.

You inherit several marker attributes from the marker super types specified earlier, but want to associate two new attributes with the audit marker. Right-click on the markers declaration and select New > attribute. Click on the new attribute element and enter "key" for the value attribute. Repeat this process to specify the "violation" attribute.

Once complete, the new marker type declaration looks like this:

<extension
   
   point="org.eclipse.core.resources.markers"
   name="Properties Auditor Marker">
   <super type="org.eclipse.core.resources.problemmarker"/>
   <super type="org.eclipse.core.resources.textmarker"/>
   <attribute name="key"/>
   <attribute name="violation"/>
   <persistent value="true"/>
</extension>
 


The aforementioned declaration specifies the marker's local identifier; the full identifier is the plug-in identifier plus the local identifier that is added as a constant in PropertiesFileAuditor.

private static final String MARKER_ID =
   FavoritesPlugin.ID + ".auditmarker";
 


14.2.2. Creating and deleting markers

You want to create one marker for each problem that is found, but first you must remove any old markers. To accomplish this, add the following lines in the auditPluginManifest() method:

private void auditPluginManifest(IProgressMonitor monitor) {
   monitor.beginTask("Audit plugin manifest", 4);

    if (!deleteAuditMarkers(getProject())) {
       return;
   }

   if (checkCancel(monitor)) {
      return;
   }

   ... etc ...
}
 


which calls the following new method to delete all existing markers in the specified project.

public static boolean deleteAuditMarkers(IProject project) {
   try {
       project.deleteMarkers(
         MARKER_ID, false, IResource.DEPTH_INFINITE);
      return true;
    }
    catch (CoreException e) {
      FavoritesLog.logError(e);
      return false;
    }
}
 


Next, add two constants and rework the reportProblem() method (see Section 14.1.2, IncrementalProjectBuilder, on page 502) to create a marker and set marker attributes (see the next section) to indicate problems. The revised method not only creates a marker but sets various marker attributes, which are discussed in the next section.

public static final String KEY = "key";
public static final String VIOLATION = "violation";

private void reportProblem(
   String msg, Location loc, int violation, boolean isError)
{
   try {
      IMarker marker = loc.file.createMarker(MARKER_ID);
      marker.setAttribute(IMarker.MESSAGE, msg + ": " + loc.key);
      marker.setAttribute(IMarker.CHAR_START, loc.charStart);
      marker.setAttribute(IMarker.CHAR_END, loc.charEnd);
      marker.setAttribute(
         IMarker.SEVERITY,
         isError
            ? IMarker.SEVERITY_ERROR
            : IMarker.SEVERITY_WARNING);
      marker.setAttribute(KEY, loc.key);
      marker.setAttribute(VIOLATION, violation);
   }
   catch (CoreException e) {
      FavoritesLog.logError(e);
      return;
   }
}
 


Finally, creating and setting attributes, and deleting markers generates resource change events. For efficiency, modify the build() method to wrap the call to auditPluginManifest() in a IWorkspaceRunnable so that events will be batched and sent when the operation has completed (see Section 9.3, Batching Change Events, on page 382):

protected IProject[] build(
   int kind,
   Map args,
   IProgressMonitor monitor
) throws CoreException
{
   if (shouldAudit(kind)) {
      ResourcesPlugin.getWorkspace().run(
         new IWorkspaceRunnable() {
            public void run(IProgressMonitor monitor)
               throws CoreException
            {
               auditPluginManifest(monitor);
            }
         },
         monitor
      );
   }
   return null;
}
 


When this is in place, the problems reported by the PropertiesFileAuditor appear in the Problems view rather than the Console view (see Figure 14-8). In addition, the markers appear as small warning and error icons along the left side of the plugin.xml and plugin.properties editors.

Figure 14-8. Problems view containing problems found by the auditor.


14.2.3. Marker attributes

Marker attributes take the form of key/value pairs, where the key is a string and the value can be a string, an integer, or a Boolean. IMarker methods for accessing attributes include:

getAttribute(String) Returns the attribute with the given name. The result is an instance of a string, an integer, or a Boolean. Returns null if the attribute is undefined.

getAttribute(String, boolean) Returns the Boolean-valued attribute with the given name. Returns the given default value if the attribute is undefined, the marker does not exist, or it is not a Boolean value.

getAttribute(String, int) Returns the integer-valued attribute with the given name. Returns the given default value if the attribute is undefined, the marker does not exist, or it is not an integer value.

getAttribute(String, String) Returns the string-valued attribute with the given name. Returns the given default value if the attribute is undefined, the marker does not exist, or it is not a string value.

getAttributes() Returns a map of the attributes for the marker. The map has String keys and values that are string, integer, Boolean, or null. If the marker has no attributes, then null is returned.

getAttributes(String[]) Returns the attributes with the given names. The result is an array whose elements correspond to the elements of the given attribute name array. Each element is a string, integer, Boolean, or null.

setAttribute(String, boolean) Sets the Boolean-valued attribute with the given name. This method changes resources; these changes will be reported in a subsequent resource change event, including an indication that this marker has been modified.

setAttribute(String, int) Sets the integer-valued attribute with the given name. This method changes resources; these changes will be reported in a subsequent resource change event, including an indication that this marker has been modified.

setAttribute(String, Object) Sets the attribute with the given name. The value must be a string, integer, Boolean, or null. If the value is null, the attribute is considered to be undefined. This method changes resources; these changes will be reported in a subsequent resource change event, including an indication that this marker has been modified.

setAttributes(String[], Object[]) Sets the given attribute key/value pairs on this marker. The values must be string, integer, Boolean, or null. If a value is null, the new value of the attribute is considered to be undefined. This method changes resources; these changes will be reported in a subsequent resource change event, including an indication that this marker has been modified.

setAttributes(Map) Sets the attributes for this marker to be the ones contained in the given map. The values must be instances of a string, integer, or Boolean. Attributes previously set on the marker but not included in the given map are considered to be removals. Passing a null parameter is equivalent to removing all marker attributes. This method changes resources; these changes will be reported in a subsequent resource change event, including an indication that this marker has been modified.

Marker attributes are declared in the plug-in manifest for documentation purposes, but are not used during compilation or execution. For example, in the marker type declaration, two new attributes were declared for the marker type named key and violation. Alternatively, they could be documented using XML <!-- --> comments, but we recommend using the attribute declaration below because future versions of Eclipse might use them.

<extension
   
   point="org.eclipse.core.resources.markers"
   name="Properties Auditor Marker">
   <super type="org.eclipse.core.resources.problemmarker"/>
   <super type="org.eclipse.core.resources.textmarker"/>
   <attribute name="key"/>
   <attribute name="violation"/>
   <persistent value="true"/>
</extension>
 


The org.eclipse.core.resources plug-in introduces several attributes used commonly throughout Eclipse. The following attribute keys are defined in IMarker.

CHAR_END Character end marker attribute. An integer value indicating where a text marker ends. This attribute is zero-relative to the file and exclusive.

CHAR_START Character start marker attribute. An integer value indicating where a text marker starts. This attribute is zero-relative to the file and inclusive.

DONE Done marker attribute. A Boolean value indicating whether a marker (e.g., a task) is considered done.

LINE_NUMBER Line number marker attribute. An integer value indicating the line number for a text marker. This attribute is 1-relative.

LOCATION Location marker attribute. The location is a human-readable (localized) string that can be used to distinguish between markers on a resource. As such, it should be concise and aimed at users. The content and form of this attribute is not specified or interpreted by the platform.

MESSAGE Message marker attribute. A localized string describing the nature of the marker (e.g., a name for a bookmark or task). The content and form of this attribute is not specified or interpreted by the platform.

PRIORITY Priority marker attribute. A number from the set of constants defined in IMarker: PRIORITY_HIGH, PRIORITY_LOW, and PRIORITY_NORMAL.

SEVERITY Severity marker attribute. A number from the set of constants defined in IMarker: SEVERITY_ERROR, SEVERITY_WARNING, and SEVERITY_INFO.

trANSIENT transient marker attribute. A Boolean value indicating whether the marker (e.g., a task) is considered transient even if its type is declared as persistent.

USER_EDITABLE User-editable marker attribute. A Boolean value indicating whether a user should be able to manually change the marker (e.g., a task). The default is true.

In the revised reportProblem() method (see Section 14.2.2, Creating and deleting markers, on page 515), several marker attributes were set that are later interpreted by Eclipse. The Problems view uses the IMarker.MESSAGE and IMarker.LOCATION attributes to populate the Description and Location columns. Editors use the IMarker.CHAR_START and IMarker.CHAR_END attributes to determine what range of text should be highlighted.

14.2.4. Marker resolutionquick fix

Now that you can generate markers, the user can quickly jump to the location of a problem by double-clicking on the corresponding entry in the Problems view, but no help with fixing the problem is provided. Using marker resolution, you can provide an automated mechanism for fixing the problems that your builder identifies.

Create a new org.eclipse.ui.ide.markerResolution extension (see Figure 14-9), add a markerResolutionGenerator nested element (see Figure 14-10), and specify the marker type as "com.qualityeclipse.favorites.auditmarker."

Figure 14-9. The New Extension wizard showing the org.eclipse.ui.ide.markerResolution extension point selected.


Figure 14-10. The Plug-in manifest editor showing markerResolutionGenerator attributes.


Use the Java Attribute Editor to generate a marker resolution class named ViolationResolutionGenerator in the package com.qualityeclipse.favorites.builder. When complete, the declaration should look something like this:

<extension point="org.eclipse.ui.ide.markerResolution">
   <markerResolutionGenerator
      markerType="com.qualityeclipse.favorites.auditmarker"
      >
   </markerResolutionGenerator>
</extension>
 


The ViolationResolutionGenerator class provides possible resolution for the user for any com.qualityeclipse.favorites.auditmarker marker by using the org.eclipse.ui.IMarkerResolutionGenerator2 interface (the IMarkerResolutionGenerator2 interface was introduced in Eclipse 3.0, providing additional functionality and replacing the now deprecated IMarkerResolutionGenerator).

package com.qualityeclipse.favorites.builder;

import ...

public class ViolationResolutionGenerator
   implements IMarkerResolutionGenerator2
{
   public boolean hasResolutions(IMarker marker) {
      switch (getViolation(marker)) {
         case PropertiesFileAuditor.MISSING_KEY_VIOLATION :
            return true;
         case PropertiesFileAuditor.UNUSED_KEY_VIOLATION :
            return true;
         default :
            return false;
      }
   }

   public IMarkerResolution[] getResolutions(IMarker marker){
      List resolutions = new ArrayList();
      switch (getViolation(marker)) {
         case PropertiesFileAuditor.MISSING_KEY_VIOLATION :
            resolutions.add(
               new CreatePropertyKeyResolution());
            break;
         case PropertiesFileAuditor.UNUSED_KEY_VIOLATION :
            resolutions.add(
               new DeletePropertyKeyResolution());
            resolutions.add(
               new CommentPropertyKeyResolution());
            break;
         default :
            break;
      }

      return (IMarkerResolution[]) resolutions.toArray(
         new IMarkerResolution[resolutions.size()]);
   }

   private int getViolation(IMarker marker) {
      return marker.getAttribute(PropertiesFileAuditor.VIOLATION, 0);
   }
}
 


The ViolationResolutionGenerator class returns one or more instances of org.eclipse.ui.IMarkerResolution2 (similar to IMarkerResolutionGenerator2, IMarkerResolution2 was introduced in Eclipse 3.0, replacing the now deprecated IMarkerResolution), indicating the possible resolutions for a violation. For example, an instance of CreatePropertyKeyResolution is returned for missing property key violations:

package com.qualityeclipse.favorites.builder;

import ...

public class CreatePropertyKeyResolution
   implements IMarkerResolution2
{
   public String getDescription() {
      return "Append a new property key/value pair"
         + " to the plugin.properties file";
   }

   public Image getImage() {
      return null;
   }

   public String getLabel() {
      return "Create a new property key";
   }
}
 


If the user selects this resolution, the run() method is executed, opening or activating the properties editor and appending a new property key/value pair.

public void run(IMarker marker) {

   // Get the corresponding plugin.properties.
   IFile file = marker.getResource().getParent().getFile(
         new Path("plugin.properties"));
   if (!file.exists()) {
      ByteArrayInputStream stream =
         new ByteArrayInputStream(new byte[] {});
      try {
         file.create(stream, false, null);
      }
      catch (CoreException e) {
         FavoritesLog.logError(e);
         return;
      }
   }

   // Open or activate the editor.
   IWorkbenchPage page = PlatformUI.getWorkbench()
         .getActiveWorkbenchWindow().getActivePage();

   IEditorPart part;
   try {
      part = IDE.openEditor(page, file, true);
   }
   catch (PartInitException e) {
      FavoritesLog.logError(e);
      return;
   }

   // Get the editor's document.
   if (!(part instanceof ITextEditor)) {
      return;
   }

   ITextEditor editor = (ITextEditor) part;
   IDocument doc = editor.getDocumentProvider()
      .getDocument(new FileEditorInput(file));

   // Determine the text to be added.
   String key;
   try {
      key = (String) marker.getAttribute(PropertiesFileAuditor.KEY);
   }
   catch (CoreException e) {
      FavoritesLog.logError(e);
      return;
   }

   String text = key + "=Value for " + key;

   // If necessary, add a newline.
   int index = doc.getLength();
   if (index > 0) {
      char ch;
      try {
         ch = doc.getChar(index - 1);
      }
      catch (BadLocationException e) {
         FavoritesLog.logError(e);
         return;
      }

      if (ch != '\r' || ch != '\n') {
         text = System.getProperty("line.separator") + text;
      }
   }

   // Append the new text.
   try {
      doc.replace(index, 0, text);
   }
   catch (BadLocationException e) {
      FavoritesLog.logError(e);
      return;
   }

   // Select the value so the user can type.
   index += text.indexOf('=') + 1;
   editor.selectAndReveal(index, doc.getLength() - index);
}
 


14.2.5. Finding markers

You can query a resource for all its markers or all its markers of a given type. If the resource is a container, such as folder, project, or the workspace root, you can request all markers for that container's children as well. The depth can be zero (just that container), one (the container and its direct children), or infinite (the resource and all direct and indirect children). For example, to retrieve all markers associated with a folder and its children to an infinite depth, you might use an expression like this:

IMarker[] markers;
try {
   markers = myFolder.findMarkers(
      IMarker.PROBLEM, true, IResource.DEPTH_INFINITE);
}
catch (CoreException e) {
   // Log the exception and bail out.
}