14.2. MarkersMarkers 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 typesMarkers 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:
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 id="auditmarker" 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 markersYou 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 attributesMarker 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:
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 id="auditmarker" 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.
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 fixNow 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" class="com.qualityeclipse.favorites.builder .ViolationResolutionGenerator"> </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 markersYou 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. } |