// Copyright (c) 2016, 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 analyzer.src.generated.sdk2;

import 'dart:collection';
import 'dart:convert';
import 'dart:io' as io;

import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/error/listener.dart';
import 'package:analyzer/exception/exception.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/file_system/memory_file_system.dart';
import 'package:analyzer/src/context/context.dart';
import 'package:analyzer/src/dart/scanner/reader.dart';
import 'package:analyzer/src/dart/scanner/scanner.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/java_engine_io.dart';
import 'package:analyzer/src/generated/parser.dart';
import 'package:analyzer/src/generated/sdk.dart';
import 'package:analyzer/src/generated/source_io.dart';
import 'package:analyzer/src/summary/idl.dart' show PackageBundle;
import 'package:analyzer/src/summary/package_bundle_reader.dart';
import 'package:path/path.dart' as pathos;
import 'package:yaml/yaml.dart';

/**
 * An abstract implementation of a Dart SDK in which the available libraries are
 * stored in a library map. Subclasses are responsible for populating the
 * library map.
 */
abstract class AbstractDartSdk implements DartSdk {
  /**
   * The resource provider used to access the file system.
   */
  ResourceProvider resourceProvider;

  /**
   * A mapping from Dart library URI's to the library represented by that URI.
   */
  LibraryMap libraryMap = new LibraryMap();

  /**
   * The [AnalysisOptions] to use to create the [context].
   */
  AnalysisOptions _analysisOptions;

  /**
   * The flag that specifies whether an SDK summary should be used. This is a
   * temporary flag until summaries are enabled by default.
   */
  bool _useSummary = false;

  /**
   * The [AnalysisContext] which is used for all of the sources in this SDK.
   */
  InternalAnalysisContext _analysisContext;

  /**
   * The mapping from Dart URI's to the corresponding sources.
   */
  Map<String, Source> _uriToSourceMap = new HashMap<String, Source>();

  PackageBundle _sdkBundle;

  /**
   * Return the analysis options for this SDK analysis context.
   */
  AnalysisOptions get analysisOptions => _analysisOptions;

  /**
   * Set the [options] for this SDK analysis context.  Throw [StateError] if the
   * context has been already created.
   */
  void set analysisOptions(AnalysisOptions options) {
    if (_analysisContext != null) {
      throw new StateError(
          'Analysis options cannot be changed after context creation.');
    }
    _analysisOptions = options;
  }

  @override
  AnalysisContext get context {
    if (_analysisContext == null) {
      _analysisContext = new SdkAnalysisContext(_analysisOptions);
      SourceFactory factory = new SourceFactory([new DartUriResolver(this)]);
      _analysisContext.sourceFactory = factory;
      if (_useSummary) {
        PackageBundle sdkBundle = getLinkedBundle();
        if (sdkBundle != null) {
          SummaryDataStore dataStore =
              new SummaryDataStore([], resourceProvider: resourceProvider);
          dataStore.addBundle(null, sdkBundle);
          _analysisContext.resultProvider =
              new InputPackagesResultProvider(_analysisContext, dataStore);
        }
      }
    }
    return _analysisContext;
  }

  @override
  List<SdkLibrary> get sdkLibraries => libraryMap.sdkLibraries;

  /**
   * Return the path separator used by the resource provider.
   */
  String get separator => resourceProvider.pathContext.separator;

  @override
  List<String> get uris => libraryMap.uris;

  /**
   * Return `true` if the SDK summary will be used when available.
   */
  bool get useSummary => _useSummary;

  /**
   * Specify whether SDK summary should be used.
   */
  void set useSummary(bool use) {
    if (_analysisContext != null) {
      throw new StateError(
          'The "useSummary" flag cannot be changed after context creation.');
    }
    _useSummary = use;
  }

  /**
   * Add the extensions from one or more sdk extension files to this sdk. The
   * [extensions] should be a table mapping the names of extensions to the paths
   * where those extensions can be found.
   */
  void addExtensions(Map<String, String> extensions) {
    extensions.forEach((String uri, String path) {
      SdkLibraryImpl library = new SdkLibraryImpl(uri);
      library.path = path;
      libraryMap.setLibrary(uri, library);
    });
  }

  @override
  Source fromFileUri(Uri uri) {
    File file =
        resourceProvider.getFile(resourceProvider.pathContext.fromUri(uri));
    String path = _getPath(file);
    if (path == null) {
      return null;
    }
    try {
      return file.createSource(Uri.parse(path));
    } on FormatException catch (exception, stackTrace) {
      AnalysisEngine.instance.logger.logInformation(
          "Failed to create URI: $path",
          new CaughtException(exception, stackTrace));
    }
    return null;
  }

  @override
  PackageBundle getLinkedBundle() {
    if (_useSummary) {
      bool strongMode = _analysisOptions?.strongMode ?? false;
      _sdkBundle ??= getSummarySdkBundle(strongMode);
      return _sdkBundle;
    }
    return null;
  }

  String getRelativePathFromFile(File file);

  @override
  SdkLibrary getSdkLibrary(String dartUri) => libraryMap.getLibrary(dartUri);

  /**
   * Return the [PackageBundle] for this SDK, if it exists, or `null` otherwise.
   * This method should not be used outside of `analyzer` and `analyzer_cli`
   * packages.
   */
  PackageBundle getSummarySdkBundle(bool strongMode);

  Source internalMapDartUri(String dartUri) {
    // TODO(brianwilkerson) Figure out how to unify the implementations in the
    // two subclasses.
    String libraryName;
    String relativePath;
    int index = dartUri.indexOf('/');
    if (index >= 0) {
      libraryName = dartUri.substring(0, index);
      relativePath = dartUri.substring(index + 1);
    } else {
      libraryName = dartUri;
      relativePath = "";
    }
    SdkLibrary library = getSdkLibrary(libraryName);
    if (library == null) {
      return null;
    }
    String srcPath;
    if (relativePath.isEmpty) {
      srcPath = library.path;
    } else {
      String libraryPath = library.path;
      int index = libraryPath.lastIndexOf(separator);
      if (index == -1) {
        index = libraryPath.lastIndexOf('/');
        if (index == -1) {
          return null;
        }
      }
      String prefix = libraryPath.substring(0, index + 1);
      srcPath = '$prefix$relativePath';
    }
    String filePath = srcPath.replaceAll('/', separator);
    try {
      File file = resourceProvider.getFile(filePath);
      return file.createSource(Uri.parse(dartUri));
    } on FormatException {
      return null;
    }
  }

  @override
  Source mapDartUri(String dartUri) {
    Source source = _uriToSourceMap[dartUri];
    if (source == null) {
      source = internalMapDartUri(dartUri);
      _uriToSourceMap[dartUri] = source;
    }
    return source;
  }

  String _getPath(File file) {
    List<SdkLibrary> libraries = libraryMap.sdkLibraries;
    int length = libraries.length;
    List<String> paths = new List(length);
    String filePath = getRelativePathFromFile(file);
    if (filePath == null) {
      return null;
    }
    for (int i = 0; i < length; i++) {
      SdkLibrary library = libraries[i];
      String libraryPath = library.path.replaceAll('/', separator);
      if (filePath == libraryPath) {
        return library.shortName;
      }
      paths[i] = libraryPath;
    }
    for (int i = 0; i < length; i++) {
      SdkLibrary library = libraries[i];
      String libraryPath = paths[i];
      int index = libraryPath.lastIndexOf(separator);
      if (index >= 0) {
        String prefix = libraryPath.substring(0, index + 1);
        if (filePath.startsWith(prefix)) {
          String relPath =
              filePath.substring(prefix.length).replaceAll(separator, '/');
          return '${library.shortName}/$relPath';
        }
      }
    }
    return null;
  }
}

/**
 * An SDK backed by URI mappings derived from an `_embedder.yaml` file.
 */
class EmbedderSdk extends AbstractDartSdk {
  static const String _DART_COLON_PREFIX = 'dart:';

  static const String _EMBEDDED_LIB_MAP_KEY = 'embedded_libs';
  final Map<String, String> _urlMappings = new HashMap<String, String>();

  Folder _embedderYamlLibFolder;

  EmbedderSdk(
      ResourceProvider resourceProvider, Map<Folder, YamlMap> embedderYamls) {
    this.resourceProvider = resourceProvider;
    embedderYamls?.forEach(_processEmbedderYaml);
    if (embedderYamls?.length == 1) {
      _embedderYamlLibFolder = embedderYamls.keys.first;
    }
  }

  @override
  // TODO(danrubel) Determine SDK version
  String get sdkVersion => '0';

  /**
   * The url mappings for this SDK.
   */
  Map<String, String> get urlMappings => _urlMappings;

  @override
  String getRelativePathFromFile(File file) => file.path;

  @override
  PackageBundle getSummarySdkBundle(bool strongMode) {
    String name = strongMode ? 'strong.sum' : 'spec.sum';
    File file = _embedderYamlLibFolder.parent.getChildAssumingFile(name);
    try {
      if (file.exists) {
        List<int> bytes = file.readAsBytesSync();
        return new PackageBundle.fromBuffer(bytes);
      }
    } catch (exception, stackTrace) {
      AnalysisEngine.instance.logger.logError(
          'Failed to load SDK analysis summary from $file',
          new CaughtException(exception, stackTrace));
    }
    return null;
  }

  @override
  Source internalMapDartUri(String dartUri) {
    String libraryName;
    String relativePath;
    int index = dartUri.indexOf('/');
    if (index >= 0) {
      libraryName = dartUri.substring(0, index);
      relativePath = dartUri.substring(index + 1);
    } else {
      libraryName = dartUri;
      relativePath = "";
    }
    SdkLibrary library = getSdkLibrary(libraryName);
    if (library == null) {
      return null;
    }
    String srcPath;
    if (relativePath.isEmpty) {
      srcPath = library.path;
    } else {
      String libraryPath = library.path;
      int index = libraryPath.lastIndexOf(separator);
      if (index == -1) {
        index = libraryPath.lastIndexOf('/');
        if (index == -1) {
          return null;
        }
      }
      String prefix = libraryPath.substring(0, index + 1);
      srcPath = '$prefix$relativePath';
    }
    String filePath = srcPath.replaceAll('/', separator);
    try {
      File file = resourceProvider.getFile(filePath);
      return file.createSource(Uri.parse(dartUri));
    } on FormatException {
      return null;
    }
  }

  /**
   * Install the mapping from [name] to [libDir]/[file].
   */
  void _processEmbeddedLibs(String name, String file, Folder libDir) {
    if (!name.startsWith(_DART_COLON_PREFIX)) {
      // SDK libraries must begin with 'dart:'.
      return;
    }
    String libPath = libDir.canonicalizePath(file);
    _urlMappings[name] = libPath;
    SdkLibraryImpl library = new SdkLibraryImpl(name);
    library.path = libPath;
    libraryMap.setLibrary(name, library);
  }

  /**
   * Given the 'embedderYamls' from [EmbedderYamlLocator] check each one for the
   * top level key 'embedded_libs'. Under the 'embedded_libs' key are key value
   * pairs. Each key is a 'dart:' library uri and each value is a path
   * (relative to the directory containing `_embedder.yaml`) to a dart script
   * for the given library. For example:
   *
   * embedded_libs:
   *   'dart:io': '../../sdk/io/io.dart'
   *
   * If a key doesn't begin with `dart:` it is ignored.
   */
  void _processEmbedderYaml(Folder libDir, YamlMap map) {
    YamlNode embedded_libs = map[_EMBEDDED_LIB_MAP_KEY];
    if (embedded_libs is YamlMap) {
      embedded_libs.forEach((k, v) => _processEmbeddedLibs(k, v, libDir));
    }
  }
}

/**
 * A Dart SDK installed in a specified directory. Typical Dart SDK layout is
 * something like...
 *
 *     dart-sdk/
 *        bin/
 *           dart[.exe]  <-- VM
 *        lib/
 *           core/
 *              core.dart
 *              ... other core library files ...
 *           ... other libraries ...
 *        util/
 *           ... Dart utilities ...
 *     Chromium/   <-- Dartium typically exists in a sibling directory
 */
class FolderBasedDartSdk extends AbstractDartSdk {
  /**
   * The name of the directory within the SDK directory that contains
   * executables.
   */
  static String _BIN_DIRECTORY_NAME = "bin";

  /**
   * The name of the directory within the SDK directory that contains
   * documentation for the libraries.
   */
  static String _DOCS_DIRECTORY_NAME = "docs";

  /**
   * The name of the directory within the SDK directory that contains the
   * sdk_library_metadata directory.
   */
  static String _INTERNAL_DIR = "_internal";

  /**
   * The name of the sdk_library_metadata directory that contains the package
   * holding the libraries.dart file.
   */
  static String _SDK_LIBRARY_METADATA_DIR = "sdk_library_metadata";

  /**
   * The name of the directory within the sdk_library_metadata that contains
   * libraries.dart.
   */
  static String _SDK_LIBRARY_METADATA_LIB_DIR = "lib";

  /**
   * The name of the directory within the SDK directory that contains the
   * libraries.
   */
  static String _LIB_DIRECTORY_NAME = "lib";

  /**
   * The name of the libraries file.
   */
  static String _LIBRARIES_FILE = "libraries.dart";

  /**
   * The name of the pub executable on windows.
   */
  static String _PUB_EXECUTABLE_NAME_WIN = "pub.bat";

  /**
   * The name of the pub executable on non-windows operating systems.
   */
  static String _PUB_EXECUTABLE_NAME = "pub";

  /**
   * The name of the file within the SDK directory that contains the version
   * number of the SDK.
   */
  static String _VERSION_FILE_NAME = "version";

  /**
   * The directory containing the SDK.
   */
  Folder _sdkDirectory;

  /**
   * The directory within the SDK directory that contains the libraries.
   */
  Folder _libraryDirectory;

  /**
   * The revision number of this SDK, or `"0"` if the revision number cannot be
   * discovered.
   */
  String _sdkVersion;

  /**
   * The file containing the pub executable.
   */
  File _pubExecutable;

  /**
   * Initialize a newly created SDK to represent the Dart SDK installed in the
   * [sdkDirectory]. The flag [useDart2jsPaths] is `true` if the dart2js path
   * should be used when it is available
   */
  FolderBasedDartSdk(ResourceProvider resourceProvider, this._sdkDirectory,
      [bool useDart2jsPaths = false]) {
    this.resourceProvider = resourceProvider;
    libraryMap = initialLibraryMap(useDart2jsPaths);
  }

  /**
   * Return the directory containing the SDK.
   */
  Folder get directory => _sdkDirectory;

  /**
   * Return the directory containing documentation for the SDK.
   */
  Folder get docDirectory =>
      _sdkDirectory.getChildAssumingFolder(_DOCS_DIRECTORY_NAME);

  /**
   * Return the directory within the SDK directory that contains the libraries.
   */
  Folder get libraryDirectory {
    if (_libraryDirectory == null) {
      _libraryDirectory =
          _sdkDirectory.getChildAssumingFolder(_LIB_DIRECTORY_NAME);
    }
    return _libraryDirectory;
  }

  /**
   * Return the file containing the Pub executable, or `null` if it does not exist.
   */
  File get pubExecutable {
    if (_pubExecutable == null) {
      _pubExecutable = _sdkDirectory
          .getChildAssumingFolder(_BIN_DIRECTORY_NAME)
          .getChildAssumingFile(OSUtilities.isWindows()
              ? _PUB_EXECUTABLE_NAME_WIN
              : _PUB_EXECUTABLE_NAME);
    }
    return _pubExecutable;
  }

  /**
   * Return the revision number of this SDK, or `"0"` if the revision number
   * cannot be discovered.
   */
  @override
  String get sdkVersion {
    if (_sdkVersion == null) {
      _sdkVersion = DartSdk.DEFAULT_VERSION;
      File revisionFile =
          _sdkDirectory.getChildAssumingFile(_VERSION_FILE_NAME);
      try {
        String revision = revisionFile.readAsStringSync();
        if (revision != null) {
          _sdkVersion = revision.trim();
        }
      } on FileSystemException {
        // Fall through to return the default.
      }
    }
    return _sdkVersion;
  }

  /**
   * Determine the search order for trying to locate the [_LIBRARIES_FILE].
   */
  Iterable<File> get _libraryMapLocations sync* {
    yield libraryDirectory
        .getChildAssumingFolder(_INTERNAL_DIR)
        .getChildAssumingFolder(_SDK_LIBRARY_METADATA_DIR)
        .getChildAssumingFolder(_SDK_LIBRARY_METADATA_LIB_DIR)
        .getChildAssumingFile(_LIBRARIES_FILE);
    yield libraryDirectory
        .getChildAssumingFolder(_INTERNAL_DIR)
        .getChildAssumingFile(_LIBRARIES_FILE);
  }

  @override
  String getRelativePathFromFile(File file) {
    String filePath = file.path;
    String libPath = libraryDirectory.path;
    if (!filePath.startsWith("$libPath$separator")) {
      return null;
    }
    return filePath.substring(libPath.length + 1);
  }

  /**
   * Return the [PackageBundle] for this SDK, if it exists, or `null` otherwise.
   * This method should not be used outside of `analyzer` and `analyzer_cli`
   * packages.
   */
  PackageBundle getSummarySdkBundle(bool strongMode) {
    String rootPath = directory.path;
    String name = strongMode ? 'strong.sum' : 'spec.sum';
    String path =
        resourceProvider.pathContext.join(rootPath, 'lib', '_internal', name);
    try {
      File file = resourceProvider.getFile(path);
      if (file.exists) {
        List<int> bytes = file.readAsBytesSync();
        return new PackageBundle.fromBuffer(bytes);
      }
    } catch (exception, stackTrace) {
      AnalysisEngine.instance.logger.logError(
          'Failed to load SDK analysis summary from $path',
          new CaughtException(exception, stackTrace));
    }
    return null;
  }

  /**
   * Read all of the configuration files to initialize the library maps. The
   * flag [useDart2jsPaths] is `true` if the dart2js path should be used when it
   * is available. Return the initialized library map.
   */
  LibraryMap initialLibraryMap(bool useDart2jsPaths) {
    List<String> searchedPaths = <String>[];
    var lastStackTrace = null;
    var lastException = null;
    for (File librariesFile in _libraryMapLocations) {
      try {
        String contents = librariesFile.readAsStringSync();
        return new SdkLibrariesReader(useDart2jsPaths)
            .readFromFile(librariesFile, contents);
      } catch (exception, stackTrace) {
        searchedPaths.add(librariesFile.path);
        lastException = exception;
        lastStackTrace = stackTrace;
      }
    }
    StringBuffer buffer = new StringBuffer();
    buffer.writeln('Could not initialize the library map from $searchedPaths');
    if (resourceProvider is MemoryResourceProvider) {
      (resourceProvider as MemoryResourceProvider).writeOn(buffer);
    }
    AnalysisEngine.instance.logger.logError(
        buffer.toString(), new CaughtException(lastException, lastStackTrace));
    return new LibraryMap();
  }

  @override
  Source internalMapDartUri(String dartUri) {
    String libraryName;
    String relativePath;
    int index = dartUri.indexOf('/');
    if (index >= 0) {
      libraryName = dartUri.substring(0, index);
      relativePath = dartUri.substring(index + 1);
    } else {
      libraryName = dartUri;
      relativePath = "";
    }
    SdkLibrary library = getSdkLibrary(libraryName);
    if (library == null) {
      return null;
    }
    try {
      File file = libraryDirectory.getChildAssumingFile(library.path);
      if (!relativePath.isEmpty) {
        File relativeFile = file.parent.getChildAssumingFile(relativePath);
        if (relativeFile.path == file.path) {
          // The relative file is the library, so return a Source for the
          // library rather than the part format.
          return file.createSource(Uri.parse(library.shortName));
        }
        file = relativeFile;
      }
      return file.createSource(Uri.parse(dartUri));
    } on FormatException {
      return null;
    }
  }

  /**
   * Return the default directory for the Dart SDK, or `null` if the directory
   * cannot be determined (or does not exist). The default directory is provided
   * by a system property named `com.google.dart.sdk`.
   */
  static Folder defaultSdkDirectory(ResourceProvider resourceProvider) {
    // TODO(brianwilkerson) This is currently only being used in the analysis
    // server's Driver class to find the default SDK. The command-line analyzer
    // uses cli_utils to find the SDK. Not sure why they're different.
    String sdkProperty = getSdkProperty(resourceProvider);
    if (sdkProperty == null) {
      return null;
    }
    Folder sdkDirectory = resourceProvider.getFolder(sdkProperty);
    if (!sdkDirectory.exists) {
      return null;
    }
    return sdkDirectory;
  }

  static String getSdkProperty(ResourceProvider resourceProvider) {
    String exec = io.Platform.resolvedExecutable;
    if (exec.length == 0) {
      return null;
    }
    pathos.Context pathContext = resourceProvider.pathContext;
    if (pathContext.style != pathos.context.style) {
      // This will only happen when running tests.
      if (exec.startsWith(new RegExp('[a-zA-Z]:'))) {
        exec = exec.substring(2);
      } else if (resourceProvider is MemoryResourceProvider) {
        exec = resourceProvider.convertPath(exec);
      }
      exec = pathContext.fromUri(pathos.context.toUri(exec));
    }
    // Might be "xcodebuild/ReleaseIA32/dart" with "sdk" sibling
    String outDir = pathContext.dirname(pathContext.dirname(exec));
    String sdkPath = pathContext.join(pathContext.dirname(outDir), "sdk");
    if (resourceProvider.getFolder(sdkPath).exists) {
      // We are executing in the context of a test.  sdkPath is the path to the
      // *source* files for the SDK.  But we want to test using the path to the
      // *built* SDK if possible.
      String builtSdkPath =
          pathContext.join(pathContext.dirname(exec), 'dart-sdk');
      if (resourceProvider.getFolder(builtSdkPath).exists) {
        return builtSdkPath;
      } else {
        return sdkPath;
      }
    }
    // probably be "dart-sdk/bin/dart"
    return pathContext.dirname(pathContext.dirname(exec));
  }
}

/**
 * An object used to locate SDK extensions.
 *
 * Given a package map, it will check in each package's `lib` directory for the
 * existence of a `_sdkext` file. This file must contain a JSON encoded map.
 * Each key in the map is a `dart:` library name. Each value is a path (relative
 * to the directory containing `_sdkext`) to a dart script for the given
 * library. For example:
 * ```
 * {
 *   "dart:sky": "../sdk_ext/dart_sky.dart"
 * }
 * ```
 * If a key doesn't begin with `dart:` it is ignored.
 */
class SdkExtensionFinder {
  /**
   * The name of the extension file.
   */
  static const String SDK_EXT_NAME = '_sdkext';

  /**
   * The prefix required for all keys in an extension file that will not be
   * ignored.
   */
  static const String DART_COLON_PREFIX = 'dart:';

  /**
   * A table mapping the names of extensions to the paths where those extensions
   * can be found.
   */
  final Map<String, String> _urlMappings = <String, String>{};

  /**
   * The absolute paths of the extension files that contributed to the
   * [_urlMappings].
   */
  final List<String> extensionFilePaths = <String>[];

  /**
   * Initialize a newly created finder to look in the packages in the given
   * [packageMap] for SDK extension files.
   */
  SdkExtensionFinder(Map<String, List<Folder>> packageMap) {
    if (packageMap == null) {
      return;
    }
    packageMap.forEach(_processPackage);
  }

  /**
   * Return a table mapping the names of extensions to the paths where those
   * extensions can be found.
   */
  Map<String, String> get urlMappings =>
      new Map<String, String>.from(_urlMappings);

  /**
   * Given a package [name] and a list of folders ([libDirs]), add any found sdk
   * extensions.
   */
  void _processPackage(String name, List<Folder> libDirs) {
    for (var libDir in libDirs) {
      var sdkExt = _readDotSdkExt(libDir);
      if (sdkExt != null) {
        _processSdkExt(sdkExt, libDir);
      }
    }
  }

  /**
   * Given the JSON for an SDK extension ([sdkExtJSON]) and a folder ([libDir]),
   * setup the uri mapping.
   */
  void _processSdkExt(String sdkExtJSON, Folder libDir) {
    var sdkExt;
    try {
      sdkExt = json.decode(sdkExtJSON);
    } catch (e) {
      return;
    }
    if ((sdkExt == null) || (sdkExt is! Map)) {
      return;
    }
    bool contributed = false;
    sdkExt.forEach((k, v) {
      if (k is String && v is String && _processSdkExtension(libDir, k, v)) {
        contributed = true;
      }
    });
    if (contributed) {
      extensionFilePaths.add(libDir.getChild(SDK_EXT_NAME).path);
    }
  }

  /**
   * Install the mapping from [name] to [libDir]/[file].
   */
  bool _processSdkExtension(Folder libDir, String name, String file) {
    if (!name.startsWith(DART_COLON_PREFIX)) {
      // SDK extensions must begin with 'dart:'.
      return false;
    }
    _urlMappings[name] = libDir.canonicalizePath(file);
    return true;
  }

  /**
   * Read the contents of [libDir]/[SDK_EXT_NAME] as a string, or `null` if the
   * file doesn't exist.
   */
  String _readDotSdkExt(Folder libDir) {
    File file = libDir.getChild(SDK_EXT_NAME);
    try {
      return file.readAsStringSync();
    } on FileSystemException {
      // File can't be read.
      return null;
    }
  }
}

/**
 * An object used to read and parse the libraries file
 * (dart-sdk/lib/_internal/sdk_library_metadata/lib/libraries.dart) for information
 * about the libraries in an SDK. The library information is represented as a
 * Dart file containing a single top-level variable whose value is a const map.
 * The keys of the map are the names of libraries defined in the SDK and the
 * values in the map are info objects defining the library. For example, a
 * subset of a typical SDK might have a libraries file that looks like the
 * following:
 *
 *     final Map<String, LibraryInfo> LIBRARIES = const <LibraryInfo> {
 *       // Used by VM applications
 *       "builtin" : const LibraryInfo(
 *         "builtin/builtin_runtime.dart",
 *         category: "Server",
 *         platforms: VM_PLATFORM),
 *
 *       "compiler" : const LibraryInfo(
 *         "compiler/compiler.dart",
 *         category: "Tools",
 *         platforms: 0),
 *     };
 */
class SdkLibrariesReader {
  /**
   * A flag indicating whether the dart2js path should be used when it is
   * available.
   */
  final bool _useDart2jsPaths;

  /**
   * Initialize a newly created library reader to use the dart2js path if
   * [_useDart2jsPaths] is `true`.
   */
  SdkLibrariesReader(this._useDart2jsPaths);

  /**
   * Return the library map read from the given [file], given that the content
   * of the file is already known to be [libraryFileContents].
   */
  LibraryMap readFromFile(File file, String libraryFileContents) =>
      readFromSource(file.createSource(), libraryFileContents);

  /**
   * Return the library map read from the given [source], given that the content
   * of the file is already known to be [libraryFileContents].
   */
  LibraryMap readFromSource(Source source, String libraryFileContents) {
    BooleanErrorListener errorListener = new BooleanErrorListener();
    Scanner scanner = new Scanner(
        source, new CharSequenceReader(libraryFileContents), errorListener);
    Parser parser = new Parser(source, errorListener);
    CompilationUnit unit = parser.parseCompilationUnit(scanner.tokenize());
    SdkLibrariesReader_LibraryBuilder libraryBuilder =
        new SdkLibrariesReader_LibraryBuilder(_useDart2jsPaths);
    // If any syntactic errors were found then don't try to visit the AST
    // structure.
    if (!errorListener.errorReported) {
      unit.accept(libraryBuilder);
    }
    return libraryBuilder.librariesMap;
  }
}
