<commit_before>// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import '../base/common.dart';
import '../base/context.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/platform.dart';
import '../base/process_manager.dart';
import '../base/version.dart';
import '../globals.dart';
import '../ios/ios_workflow.dart';
import '../ios/plist_utils.dart' as plist;

AndroidStudio get androidStudio => context[AndroidStudio];

// Android Studio layout:

// Linux/Windows:
// $HOME/.AndroidStudioX.Y/system/.home

// macOS:
// /Applications/Android Studio.app/Contents/
// $HOME/Applications/Android Studio.app/Contents/

final RegExp _dotHomeStudioVersionMatcher =
    RegExp(r'^\.(AndroidStudio[^\d]*)([\d.]+)');

String get javaPath => androidStudio?.javaPath;

class AndroidStudio implements Comparable<AndroidStudio> {
  AndroidStudio(this.directory,
      {Version version, this.configured, this.studioAppName = 'AndroidStudio'})
      : version = version ?? Version.unknown {
    _init();
  }

  factory AndroidStudio.fromMacOSBundle(String bundlePath) {
    final String studioPath = fs.path.join(bundlePath, 'Contents');
    final String plistFile = fs.path.join(studioPath, 'Info.plist');
    final String versionString = iosWorkflow.getPlistValueFromFile(
      plistFile,
      plist.kCFBundleShortVersionStringKey,
    );

    Version version;
    if (versionString != null)
      version = Version.parse(versionString);
    return AndroidStudio(studioPath, version: version);
  }

  factory AndroidStudio.fromHomeDot(Directory homeDotDir) {
    final Match versionMatch =
        _dotHomeStudioVersionMatcher.firstMatch(homeDotDir.basename);
    if (versionMatch?.groupCount != 2) {
      return null;
    }
    final Version version = Version.parse(versionMatch[2]);
    final String studioAppName = versionMatch[1];
    if (studioAppName == null || version == null) {
      return null;
    }
    String installPath;
    try {
      installPath = fs
          .file(fs.path.join(homeDotDir.path, 'system', '.home'))
          .readAsStringSync();
    } catch (e) {
      // ignored, installPath will be null, which is handled below
    }
    if (installPath != null && fs.isDirectorySync(installPath)) {
      return AndroidStudio(
          installPath,
          version: version,
          studioAppName: studioAppName,
      );
    }
    return null;
  }

  final String directory;
  final String studioAppName;
  final Version version;
  final String configured;

  String _pluginsPath;
  String _javaPath;
  bool _isValid = false;
  final List<String> _validationMessages = <String>[];

  String get javaPath => _javaPath;

  bool get isValid => _isValid;

  String get pluginsPath {
    if (_pluginsPath == null) {
      final int major = version.major;
      final int minor = version.minor;
      if (platform.isMacOS) {
        _pluginsPath = fs.path.join(
            homeDirPath,
            'Library',
            'Application Support',
            'AndroidStudio$major.$minor');
      } else {
        _pluginsPath = fs.path.join(homeDirPath,
            '.$studioAppName$major.$minor',
            'config',
            'plugins');
      }
    }
    return _pluginsPath;
  }

  List<String> get validationMessages => _validationMessages;

  @override
  int compareTo(AndroidStudio other) {
    final int result = version.compareTo(other.version);
    if (result == 0)
      return directory.compareTo(other.directory);
    return result;
  }

  /// Locates the newest, valid version of Android Studio.
  static AndroidStudio latestValid() {
    final String configuredStudio = config.getValue('android-studio-dir');
    if (configuredStudio != null) {
      String configuredStudioPath = configuredStudio;
      if (platform.isMacOS && !configuredStudioPath.endsWith('Contents'))
        configuredStudioPath = fs.path.join(configuredStudioPath, 'Contents');
      return AndroidStudio(configuredStudioPath,
          configured: configuredStudio);
    }

    // Find all available Studio installations.
    final List<AndroidStudio> studios = allInstalled();
    if (studios.isEmpty) {
      return null;
    }
    studios.sort();
    return studios.lastWhere((AndroidStudio s) => s.isValid,
        orElse: () => null);
  }

  static List<AndroidStudio> allInstalled() =>
      platform.isMacOS ? _allMacOS() : _allLinuxOrWindows();

  static List<AndroidStudio> _allMacOS() {
    final List<FileSystemEntity> candidatePaths = <FileSystemEntity>[];

    void _checkForStudio(String path) {
      if (!fs.isDirectorySync(path))
        return;
      try {
        final Iterable<Directory> directories = fs
            .directory(path)
            .listSync()
            .whereType<Directory>();
        for (Directory directory in directories) {
          final String name = directory.basename;
          // An exact match, or something like 'Android Studio 3.0 Preview.app'.
          if (name.startsWith('Android Studio') && name.endsWith('.app')) {
            candidatePaths.add(directory);
          } else if (!directory.path.endsWith('.app')) {
            _checkForStudio(directory.path);
          }
        }
      } catch (e) {
        printTrace('Exception while looking for Android Studio: $e');
      }
    }

    _checkForStudio('/Applications');
    _checkForStudio(fs.path.join(homeDirPath, 'Applications'));

    final String configuredStudioDir = config.getValue('android-studio-dir');
    if (configuredStudioDir != null) {
      FileSystemEntity configuredStudio = fs.file(configuredStudioDir);
      if (configuredStudio.basename == 'Contents') {
        configuredStudio = configuredStudio.parent;
      }
      if (!candidatePaths
          .any((FileSystemEntity e) => e.path == configuredStudio.path)) {
        candidatePaths.add(configuredStudio);
      }
    }

    return candidatePaths
        .map<AndroidStudio>((FileSystemEntity e) => AndroidStudio.fromMacOSBundle(e.path))
        .where((AndroidStudio s) => s != null)
        .toList();
  }

  static List<AndroidStudio> _allLinuxOrWindows() {
    final List<AndroidStudio> studios = <AndroidStudio>[];

    bool _hasStudioAt(String path, {Version newerThan}) {
      return studios.any((AndroidStudio studio) {
        if (studio.directory != path)
          return false;
        if (newerThan != null) {
          return studio.version.compareTo(newerThan) >= 0;
        }
        return true;
      });
    }

    // Read all $HOME/.AndroidStudio*/system/.home files. There may be several
    // pointing to the same installation, so we grab only the latest one.
    if (fs.directory(homeDirPath).existsSync()) {
      for (FileSystemEntity entity in fs.directory(homeDirPath).listSync()) {
        if (entity is Directory && entity.basename.startsWith('.AndroidStudio')) {
          final AndroidStudio studio = AndroidStudio.fromHomeDot(entity);
          if (studio != null && !_hasStudioAt(studio.directory, newerThan: studio.version)) {
            studios.removeWhere((AndroidStudio other) => other.directory == studio.directory);
            studios.add(studio);
          }
        }
      }
    }

    final String configuredStudioDir = config.getValue('android-studio-dir');
    if (configuredStudioDir != null && !_hasStudioAt(configuredStudioDir)) {
      studios.add(AndroidStudio(configuredStudioDir,
          configured: configuredStudioDir));
    }

    if (platform.isLinux) {
      void _checkWellKnownPath(String path) {
        if (fs.isDirectorySync(path) && !_hasStudioAt(path)) {
          studios.add(AndroidStudio(path));<commit_msg>Fix recursive link resulted `flutter doctor` stucking. See : #25885 and #25870 (#26017)<commit_after>// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import '../base/common.dart';
import '../base/context.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/platform.dart';
import '../base/process_manager.dart';
import '../base/version.dart';
import '../globals.dart';
import '../ios/ios_workflow.dart';
import '../ios/plist_utils.dart' as plist;

AndroidStudio get androidStudio => context[AndroidStudio];

// Android Studio layout:

// Linux/Windows:
// $HOME/.AndroidStudioX.Y/system/.home

// macOS:
// /Applications/Android Studio.app/Contents/
// $HOME/Applications/Android Studio.app/Contents/

final RegExp _dotHomeStudioVersionMatcher =
    RegExp(r'^\.(AndroidStudio[^\d]*)([\d.]+)');

String get javaPath => androidStudio?.javaPath;

class AndroidStudio implements Comparable<AndroidStudio> {
  AndroidStudio(this.directory,
      {Version version, this.configured, this.studioAppName = 'AndroidStudio'})
      : version = version ?? Version.unknown {
    _init();
  }

  factory AndroidStudio.fromMacOSBundle(String bundlePath) {
    final String studioPath = fs.path.join(bundlePath, 'Contents');
    final String plistFile = fs.path.join(studioPath, 'Info.plist');
    final String versionString = iosWorkflow.getPlistValueFromFile(
      plistFile,
      plist.kCFBundleShortVersionStringKey,
    );

    Version version;
    if (versionString != null)
      version = Version.parse(versionString);
    return AndroidStudio(studioPath, version: version);
  }

  factory AndroidStudio.fromHomeDot(Directory homeDotDir) {
    final Match versionMatch =
        _dotHomeStudioVersionMatcher.firstMatch(homeDotDir.basename);
    if (versionMatch?.groupCount != 2) {
      return null;
    }
    final Version version = Version.parse(versionMatch[2]);
    final String studioAppName = versionMatch[1];
    if (studioAppName == null || version == null) {
      return null;
    }
    String installPath;
    try {
      installPath = fs
          .file(fs.path.join(homeDotDir.path, 'system', '.home'))
          .readAsStringSync();
    } catch (e) {
      // ignored, installPath will be null, which is handled below
    }
    if (installPath != null && fs.isDirectorySync(installPath)) {
      return AndroidStudio(
          installPath,
          version: version,
          studioAppName: studioAppName,
      );
    }
    return null;
  }

  final String directory;
  final String studioAppName;
  final Version version;
  final String configured;

  String _pluginsPath;
  String _javaPath;
  bool _isValid = false;
  final List<String> _validationMessages = <String>[];

  String get javaPath => _javaPath;

  bool get isValid => _isValid;

  String get pluginsPath {
    if (_pluginsPath == null) {
      final int major = version.major;
      final int minor = version.minor;
      if (platform.isMacOS) {
        _pluginsPath = fs.path.join(
            homeDirPath,
            'Library',
            'Application Support',
            'AndroidStudio$major.$minor');
      } else {
        _pluginsPath = fs.path.join(homeDirPath,
            '.$studioAppName$major.$minor',
            'config',
            'plugins');
      }
    }
    return _pluginsPath;
  }

  List<String> get validationMessages => _validationMessages;

  @override
  int compareTo(AndroidStudio other) {
    final int result = version.compareTo(other.version);
    if (result == 0)
      return directory.compareTo(other.directory);
    return result;
  }

  /// Locates the newest, valid version of Android Studio.
  static AndroidStudio latestValid() {
    final String configuredStudio = config.getValue('android-studio-dir');
    if (configuredStudio != null) {
      String configuredStudioPath = configuredStudio;
      if (platform.isMacOS && !configuredStudioPath.endsWith('Contents'))
        configuredStudioPath = fs.path.join(configuredStudioPath, 'Contents');
      return AndroidStudio(configuredStudioPath,
          configured: configuredStudio);
    }

    // Find all available Studio installations.
    final List<AndroidStudio> studios = allInstalled();
    if (studios.isEmpty) {
      return null;
    }
    studios.sort();
    return studios.lastWhere((AndroidStudio s) => s.isValid,
        orElse: () => null);
  }

  static List<AndroidStudio> allInstalled() =>
      platform.isMacOS ? _allMacOS() : _allLinuxOrWindows();

  static List<AndroidStudio> _allMacOS() {
    final List<FileSystemEntity> candidatePaths = <FileSystemEntity>[];

    void _checkForStudio(String path) {
      if (!fs.isDirectorySync(path))
        return;
      try {
        final Iterable<Directory> directories = fs
            .directory(path)
            .listSync(followLinks: false)
            .whereType<Directory>();
        for (Directory directory in directories) {
          final String name = directory.basename;
          // An exact match, or something like 'Android Studio 3.0 Preview.app'.
          if (name.startsWith('Android Studio') && name.endsWith('.app')) {
            candidatePaths.add(directory);
          } else if (!directory.path.endsWith('.app')) {
            _checkForStudio(directory.path);
          }
        }
      } catch (e) {
        printTrace('Exception while looking for Android Studio: $e');
      }
    }

    _checkForStudio('/Applications');
    _checkForStudio(fs.path.join(homeDirPath, 'Applications'));

    final String configuredStudioDir = config.getValue('android-studio-dir');
    if (configuredStudioDir != null) {
      FileSystemEntity configuredStudio = fs.file(configuredStudioDir);
      if (configuredStudio.basename == 'Contents') {
        configuredStudio = configuredStudio.parent;
      }
      if (!candidatePaths
          .any((FileSystemEntity e) => e.path == configuredStudio.path)) {
        candidatePaths.add(configuredStudio);
      }
    }

    return candidatePaths
        .map<AndroidStudio>((FileSystemEntity e) => AndroidStudio.fromMacOSBundle(e.path))
        .where((AndroidStudio s) => s != null)
        .toList();
  }

  static List<AndroidStudio> _allLinuxOrWindows() {
    final List<AndroidStudio> studios = <AndroidStudio>[];

    bool _hasStudioAt(String path, {Version newerThan}) {
      return studios.any((AndroidStudio studio) {
        if (studio.directory != path)
          return false;
        if (newerThan != null) {
          return studio.version.compareTo(newerThan) >= 0;
        }
        return true;
      });
    }

    // Read all $HOME/.AndroidStudio*/system/.home files. There may be several
    // pointing to the same installation, so we grab only the latest one.
    if (fs.directory(homeDirPath).existsSync()) {
      for (FileSystemEntity entity in fs.directory(homeDirPath).listSync(followLinks: false)) {
        if (entity is Directory && entity.basename.startsWith('.AndroidStudio')) {
          final AndroidStudio studio = AndroidStudio.fromHomeDot(entity);
          if (studio != null && !_hasStudioAt(studio.directory, newerThan: studio.version)) {
            studios.removeWhere((AndroidStudio other) => other.directory == studio.directory);
            studios.add(studio);
          }
        }
      }
    }

    final String configuredStudioDir = config.getValue('android-studio-dir');
    if (configuredStudioDir != null && !_hasStudioAt(configuredStudioDir)) {
      studios.add(AndroidStudio(configuredStudioDir,
          configured: configuredStudioDir));
    }

    if (platform.isLinux) {
      void _checkWellKnownPath(String path) {
        if (fs.isDirectorySync(path) && !_hasStudioAt(path)) {
          studios.add(AndroidStudio(path));
{'fraction_non_alphanumeric': 0.11815728604471858, 'fraction_numerical': 0.0023772809046517605, 'mean_word_length': 2.940506329113924, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 59, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6538520', 'n_tokens_mistral': 4548, 'n_tokens_neox': 4305, 'n_words': 1016}