/******************************************************************************* * Copyright (c) 2008 IBM Corporation. * 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: * Stan Sutton (suttons@us.ibm.com) - initial API and implementation * Copied liberally and adapted from an implementation provided by * E. D. Willink as an attachment to Eclipse bugzilla bug #245296 (and * copyrighted 2008 under EPL v. 1.0 http://www.eclipse.org/legal/epl-v10.html). * *******************************************************************************/ package org.eclipse.imp.builder; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.IWorkspaceRunnable; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.imp.builder.ProblemLimit.LimitExceededException; import org.eclipse.imp.core.ErrorHandler; import org.eclipse.imp.parser.IParseController; /** * An implementation of IMessageHandler for collecting messages over time * and then creating a group of corresponding resource markers in one batch using * a single workspace operation. * * Copied liberally from a MarkerProblemHandler implementation provided by * E. D. Willink as an attachment to Eclipse bugzilla bug #245296 (and * copyrighted 2008 under EPL v. 1.0 http://www.eclipse.org/legal/epl-v10.html). * * @author Stan Sutton (suttons@us.ibm.com) */ public class MarkerCreatorWithBatching extends MarkerCreator { protected String page = null; // TODO Is this used anywhere? protected Map<Integer, List<Map<String, Object>>> entries = null; // Map of line number to list of marker attributes for line protected ProblemLimit problemLimit = null; protected BuilderBase builder = null; // TODO Make these private and final public String BUILDER_ID; public String PROBLEM_MARKER_ID; public Map<Integer, Integer> severityMap = new HashMap<Integer, Integer>(); { severityMap.put(IMarker.SEVERITY_ERROR, IMarker.SEVERITY_ERROR); severityMap.put(IMarker.SEVERITY_INFO, IMarker.SEVERITY_INFO); severityMap.put(IMarker.SEVERITY_WARNING, IMarker.SEVERITY_WARNING); } /* * NOTE: The parse controller that is provided in constructing an instance of this * type is used to obtain a parse stream that is used in processing error messages * received by the instance. In particular, error messages are associated with positions * in the text, those positions are used to identify corresponding parse tokens, and * those parse tokens are used in determining positions for error markers. For that * reason, the given parse controller should provide a parse stream that is consistent * with (if not identical to) the one obtained when the error messages were generated. * Also for that reason, no additional parsing is done within the marker creator. * Violation of this assumption can lead to markers (and marker annotations) that are * not correctly located with respect to the underling error in the text. */ /** * Constructor to use when you want to create markers that are not related to a * particular builder and that will have the marker type provided here. * * @param resource The resource (e.g., file) on which markers are to be placed * @param parseController The source of the parse stream to which error messages will * be related * @param problemType The type of problem marker (i.e., the problem marker id) */ public MarkerCreatorWithBatching(IFile file, IParseController parseController, String problemType) { super(file, parseController, problemType); BUILDER_ID = null; PROBLEM_MARKER_ID = problemType; } /** * Constructor to use when you want to create markers that are related to a * particular builder and that will have a marker type and builder id based * on that builder. * * @param file The file on which markers are to be placed * @param parseController The source of the parse stream to which error messages will * be related * @param builder The builder that is presumably driving the creation of * markers and that defines the type of marker and builder id * that will be used here */ public MarkerCreatorWithBatching(IFile file, IParseController parseController, BuilderBase builder) { super(file, parseController, builder.getErrorMarkerID()); this.builder = builder; if (builder != null) { BUILDER_ID = builder.getBuilderID(); } else { BUILDER_ID = parseController.getLanguage().getName() + ".builder"; } if (builder != null) { PROBLEM_MARKER_ID = builder.getErrorMarkerID(); } else { PROBLEM_MARKER_ID = parseController.getLanguage().getName() + ".builder"; } } public void setSeverityMap(Map<Integer, Integer> mapOfSeverities) { // TODO: Figure out what to do about target severities that do not correspond // to marker severities severityMap = mapOfSeverities; } public Map<Integer, Integer> getSeverityMap() { return severityMap; } public void addMarker(int severity, String message, int lineNumber, int charStart, int charEnd) throws ProblemLimit.LimitExceededException { String adjustedMessage = message; if (problemLimit != null) { adjustedMessage = problemLimit.check(severity, message); if (adjustedMessage == null) return; } Map<String, Object> attributes = new HashMap<String, Object>(); attributes.put(IMarker.MESSAGE, adjustedMessage); attributes.put(IMarker.SEVERITY, severity); if (lineNumber == -1) { lineNumber = 1; } Integer lineKey = Integer.valueOf(lineNumber); attributes.put(IMarker.LINE_NUMBER, lineKey); if (charStart <= charEnd) { attributes.put(IMarker.CHAR_START, charStart); attributes.put(IMarker.CHAR_END, charEnd); } // attributes.put(BUILDER_ID, creationFactory.getBuilderId()); if (builder != null) { attributes.put(BUILDER_ID, builder.getBuilderID()); } if (entries == null) { entries = new HashMap<Integer, List<Map<String, Object>>>(); } List<Map<String, Object>> lineEntries = entries.get(lineKey); if (lineEntries == null) { lineEntries = new ArrayList<Map<String, Object>>(); entries.put(lineKey, lineEntries); } lineEntries.add(attributes); if (adjustedMessage != message) throw new ProblemLimit.LimitExceededException(adjustedMessage); } public void clearMessages() { // TODO Auto-generated method stub } public void startMessageGroup(String groupName) { } public void endMessageGroup() { } public void flush(IProgressMonitor monitor) { // Re-use existing markers wherever possible // a) since many rebuilds generate the same errors // b) to avoid a marker being deleted by a refreshMarkers on editor entry before // gotoMarker is invoked to go to a pre-existing context. if (file.exists()) { IWorkspaceRunnable action = new IWorkspaceRunnable() { public void run(IProgressMonitor monitor) throws CoreException { String markerId = PROBLEM_MARKER_ID; if (entries != null) { IMarker[] oldMarkers = file.findMarkers(markerId, false, IFile.DEPTH_ZERO); for (IMarker oldMarker : oldMarkers) { Map<?, ?> oldAttributes = oldMarker.getAttributes(); List<Map<String, Object>> lineEntries = entries.get(oldAttributes.get(IMarker.LINE_NUMBER)); if (lineEntries != null) { for (Map<String, Object> newAttributes : lineEntries) { if (isSameMarker(oldAttributes, newAttributes)) { lineEntries.remove(newAttributes); oldMarker = null; break; } } } if (oldMarker != null) oldMarker.delete(); } for (List<Map<String, Object>> lineEntries : entries.values()) { for (Map<String, Object> entry : lineEntries) { IMarker marker = file.createMarker(markerId); marker.setAttributes(entry); } } } else file.deleteMarkers(markerId, false, IFile.DEPTH_ZERO); } }; try { // TODO: Allow for the introduction of a non-null progress monitor // IProgressMonitor progressMonitor = monitor != null ? BasicMonitor.toIProgressMonitor(monitor) : new NullProgressMonitor(); IProgressMonitor progressMonitor = new NullProgressMonitor(); file.getWorkspace().run(action, file, IWorkspace.AVOID_UPDATE, progressMonitor); } catch (CoreException e) { ErrorHandler.logError("Failed to update file markers", e); } } } public void handleSimpleMessage( String msg, int startOffset, int endOffset, int startCol, int endCol, int startLine, int endLine) { try { addMarker(IMarker.SEVERITY_ERROR, msg, startLine, startOffset, endOffset+1); } catch (LimitExceededException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * Return true if newAttributes and oldAttributes provide the same marker description. */ public boolean isSameMarker(Map<?, ?> oldAttributes, Map<String, Object> newAttributes) { Set<?> oldKeys = oldAttributes.keySet(); Set<String> newKeys = newAttributes.keySet(); if (oldKeys.size() != newKeys.size()) return false; for (String key : newKeys) { if (!oldAttributes.containsKey(key)) return false; Object oldValue = oldAttributes.get(key); Object newValue = newAttributes.get(key); if (oldValue == newValue) continue; if (oldValue == null) return false; if (newValue == null) return false; if (!oldValue.equals(newValue)) return false; } return true; } public void setProblemLimit(ProblemLimit problemLimit) { this.problemLimit = problemLimit; } public void setPage(String page) { this.page = page; } }