blob: 7af6afdc1ea2e412136784576dc2777641049bff [file] [log] [blame]
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library analysis.server;
import 'dart:async';
import 'package:analysis_server/src/analysis_logger.dart';
import 'package:analysis_server/src/channel.dart';
import 'package:analysis_server/src/protocol.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/error.dart';
import 'package:analyzer/src/generated/java_core.dart';
/**
* Instances of the class [AnalysisServer] implement a server that listens on a
* [CommunicationChannel] for analysis requests and process them.
*/
class AnalysisServer {
/**
* The name of the notification of new errors associated with a source.
*/
static const String ERROR_NOTIFICATION_NAME = 'context.errors';
/**
* The name of the parameter whose value is a list of errors.
*/
static const String ERRORS_PARAM = 'errors';
/**
* The name of the parameter whose value is a source.
*/
static const String SOURCE_PARAM = 'source';
/**
* The event name of the connected notification.
*/
static const String CONNECTED_NOTIFICATION = 'server.connected';
/**
* The channel from which requests are received and to which responses should
* be sent.
*/
final ServerCommunicationChannel channel;
/**
* A flag indicating whether the server is running. When false, contexts
* will no longer be added to [contextWorkQueue], and [performTask] will
* discard any tasks it finds on [contextWorkQueue].
*/
bool running;
/**
* A list of the request handlers used to handle the requests sent to this
* server.
*/
List<RequestHandler> handlers;
/**
* A table mapping context id's to the analysis contexts associated with them.
*/
final Map<String, AnalysisContext> contextMap = new Map<String, AnalysisContext>();
/**
* A list of the analysis contexts for which analysis work needs to be
* performed.
*
* Invariant: when this list is non-empty, there is exactly one pending call
* to [performTask] on the event queue. When this list is empty, there are
* no calls to [performTask] on the event queue.
*/
final List<AnalysisContext> contextWorkQueue = new List<AnalysisContext>();
/**
* Initialize a newly created server to receive requests from and send
* responses to the given [channel].
*/
AnalysisServer(this.channel) {
AnalysisEngine.instance.logger = new AnalysisLogger();
running = true;
Notification notification = new Notification(CONNECTED_NOTIFICATION);
channel.sendNotification(notification);
channel.listen(handleRequest, onDone: done, onError: error);
}
/**
* If [running] is true, add the given [context] to the list of analysis
* contexts for which analysis work needs to be performed, and ensure that
* the work will be performed.
*/
void addContextToWorkQueue(AnalysisContext context) {
if (!running) {
return;
}
if (!contextWorkQueue.contains(context)) {
contextWorkQueue.add(context);
if (contextWorkQueue.length == 1) {
// Work queue was previously empty, so schedule analysis.
_scheduleTask();
}
}
}
/**
* The socket from which requests are being read has been closed.
*/
void done() {
running = false;
}
/**
* There was an error related to the socket from which requests are being
* read.
*/
void error(argument) {
running = false;
}
/**
* Handle a [request] that was read from the communication channel.
*/
void handleRequest(Request request) {
int count = handlers.length;
for (int i = 0; i < count; i++) {
try {
Response response = handlers[i].handleRequest(request);
if (response != null) {
channel.sendResponse(response);
return;
}
} on RequestFailure catch (exception) {
channel.sendResponse(exception.response);
return;
}
}
channel.sendResponse(new Response.unknownRequest(request));
}
/**
* Perform the next available task. If a request was received that has not yet
* been performed, perform it next. Otherwise, look for some analysis that
* needs to be done and do that. Otherwise, do nothing.
*/
void performTask() {
if (!running) {
// An error has occurred, or the connection to the client has been
// closed, since performTask() was scheduled on the event queue. So
// don't do any analysis. Instead clear the work queue.
contextWorkQueue.clear();
}
if (contextWorkQueue.isEmpty) {
// Nothing to do.
return;
}
//
// Look for a context that has work to be done and then perform one task.
//
List<ChangeNotice> notices = null;
try {
AnalysisContext context = contextWorkQueue[0];
AnalysisResult result = context.performAnalysisTask();
notices = result.changeNotices;
} finally {
if (notices == null) {
// Either we have no more work to do for this context, or there was an
// unhandled exception trying to perform the analysis. In either case,
// remove the context form the work queue so we won't try to do more
// analysis on it.
contextWorkQueue.removeAt(0);
}
//
// Schedule this method to be run again if there is any more work to be
// done.
//
if (!contextWorkQueue.isEmpty) {
_scheduleTask();
}
}
if (notices != null) {
sendNotices(notices);
}
}
/**
* Send the information in the given list of notices back to the client.
*/
void sendNotices(List<ChangeNotice> notices) {
for (int i = 0; i < notices.length; i++) {
ChangeNotice notice = notices[i];
Notification notification = new Notification(ERROR_NOTIFICATION_NAME);
notification.setParameter(SOURCE_PARAM, notice.source.encoding);
notification.setParameter(ERRORS_PARAM, notice.errors.map(
errorToJson).toList());
sendNotification(notification);
}
}
static Map<String, Object> errorToJson(AnalysisError analysisError) {
// TODO(paulberry): move this function into the AnalysisError class.
// TODO(paulberry): we really shouldn't be exposing errorCode.ordinal
// outside the analyzer, since the ordinal numbers change whenever we
// regenerate the analysis engine.
Map<String, Object> result = {
'source': analysisError.source.encoding,
'errorCode': (analysisError.errorCode as Enum).ordinal,
'offset': analysisError.offset,
'length': analysisError.length,
'message': analysisError.message
};
if (analysisError.correction != null) {
result['correction'] = analysisError.correction;
}
return result;
}
/**
* Send the given [notification] to the client.
*/
void sendNotification(Notification notification) {
channel.sendNotification(notification);
}
void _scheduleTask() {
new Future(performTask).catchError((ex, st) {
AnalysisEngine.instance.logger.logError("${ex}\n${st}");
});
}
}