| // 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 domain.execution; |
| |
| import 'dart:async'; |
| import 'dart:collection'; |
| import 'dart:core' hide Resource; |
| |
| import 'package:analysis_server/src/analysis_server.dart'; |
| import 'package:analysis_server/src/constants.dart'; |
| import 'package:analysis_server/src/protocol.dart'; |
| import 'package:analyzer/file_system/file_system.dart'; |
| import 'package:analyzer/src/generated/engine.dart'; |
| import 'package:analyzer/src/generated/source.dart'; |
| |
| /** |
| * Instances of the class [ExecutionDomainHandler] implement a [RequestHandler] |
| * that handles requests in the `execution` domain. |
| */ |
| class ExecutionDomainHandler implements RequestHandler { |
| /** |
| * The analysis server that is using this handler to process requests. |
| */ |
| final AnalysisServer server; |
| |
| /** |
| * The next execution context identifier to be returned. |
| */ |
| int nextContextId = 0; |
| |
| /** |
| * A table mapping execution context id's to the root of the context. |
| */ |
| Map<String, String> contextMap = new HashMap<String, String>(); |
| |
| /** |
| * The subscription to the 'onAnalysisComplete' events, |
| * used to send notifications when |
| */ |
| StreamSubscription onFileAnalyzed; |
| |
| /** |
| * Initialize a newly created handler to handle requests for the given [server]. |
| */ |
| ExecutionDomainHandler(this.server); |
| |
| /** |
| * Implement the `execution.createContext` request. |
| */ |
| Response createContext(Request request) { |
| String file = |
| new ExecutionCreateContextParams.fromRequest(request).contextRoot; |
| String contextId = (nextContextId++).toString(); |
| contextMap[contextId] = file; |
| return new ExecutionCreateContextResult(contextId).toResponse(request.id); |
| } |
| |
| /** |
| * Implement the `execution.deleteContext` request. |
| */ |
| Response deleteContext(Request request) { |
| String contextId = new ExecutionDeleteContextParams.fromRequest(request).id; |
| contextMap.remove(contextId); |
| return new ExecutionDeleteContextResult().toResponse(request.id); |
| } |
| |
| @override |
| Response handleRequest(Request request) { |
| try { |
| String requestName = request.method; |
| if (requestName == EXECUTION_CREATE_CONTEXT) { |
| return createContext(request); |
| } else if (requestName == EXECUTION_DELETE_CONTEXT) { |
| return deleteContext(request); |
| } else if (requestName == EXECUTION_MAP_URI) { |
| return mapUri(request); |
| } else if (requestName == EXECUTION_SET_SUBSCRIPTIONS) { |
| return setSubscriptions(request); |
| } |
| } on RequestFailure catch (exception) { |
| return exception.response; |
| } |
| return null; |
| } |
| |
| /** |
| * Implement the 'execution.mapUri' request. |
| */ |
| Response mapUri(Request request) { |
| ExecutionMapUriParams params = |
| new ExecutionMapUriParams.fromRequest(request); |
| String contextId = params.id; |
| String path = contextMap[contextId]; |
| if (path == null) { |
| return new Response.invalidParameter(request, 'id', |
| 'There is no execution context with an id of $contextId'); |
| } |
| AnalysisContext context = server.getContainingContext(path); |
| if (context == null) { |
| return new Response.invalidExecutionContext(request, contextId); |
| } |
| String file = params.file; |
| String uri = params.uri; |
| if (file != null) { |
| if (uri != null) { |
| return new Response.invalidParameter(request, 'file', |
| 'Either file or uri must be provided, but not both'); |
| } |
| Resource resource = server.resourceProvider.getResource(file); |
| if (!resource.exists) { |
| return new Response.invalidParameter(request, 'file', 'Must exist'); |
| } else if (resource is! File) { |
| return new Response.invalidParameter( |
| request, 'file', 'Must not refer to a directory'); |
| } |
| ContextSourcePair contextSource = server.getContextSourcePair(file); |
| Source source = contextSource.source; |
| uri = context.sourceFactory.restoreUri(source).toString(); |
| return new ExecutionMapUriResult(uri: uri).toResponse(request.id); |
| } else if (uri != null) { |
| Source source = context.sourceFactory.forUri(uri); |
| if (source == null) { |
| return new Response.invalidParameter(request, 'uri', 'Invalid URI'); |
| } |
| file = source.fullName; |
| return new ExecutionMapUriResult(file: file).toResponse(request.id); |
| } |
| return new Response.invalidParameter( |
| request, 'file', 'Either file or uri must be provided'); |
| } |
| |
| /** |
| * Implement the 'execution.setSubscriptions' request. |
| */ |
| Response setSubscriptions(Request request) { |
| List<ExecutionService> subscriptions = |
| new ExecutionSetSubscriptionsParams.fromRequest(request).subscriptions; |
| if (subscriptions.contains(ExecutionService.LAUNCH_DATA)) { |
| if (onFileAnalyzed == null) { |
| onFileAnalyzed = server.onFileAnalyzed.listen(_fileAnalyzed); |
| _reportCurrentFileStatus(); |
| } |
| } else { |
| if (onFileAnalyzed != null) { |
| onFileAnalyzed.cancel(); |
| onFileAnalyzed = null; |
| } |
| } |
| return new ExecutionSetSubscriptionsResult().toResponse(request.id); |
| } |
| |
| void _fileAnalyzed(ChangeNotice notice) { |
| ServerPerformanceStatistics.executionNotifications.makeCurrentWhile(() { |
| Source source = notice.source; |
| String filePath = source.fullName; |
| // check files |
| bool isDartFile = notice.resolvedDartUnit != null; |
| bool isHtmlFile = notice.resolvedHtmlUnit != null; |
| if (!isDartFile && !isHtmlFile) { |
| return; |
| } |
| // prepare context |
| AnalysisContext context = server.getContainingContext(filePath); |
| if (context == null) { |
| return; |
| } |
| // analyze the file |
| if (isDartFile) { |
| ExecutableKind kind = ExecutableKind.NOT_EXECUTABLE; |
| if (context.isClientLibrary(source)) { |
| kind = ExecutableKind.CLIENT; |
| if (context.isServerLibrary(source)) { |
| kind = ExecutableKind.EITHER; |
| } |
| } else if (context.isServerLibrary(source)) { |
| kind = ExecutableKind.SERVER; |
| } |
| server.sendNotification( |
| new ExecutionLaunchDataParams(filePath, kind: kind) |
| .toNotification()); |
| } else if (isHtmlFile) { |
| List<Source> libraries = context.getLibrariesReferencedFromHtml(source); |
| server.sendNotification(new ExecutionLaunchDataParams(filePath, |
| referencedFiles: _getFullNames(libraries)).toNotification()); |
| } |
| }); |
| } |
| |
| /** |
| * Return `true` if the given [filePath] represents a file that is in an |
| * analysis root. |
| */ |
| bool _isInAnalysisRoot(String filePath) => |
| server.contextDirectoryManager.isInAnalysisRoot(filePath); |
| |
| void _reportCurrentFileStatus() { |
| for (AnalysisContext context in server.getAnalysisContexts()) { |
| List<Source> librarySources = context.librarySources; |
| List<Source> clientSources = context.launchableClientLibrarySources; |
| List<Source> serverSources = context.launchableServerLibrarySources; |
| for (Source source in clientSources) { |
| if (serverSources.remove(source)) { |
| _sendKindNotification(source.fullName, ExecutableKind.EITHER); |
| } else { |
| _sendKindNotification(source.fullName, ExecutableKind.CLIENT); |
| } |
| librarySources.remove(source); |
| } |
| for (Source source in serverSources) { |
| _sendKindNotification(source.fullName, ExecutableKind.SERVER); |
| librarySources.remove(source); |
| } |
| for (Source source in librarySources) { |
| _sendKindNotification(source.fullName, ExecutableKind.NOT_EXECUTABLE); |
| } |
| for (Source source in context.htmlSources) { |
| String filePath = source.fullName; |
| if (_isInAnalysisRoot(filePath)) { |
| List<Source> libraries = |
| context.getLibrariesReferencedFromHtml(source); |
| server.sendNotification(new ExecutionLaunchDataParams(filePath, |
| referencedFiles: _getFullNames(libraries)).toNotification()); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Send a notification indicating the [kind] of the file with the given |
| * [filePath], but only if the file is in an analysis root. |
| */ |
| void _sendKindNotification(String filePath, ExecutableKind kind) { |
| if (_isInAnalysisRoot(filePath)) { |
| server.sendNotification( |
| new ExecutionLaunchDataParams(filePath, kind: kind).toNotification()); |
| } |
| } |
| |
| static List<String> _getFullNames(List<Source> sources) { |
| return sources.map((Source source) => source.fullName).toList(); |
| } |
| } |