/*
* Copyright (c) 2014, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.tools.core.internal.builder;
import com.google.dart.tools.core.DartCore;
import com.google.dart.tools.core.internal.analysis.model.WorkspaceAnalysisServerListener;
import org.dartlang.analysis.server.protocol.AnalysisError;
import org.dartlang.analysis.server.protocol.AnalysisErrorSeverity;
import org.dartlang.analysis.server.protocol.AnalysisErrorType;
import org.dartlang.analysis.server.protocol.Location;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import java.util.ArrayList;
/**
* Instances of {@code AnalysisMarkerManager} queue {@link AnalysisError}s from sources such as
* {@link AnalysisWorker} and translate those errors into Eclipse markers on a separate thread.
* There is a single instance accessible via {@link #getInstance()} for use during normal execution,
* but other instances can be created for testing purposes.
* <p>
* Typically the {@link WorkspaceAnalysisServerListener} repeatedly calls
* {@link #queueErrors(IResource, AnalysisError[])} until all errors have been queued.
* <p>
* When the workspace is shutdown, {@link #stop()} should be called to gracefully exit the
* background process if it is running.
*
* @coverage dart.tools.core.builder
*/
public class AnalysisMarkerManager_NEW {
/**
* Errors to be translated into markers
*/
private static final class ErrorResult implements Result {
final IResource resource;
final AnalysisError[] errors;
ErrorResult(IResource resource, AnalysisError[] errors) {
this.resource = resource;
this.errors = errors;
}
@Override
public IResource getResource() {
return resource;
}
@Override
public void showErrors() throws CoreException {
if (!resource.isAccessible()) {
return;
}
resource.deleteMarkers(DartCore.DART_PROBLEM_MARKER_TYPE, true, IResource.DEPTH_ZERO);
resource.deleteMarkers(DartCore.DART_TASK_MARKER_TYPE, true, IResource.DEPTH_ZERO);
resource.deleteMarkers(DartCore.ANGULAR_WARNING_MARKER_TYPE, true, IResource.DEPTH_ZERO);
// Ignore if user requested to don't analyze resource.
if (!DartCore.isAnalyzed(resource)) {
return;
}
// Show errors first, then warnings, followed by everything else
// while limiting the total number of markers added to MAX_ERROR_COUNT
int errorCount = 0;
errorCount = showErrors(errorCount, AnalysisErrorSeverity.ERROR, IMarker.SEVERITY_ERROR);
errorCount = showErrors(errorCount, AnalysisErrorSeverity.WARNING, IMarker.SEVERITY_WARNING);
errorCount = showErrors(errorCount, AnalysisErrorSeverity.INFO, IMarker.SEVERITY_INFO);
if (errorCount >= MAX_ERROR_COUNT) {
IMarker marker = resource.createMarker(DartCore.DART_PROBLEM_MARKER_TYPE);
marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_WARNING);
marker.setAttribute(IMarker.LINE_NUMBER, 1);
marker.setAttribute(IMarker.MESSAGE, "There are more then " + MAX_ERROR_COUNT
+ " errors; not showing any more...");
}
}
private int showErrors(int errorCount, String errorSeverity, int markerSeverity)
throws CoreException {
for (AnalysisError error : errors) {
if (!error.getSeverity().equals(errorSeverity)) {
continue;
}
Location location = error.getLocation();
String errorType = error.getType();
boolean isHint = errorType.equals(AnalysisErrorType.HINT);
String markerType = DartCore.DART_PROBLEM_MARKER_TYPE;
// Server doesn't have the angular error type
// if (errorCode.getType() == ErrorType.ANGULAR) {
// markerType = DartCore.ANGULAR_WARNING_MARKER_TYPE;
// markerSeverity = IMarker.SEVERITY_WARNING;
// } else
if (errorType.equals(AnalysisErrorType.TODO)) {
markerType = DartCore.DART_TASK_MARKER_TYPE;
} else if (isHint) {
markerType = DartCore.DART_HINT_MARKER_TYPE;
}
IMarker marker = resource.createMarker(markerType);
marker.setAttribute(IMarker.SEVERITY, markerSeverity);
marker.setAttribute(IMarker.CHAR_START, location.getOffset());
marker.setAttribute(IMarker.CHAR_END, location.getOffset() + location.getLength());
marker.setAttribute(IMarker.LINE_NUMBER, location.getStartLine());
marker.setAttribute(IMarker.MESSAGE, error.getMessage());
marker.setAttribute(DartCore.MARKER_ATTR_CORRECTION, error.getCorrection());
if (isHint) {
marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH);
}
errorCount++;
if (errorCount >= MAX_ERROR_COUNT) {
break;
}
}
return errorCount;
}
}
/**
* Add/remove marker indicating that a particular project has an SDK associated with it
*/
private final class HasSdkResult implements Result {
private final IProject project;
private final boolean hasSdk;
public HasSdkResult(IProject project, boolean hasSdk) {
this.project = project;
this.hasSdk = hasSdk;
}
@Override
public IResource getResource() {
return project;
}
@Override
public void showErrors() throws CoreException {
if (!project.isAccessible()) {
return;
}
project.deleteMarkers(DartCore.DART_PROBLEM_MARKER_TYPE, true, IResource.DEPTH_ZERO);
if (!hasSdk) {
IMarker marker = project.createMarker(DartCore.DART_PROBLEM_MARKER_TYPE);
marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
marker.setAttribute(IMarker.CHAR_START, 0);
marker.setAttribute(IMarker.CHAR_END, 0);
marker.setAttribute(IMarker.LINE_NUMBER, 1);
//TODO (danrubel): improve error message to indicate action to install SDK
marker.setAttribute(IMarker.MESSAGE, "Missing Dart SDK");
//TODO (danrubel): Quick Fix ?
}
}
}
/**
* Results to be translated into markers
*/
private interface Result {
/**
* The resource for which markers are being added / removed.
*
* @return the resource
*/
IResource getResource();
/**
* Set markers on the specified resource to represent the cached analysis errors
*/
void showErrors() throws CoreException;
}
private static final int MAX_ERROR_COUNT = 500;
/**
* The singleton used for translating {@link AnalysisError}s into Eclipse markers.
*/
private static final AnalysisMarkerManager_NEW INSTANCE = new AnalysisMarkerManager_NEW(
ResourcesPlugin.getWorkspace());
/**
* Answer the singleton used for translating {@link AnalysisError}s into Eclipse markers.
*
* @return the marker manager (not {@code null})
*/
public static AnalysisMarkerManager_NEW getInstance() {
return INSTANCE;
}
/**
* The workspace used to batch translation of errors to Eclipse markers (not {@code null}).
*/
private final IWorkspace workspace;
/**
* The progress monitor used for canceling the background process.
*/
private final NullProgressMonitor monitor = new NullProgressMonitor();
/**
* Synchronize against this object before accessing private fields and method in this class.
*/
private final Object lock = new Object();
/**
* A queue of results to be displayed.
* <p>
* Note: Only access this field while synchronized on {@link #lock}.
*/
private ArrayList<Result> results;
/**
* The time when {@link #results} was updated last time.
*/
private long lastResultsUpdate = 0;
/**
* The background thread that translates {@link AnalysisError}s into Eclipse markers or
* {@code null} if either {@link #translateErrors()} has not been called or background processing
* is complete and there are no new errors to translate.
* <p>
* Note: Only access this field while synchronized on {@link #lock}.
*/
private Thread updateThread;
/**
* Used exclusively by the background thread during translation. Should not be accessed in any
* other code.
*/
private ArrayList<Result> resultsBeingTranslated;
/**
* Construct a new instance for translating errors to markers using the specified workspace.
*/
public AnalysisMarkerManager_NEW(IWorkspace workspace) {
this.workspace = workspace;
}
/**
* Call this to clear markers and remove resource from error queue
*/
public void clearMarkers(IResource resource) {
//TODO(keertip): remove resource from queue
try {
if (resource.isAccessible()) {
if (resource instanceof IContainer) {
resource.deleteMarkers(null, false, IResource.DEPTH_INFINITE);
} else {
resource.deleteMarkers(null, false, IResource.DEPTH_ZERO);
}
}
} catch (Exception e) {
DartCore.logError(e);
}
}
/**
* Queue the specified errors for later translation to Eclipse markers.
*
* @param resource the resource on which the errors should be displayed (not {@code null})
* @param errors the errors to be translated (not {@code null}, contains no {@code null}s)
*/
public void queueErrors(IResource resource, AnalysisError[] errors) {
queueResult(new ErrorResult(resource, errors));
lastResultsUpdate = System.currentTimeMillis();
}
/**
* Queue the specified information about whether the project has a Dart SDK associated with it so
* that the information can be translated into an Eclipse marker at a later time.
*
* @param resource the resource (not {@code null})
* @param hasSdk {@code true} if there is a Dart SDK, else {@code false}
*/
public void queueHasDartSdk(IResource resource, boolean hasSdk) {
IProject project = resource.getProject();
// workspace root getProject() returns null
if (project != null) {
queueResult(new HasSdkResult(project, hasSdk));
}
}
/**
* Call this method to cancel the background thread.
*/
public void stop() {
monitor.setCanceled(true);
}
/**
* Queue the specified result for later translation to Eclipse markers.
*
* @param result the result to be translated (not {@code null})
*/
private void queueResult(Result result) {
synchronized (lock) {
// queue the errors to be translated
if (results == null) {
results = new ArrayList<Result>();
}
results.add(result);
// kick off a background thread if one has not already been started
if (updateThread == null) {
updateThread = new Thread(getClass().getSimpleName()) {
@Override
public void run() {
translateErrors();
}
};
updateThread.start();
}
}
}
/**
* Call this on the background thread to translate errors into Eclipse markers.
*/
private void translateErrors() {
while (true) {
synchronized (lock) {
try {
lock.wait(50);
} catch (InterruptedException e) {
//$FALL-THROUGH$
}
// Exit if nothing to translate
if (results == null) {
updateThread = null;
return;
}
// Wait at least 45 milliseconds to get more errors to translate
if (System.currentTimeMillis() - lastResultsUpdate < 45) {
continue;
}
// Grab the current collection of results to be translated
resultsBeingTranslated = results;
results = null;
}
// Batch translation of the errors
IWorkspaceRunnable op = new IWorkspaceRunnable() {
@Override
public void run(IProgressMonitor monitor) {
for (Result result : resultsBeingTranslated) {
if (monitor.isCanceled()) {
//TODO (danrubel): Investigate pushing remaining work back on the queue
// or serializing it on shutdown
break;
}
try {
result.showErrors();
} catch (CoreException e) {
DartCore.logError("Failed to show errors for " + result.getResource(), e);
}
}
resultsBeingTranslated = null;
}
};
try {
workspace.run(op, workspace.getRoot(), IWorkspace.AVOID_UPDATE, monitor);
} catch (CoreException e) {
DartCore.logError("Exception translating analysis errors to markers", e);
} catch (NullPointerException e) {
// Suppress this error, it happens because of workspace shutdown.
}
}
}
}