Flutter doctor detect IntelliJ on Mac (#6262)

* cleanup obtaining user home directory path
* refactor doctor and detect IntelliJ on Mac
* fix detect Flutter plugin for IntelliJ
diff --git a/packages/flutter_tools/lib/src/android/android_sdk.dart b/packages/flutter_tools/lib/src/android/android_sdk.dart
index ea9c913..e509d6b 100644
--- a/packages/flutter_tools/lib/src/android/android_sdk.dart
+++ b/packages/flutter_tools/lib/src/android/android_sdk.dart
@@ -7,6 +7,7 @@
 import 'package:path/path.dart' as path;
 import 'package:pub_semver/pub_semver.dart';
 
+import '../base/common.dart';
 import '../base/os.dart';
 import '../globals.dart';
 
@@ -60,13 +61,11 @@
     if (Platform.environment.containsKey('ANDROID_HOME')) {
       androidHomeDir = Platform.environment['ANDROID_HOME'];
     } else if (Platform.isLinux) {
-      String homeDir = Platform.environment['HOME'];
-      if (homeDir != null)
-        androidHomeDir = '$homeDir/Android/Sdk';
+      if (homeDirPath != null)
+        androidHomeDir = '$homeDirPath/Android/Sdk';
     } else if (Platform.isMacOS) {
-      String homeDir = Platform.environment['HOME'];
-      if (homeDir != null)
-        androidHomeDir = '$homeDir/Library/Android/sdk';
+      if (homeDirPath != null)
+        androidHomeDir = '$homeDirPath/Library/Android/sdk';
     }
 
     if (androidHomeDir != null) {
diff --git a/packages/flutter_tools/lib/src/base/common.dart b/packages/flutter_tools/lib/src/base/common.dart
index b9fef20..4ed265b 100644
--- a/packages/flutter_tools/lib/src/base/common.dart
+++ b/packages/flutter_tools/lib/src/base/common.dart
@@ -2,10 +2,27 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:io';
+
+import 'package:path/path.dart' as path;
+
 const int kDefaultObservatoryPort = 8100;
 const int kDefaultDiagnosticPort  = 8101;
 const int kDefaultDrivePort       = 8183;
 
+/// Return the absolute path of the user's home directory
+String get homeDirPath {
+  if (_homeDirPath == null) {
+    _homeDirPath = Platform.isWindows
+        ? Platform.environment['USERPROFILE']
+        : Platform.environment['HOME'];
+    if (_homeDirPath != null)
+      _homeDirPath = path.absolute(_homeDirPath);
+  }
+  return _homeDirPath;
+}
+String _homeDirPath;
+
 /// Specialized exception for expected situations
 /// where the tool should exit with a clear message to the user
 /// and no stack trace unless the --verbose option is specified.
diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart
index 94b7129..663f207 100644
--- a/packages/flutter_tools/lib/src/doctor.dart
+++ b/packages/flutter_tools/lib/src/doctor.dart
@@ -9,6 +9,7 @@
 import 'package:path/path.dart' as path;
 
 import 'android/android_workflow.dart';
+import 'base/common.dart';
 import 'base/context.dart';
 import 'base/os.dart';
 import 'device.dart';
@@ -41,7 +42,7 @@
 
     List<DoctorValidator> ideValidators = <DoctorValidator>[];
     ideValidators.addAll(AtomValidator.installed);
-    ideValidators.addAll(IntellijValidator.installed);
+    ideValidators.addAll(IntelliJValidator.installed);
     if (ideValidators.isNotEmpty)
       _validators.addAll(ideValidators);
     else
@@ -313,52 +314,25 @@
   }
 }
 
-class IntellijValidator extends DoctorValidator {
-  IntellijValidator(String title, {this.version, this.pluginsPath}) : super(title);
+abstract class IntelliJValidator extends DoctorValidator {
+  IntelliJValidator(String title) : super(title);
 
-  final String version;
-  final String pluginsPath;
+  String get version;
+  String get pluginsPath;
+
+  static final Map<String, String> _idToTitle = <String, String>{
+    'IntelliJIdea' : 'IntelliJ IDEA Ultimate Edition',
+    'IdeaIC' : 'IntelliJ IDEA Community Edition',
+    'WebStorm' : 'IntelliJ WebStorm',
+  };
 
   static Iterable<DoctorValidator> get installed {
-    List<DoctorValidator> validators = <DoctorValidator>[];
-    Map<String, String> products = <String, String>{
-      'IntelliJIdea' : 'IntelliJ IDEA Ultimate Edition',
-      'IdeaIC' : 'IntelliJ IDEA Community Edition',
-      'WebStorm' : 'IntelliJ WebStorm',
-    };
-    String homeDir = Platform.environment['HOME'];
-
-    if (Platform.isLinux && homeDir != null) {
-      for (FileSystemEntity dir in new Directory(homeDir).listSync()) {
-        if (dir is Directory) {
-          String name = path.basename(dir.path);
-          products.forEach((String id, String title) {
-            if (name.startsWith('.$id')) {
-              String version = name.substring(id.length + 1);
-              String installPath;
-              try {
-                installPath = new File(path.join(dir.path, 'system', '.home')).readAsStringSync();
-              } catch (e) {
-                // ignored
-              }
-              if (installPath != null && FileSystemEntity.isDirectorySync(installPath)) {
-                validators.add(new IntellijValidator(
-                  title,
-                  version: version,
-                  pluginsPath: path.join(dir.path, 'config', 'plugins'),
-                ));
-              }
-            }
-          });
-        }
-      }
-    } else if (Platform.isMacOS) {
-      // TODO(danrubel): add support for Mac
-
-    } else {
-      // TODO(danrubel): add support for Windows
-    }
-    return validators;
+    if (Platform.isLinux)
+      return IntelliJValidatorOnLinux.installed;
+    if (Platform.isMacOS)
+      return IntelliJValidatorOnMac.installed;
+    // TODO(danrubel): add support for Windows
+    return <DoctorValidator>[];
   }
 
   @override
@@ -370,7 +344,7 @@
     if (_validateHasPackage(messages, 'Dart', 'Dart'))
       installCount++;
 
-    if (_validateHasPackage(messages, 'Flutter', 'Flutter'))
+    if (_validateHasPackage(messages, 'flutter-intellij.jar', 'Flutter'))
       installCount++;
 
     if (installCount < 2) {
@@ -387,23 +361,123 @@
     );
   }
 
-  bool _validateHasPackage(List<ValidationMessage> messages, String packageName, String description) {
+  bool _validateHasPackage(List<ValidationMessage> messages, String packageName, String title) {
     if (!hasPackage(packageName)) {
       messages.add(new ValidationMessage(
-        '$packageName plugin not installed; this adds $description specific functionality.'
+        '$title plugin not installed; this adds $title specific functionality.'
       ));
       return false;
     }
-    messages.add(new ValidationMessage('$packageName plugin installed'));
+    messages.add(new ValidationMessage('$title plugin installed'));
     return true;
   }
 
   bool hasPackage(String packageName) {
     String packagePath = path.join(pluginsPath, packageName);
+    if (packageName.endsWith('.jar'))
+      return FileSystemEntity.isFileSync(packagePath);
     return FileSystemEntity.isDirectorySync(packagePath);
   }
 }
 
+class IntelliJValidatorOnLinux extends IntelliJValidator {
+  IntelliJValidatorOnLinux(String title, this.version, this.pluginsPath) : super(title);
+
+  @override
+  String version;
+
+  @override
+  String pluginsPath;
+
+  static Iterable<DoctorValidator> get installed {
+    List<DoctorValidator> validators = <DoctorValidator>[];
+    if (homeDirPath == null) return validators;
+    for (FileSystemEntity dir in new Directory(homeDirPath).listSync()) {
+      if (dir is Directory) {
+        String name = path.basename(dir.path);
+        IntelliJValidator._idToTitle.forEach((String id, String title) {
+          if (name.startsWith('.$id')) {
+            String version = name.substring(id.length + 1);
+            String installPath;
+            try {
+              installPath = new File(path.join(dir.path, 'system', '.home')).readAsStringSync();
+            } catch (e) {
+              // ignored
+            }
+            if (installPath != null && FileSystemEntity.isDirectorySync(installPath)) {
+              String pluginsPath = path.join(dir.path, 'config', 'plugins');
+              validators.add(new IntelliJValidatorOnLinux(title, version, pluginsPath));
+            }
+          }
+        });
+      }
+    }
+    return validators;
+  }
+}
+
+class IntelliJValidatorOnMac extends IntelliJValidator {
+  IntelliJValidatorOnMac(String title, this.id, this.installPath) : super(title);
+
+  final String id;
+  final String installPath;
+
+  static final Map<String, String> _dirNameToId = <String, String>{
+    'IntelliJ IDEA.app' : 'IntelliJIdea',
+    'IntelliJ IDEA CE.app' : 'IdeaIC',
+    'WebStorm.app' : 'WebStorm',
+  };
+
+  static Iterable<DoctorValidator> get installed {
+    List<DoctorValidator> validators = <DoctorValidator>[];
+    for (FileSystemEntity dir in new Directory('/Applications').listSync()) {
+      if (dir is Directory) {
+        String name = path.basename(dir.path);
+        _dirNameToId.forEach((String dirName, String id) {
+          if (name == dirName) {
+            String title = IntelliJValidator._idToTitle[id];
+            validators.add(new IntelliJValidatorOnMac(title, id, dir.path));
+          }
+        });
+      }
+    }
+    return validators;
+  }
+
+  @override
+  String get version {
+    if (_version == null) {
+      String plist;
+      try {
+        plist = new File(path.join(installPath, 'Contents', 'Info.plist')).readAsStringSync();
+        int index = plist.indexOf('CFBundleShortVersionString');
+        if (index > 0) {
+          int start = plist.indexOf('<string>', index);
+          if (start > 0) {
+            int end = plist.indexOf('</string>', start);
+            if (end > 0) {
+              _version = plist.substring(start + 8, end);
+            }
+          }
+        }
+      } on FileSystemException catch (_) {
+        // ignored
+      }
+      _version ??= 'unknown';
+    }
+    return _version;
+  }
+  String _version;
+
+  @override
+  String get pluginsPath {
+    List<String> split = version.split('.');
+    String major = split[0];
+    String minor = split[1];
+    return path.join(homeDirPath, 'Library', 'Application Support', '$id$major.$minor');
+  }
+}
+
 class DeviceValidator extends DoctorValidator {
   DeviceValidator() : super('Connected devices');
 
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index ebe9ded..9016171 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -17,8 +17,6 @@
 import '../services.dart';
 import 'xcodeproj.dart';
 
-String get homeDirectory => path.absolute(Platform.environment['HOME']);
-
 const int kXcodeRequiredVersionMajor = 7;
 const int kXcodeRequiredVersionMinor = 0;
 
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index 49201a3..4b18195 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -10,6 +10,7 @@
 import 'package:path/path.dart' as path;
 
 import '../application_package.dart';
+import '../base/common.dart';
 import '../base/context.dart';
 import '../base/process.dart';
 import '../build_info.dart';
@@ -318,7 +319,7 @@
   String get xcrunPath => path.join('/usr', 'bin', 'xcrun');
 
   String _getSimulatorPath() {
-    return path.join(homeDirectory, 'Library', 'Developer', 'CoreSimulator', 'Devices', id);
+    return path.join(homeDirPath, 'Library', 'Developer', 'CoreSimulator', 'Devices', id);
   }
 
   String _getSimulatorAppHomeDirectory(ApplicationPackage app) {
@@ -544,7 +545,7 @@
   }
 
   String get logFilePath {
-    return path.join(homeDirectory, 'Library', 'Logs', 'CoreSimulator', id, 'system.log');
+    return path.join(homeDirPath, 'Library', 'Logs', 'CoreSimulator', id, 'system.log');
   }
 
   @override
@@ -587,7 +588,6 @@
 
   @override
   Future<bool> takeScreenshot(File outputFile) async {
-    String homeDirPath = Platform.environment['HOME'] ?? Platform.environment['USERPROFILE'];
     Directory desktopDir = new Directory(path.join(homeDirPath, 'Desktop'));
 
     // 'Simulator Screen Shot Mar 25, 2016, 2.59.43 PM.png'