diff --git a/org.eclipse.dartboard.releng/pom.xml b/org.eclipse.dartboard.releng/pom.xml
index b07fc25..a3507df 100644
--- a/org.eclipse.dartboard.releng/pom.xml
+++ b/org.eclipse.dartboard.releng/pom.xml
@@ -19,7 +19,7 @@
pom
- 1.6.0-SNAPSHOT
+ 1.6.0
UTF-8
http://download.eclipse.org/releases/2020-03
@@ -27,7 +27,7 @@
http://download.eclipse.org/lsp4e/releases/latest/
http://download.eclipse.org/wildwebdeveloper/snapshots
https://download.eclipse.org/tools/orbit/downloads/drops/R20191126223242/repository
- 1.6.0-SNAPSHOT
+ 1.6.0
diff --git a/org.eclipse.dartboard.test/src/org/eclipse/dartboard/test/preference/DartPreferencePageTest.java b/org.eclipse.dartboard.test/src/org/eclipse/dartboard/test/preference/DartPreferencePageTest.java
index e6f9a2f..51b44f2 100644
--- a/org.eclipse.dartboard.test/src/org/eclipse/dartboard/test/preference/DartPreferencePageTest.java
+++ b/org.eclipse.dartboard.test/src/org/eclipse/dartboard/test/preference/DartPreferencePageTest.java
@@ -16,15 +16,17 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
-import org.eclipse.core.runtime.Platform;
import org.eclipse.dartboard.test.util.DefaultPreferences;
+import org.eclipse.reddeer.common.wait.WaitUntil;
import org.eclipse.reddeer.core.reference.ReferencedComposite;
import org.eclipse.reddeer.jface.preference.PreferenceDialog;
import org.eclipse.reddeer.jface.preference.PreferencePage;
import org.eclipse.reddeer.junit.runner.RedDeerSuite;
import org.eclipse.reddeer.swt.impl.button.CheckBox;
import org.eclipse.reddeer.swt.impl.button.RadioButton;
+import org.eclipse.reddeer.swt.impl.clabel.DefaultCLabel;
import org.eclipse.reddeer.swt.impl.text.DefaultText;
import org.eclipse.reddeer.swt.impl.text.LabeledText;
import org.eclipse.reddeer.workbench.ui.dialogs.WorkbenchPreferenceDialog;
@@ -35,16 +37,29 @@
@RunWith(RedDeerSuite.class)
public class DartPreferencePageTest {
- /** Dart SDK location is operating system specific. Here catering for Linuz and Windows */
- private static String DART_SDK_LOC;
+ static private final String DIALOG_TITLE = "Dart and Flutter";
+ /**
+ * Dart SDK location is operating system specific. Here catering for Linux and
+ * Windows
+ */
private PreferenceDialog preferenceDialog;
private DartPreferencePage preferencePage;
@Before
public void setup() {
- DART_SDK_LOC = Platform.getOS().equals(Platform.OS_WIN32) ? "C:\\Program Files\\Dart\\dart-sdk" : "/usr/lib/dart";
+ boolean firstTime = preferenceDialog == null;
+ if (firstTime) {// First time - clear settings from previous test session
+ DefaultPreferences.resetPreferences();
+ }
+
preferenceDialog = new WorkbenchPreferenceDialog();
+ if (firstTime) {
+ // Make sure preferences dialog is closed before test commences
+ if (preferenceDialog.isOpen()) {
+ preferenceDialog.cancel();
+ }
+ }
preferencePage = new DartPreferencePage(preferenceDialog);
preferenceDialog.open();
preferenceDialog.select(preferencePage);
@@ -56,29 +71,46 @@ public void tearDown() {
if (preferenceDialog.isOpen()) {
preferenceDialog.cancel();
}
+ }
+ public void doAllTests() throws Exception {
+ dartPreferencePage__DefaultPreferences__CorrectDefaultsAreDisplayed();
+ dartPreferencePage__InvalidToValidSDKLocation_PageIsNotValidThenOk();
}
@Test
public void dartPreferencePage__DefaultPreferences__CorrectDefaultsAreDisplayed() throws Exception {
+ assumeTrue(PreferenceTestConstants.DEFAULT_FLUTTER_LOCATION != null);
+ assertEquals(PreferenceTestConstants.DEFAULT_FLUTTER_LOCATION, preferencePage.getFlutterSDKLocation());
assertTrue("Auto pub synchronization not selected", preferencePage.isAutoPubSynchronization());
assertFalse("Use offline pub is selected", preferencePage.isUseOfflinePub());
preferencePage.setPluginMode("Dart");
- assertEquals(DART_SDK_LOC, preferencePage.getSDKLocation());
+ assertEquals(PreferenceTestConstants.DEFAULT_DART_LOCATION, preferencePage.getDartSDKLocation());
}
@Test
- public void dartPreferencePage__InvalidSDKLocation__PageIsNotValid() throws Exception {
+ public void dartPreferencePage__InvalidToValidSDKLocation_PageIsNotValidThenOk() throws Exception {
preferencePage.setPluginMode("Dart");
- preferencePage.setSDKLocation("some-random-test-location/path-segment");
+ String result = preferencePage.getDartSDKLocation();
+ assertTrue(PreferenceTestConstants.DEFAULT_DART_LOCATION.equals(result));
+ // Change away from default so it can be changed back
+ preferencePage.setSDKLocation(PreferenceTestConstants.INVALID_SDK_LOCATION);
assertTrue(preferencePage.isShowingSDKInvalidError());
+ // Always leave an open preference page set to a valid value or a modal dialog pops up
+ // warning the page has an invalid value. RedDeer is unable to close the modal
+ // dialog because it is native.
+ preferencePage.setSDKLocation(PreferenceTestConstants.DEFAULT_DART_LOCATION);
+ new WaitUntil(new WaitForValidState(preferencePage, DIALOG_TITLE));
+ result = preferencePage.getDartSDKLocation();
+ assertTrue(PreferenceTestConstants.DEFAULT_DART_LOCATION.equals(result));
}
- public class DartPreferencePage extends PreferencePage {
+
+ public class DartPreferencePage extends PreferencePage implements ValidPreferenceState {
public DartPreferencePage(ReferencedComposite referencedComposite) {
- super(referencedComposite, "Dart and Flutter");
+ super(referencedComposite, DIALOG_TITLE);
}
public DartPreferencePage setSDKLocation(String text) {
@@ -86,10 +118,14 @@ public DartPreferencePage setSDKLocation(String text) {
return this;
}
- public String getSDKLocation() {
+ public String getDartSDKLocation() {
return new LabeledText("Dart SDK Location:").getText();
}
+ public String getFlutterSDKLocation() {
+ return new LabeledText("Flutter SDK Location:").getText();
+ }
+
public DartPreferencePage setAutoPubSynchronization(boolean value) {
new CheckBox("Automatic Pub dependency synchronization").toggle(value);
return this;
@@ -125,6 +161,15 @@ public boolean isShowingSDKInvalidError() {
return false;
}
}
+
+ @Override
+ public boolean isValid() {
+ try {
+ new DefaultCLabel(DIALOG_TITLE);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
}
-
}
diff --git a/org.eclipse.dartboard.test/src/org/eclipse/dartboard/test/preference/PreferenceTestConstants.java b/org.eclipse.dartboard.test/src/org/eclipse/dartboard/test/preference/PreferenceTestConstants.java
new file mode 100644
index 0000000..6614709
--- /dev/null
+++ b/org.eclipse.dartboard.test/src/org/eclipse/dartboard/test/preference/PreferenceTestConstants.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2020 vogella GmbH and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Jonas Hungershausen - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.dartboard.test.preference;
+
+import java.nio.file.Path;
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+
+import org.eclipse.dartboard.util.PlatformUtil;
+
+public class PreferenceTestConstants {
+
+ private static String OS = System.getProperty("os.name").toLowerCase();
+
+ public static final String DEFAULT_DART_LOCATION;
+ public static final String INVALID_SDK_LOCATION = "some-random-test-location/path-segment";
+
+ public static final String DEFAULT_FLUTTER_LOCATION;
+
+ static {
+ boolean isWindows = OS.indexOf("win") >= 0;
+ DEFAULT_DART_LOCATION = isWindows ? "C:\\Program Files\\Dart\\dart-sdk" : "/usr/lib/dart";
+ Optional flutterSdkLocation = null;
+ try {
+ flutterSdkLocation = PlatformUtil.getInstance().getLocation("flutter");
+ } catch (ExecutionException e) {
+ }
+ boolean isFlutterAvailable = (flutterSdkLocation != null) && flutterSdkLocation.isPresent();
+ DEFAULT_FLUTTER_LOCATION = isFlutterAvailable ? flutterSdkLocation.get().toString() : null;
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.dartboard.test/src/org/eclipse/dartboard/test/preference/ValidPreferenceState.java b/org.eclipse.dartboard.test/src/org/eclipse/dartboard/test/preference/ValidPreferenceState.java
new file mode 100644
index 0000000..246293e
--- /dev/null
+++ b/org.eclipse.dartboard.test/src/org/eclipse/dartboard/test/preference/ValidPreferenceState.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright (c) 2020 vogella GmbH and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Andrew Bowley
+ *******************************************************************************/
+package org.eclipse.dartboard.test.preference;
+
+/**
+ * Interface for preference dialog which can flag when it is displaying it's
+ * valid state
+ *
+ * @author Andrew Bowley
+ *
+ */
+public interface ValidPreferenceState {
+
+ boolean isValid();
+}
diff --git a/org.eclipse.dartboard.test/src/org/eclipse/dartboard/test/preference/WaitForValidState.java b/org.eclipse.dartboard.test/src/org/eclipse/dartboard/test/preference/WaitForValidState.java
new file mode 100644
index 0000000..9d813a1
--- /dev/null
+++ b/org.eclipse.dartboard.test/src/org/eclipse/dartboard/test/preference/WaitForValidState.java
@@ -0,0 +1,58 @@
+package org.eclipse.dartboard.test.preference;
+
+import org.eclipse.reddeer.common.condition.WaitCondition;
+
+/**
+ * RedDeer WaitCondition implementation for wait for preference dialog to
+ * display it's valid state. This normally means the dialog header displays a
+ * title rather than an error message.
+ *
+ * @author Andrew Bowley
+ *
+ */
+public class WaitForValidState implements WaitCondition {
+
+ private final String title;
+ private final ValidPreferenceState preferencePage;
+ private boolean isValid;
+
+ /**
+ * Construct WaitForValidState object
+ *
+ * @param preferencePage Preference page implementing ValidPreferenceState
+ * interface
+ * @param title Title displayed in dialog header
+ */
+ public WaitForValidState(ValidPreferenceState preferencePage, String title) {
+ this.preferencePage = preferencePage;
+ this.title = title;
+ }
+
+ @Override
+ public boolean test() {
+ isValid = preferencePage.isValid();
+ return isValid;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Boolean getResult() {
+ return isValid;
+ }
+
+ @Override
+ public String description() {
+ return title + " preference dialog transition to valid state";
+ }
+
+ @Override
+ public String errorMessageWhile() {
+ return "Waiting for " + description();
+ }
+
+ @Override
+ public String errorMessageUntil() {
+ return "Until " + description();
+ }
+
+}
diff --git a/org.eclipse.dartboard/src/org/eclipse/dartboard/os/linux/PlatformDartSdkChecker.java b/org.eclipse.dartboard/src/org/eclipse/dartboard/os/linux/PlatformDartSdkChecker.java
new file mode 100644
index 0000000..1cb03fa
--- /dev/null
+++ b/org.eclipse.dartboard/src/org/eclipse/dartboard/os/linux/PlatformDartSdkChecker.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2020 vogella GmbH and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Andrew Bowley
+ *******************************************************************************/
+package org.eclipse.dartboard.os.linux;
+
+import java.io.File;
+import java.util.List;
+
+import org.eclipse.dartboard.util.DartSdkChecker;
+import org.eclipse.swt.widgets.Shell;
+
+import com.google.common.collect.Lists;
+
+/**
+ * Checks if a given Linux location contains a Dart SDK
+ *
+ * @author Andrew Bowley
+ *
+ */
+@SuppressWarnings("nls")
+public class PlatformDartSdkChecker extends DartSdkChecker {
+
+ /**
+ * Construct LinuxDartSdkChecker object
+ *
+ * @param shell Parent shell of owner or null if none
+ * @param isFlutter Flag set true if Dart SDK is inside Flutter
+ */
+ public PlatformDartSdkChecker(Shell shell, boolean isFlutter) {
+ super(shell, !isFlutter ? "bin"
+ : "bin" + File.separator + "cache" + File.separator + "dart-sdk" + File.separator + "bin");
+ }
+
+ @Override
+ public String[] getDartVersionCommands(String executablePath) {
+ return new String[] { "/bin/bash", "-c", executablePath + " --version" };
+ }
+
+ @Override
+ public List getBlacklist() {
+ return Lists.newArrayList("/bin/dart", "/usr/bin/dart");
+ }
+
+ @Override
+ public String getDartExecutable() {
+ return "dart"; //$NON-NLS-1$
+ }
+}
diff --git a/org.eclipse.dartboard/src/org/eclipse/dartboard/os/linux/PlatformFactory.java b/org.eclipse.dartboard/src/org/eclipse/dartboard/os/linux/PlatformFactory.java
new file mode 100644
index 0000000..70c5295
--- /dev/null
+++ b/org.eclipse.dartboard/src/org/eclipse/dartboard/os/linux/PlatformFactory.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 2020 vogella GmbH and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Andrew Bowley
+ *******************************************************************************/
+package org.eclipse.dartboard.os.linux;
+
+import org.eclipse.dartboard.util.DartSdkChecker;
+import org.eclipse.dartboard.util.IPlatformFactory;
+import org.eclipse.dartboard.util.SdkLocator;
+import org.eclipse.swt.widgets.Shell;
+
+ public class PlatformFactory implements IPlatformFactory {
+
+ private final PlatformSdkLocator sdkLocator;
+
+ public PlatformFactory() {
+ sdkLocator = new PlatformSdkLocator();
+ }
+
+ @Override
+ public DartSdkChecker getDartSdkChecker(Shell shell, boolean isFlutter) {
+
+ /**
+ * Returns a Dart SDK checker
+ *
+ * @param shell Parent shell of owner or null if none
+ * @param isFlutter Flag set true if Dart SDK is inside Flutter
+ * @return DartSdkChecker object
+ */
+ return new PlatformDartSdkChecker(shell, isFlutter);
+
+ }
+
+ /**
+ * Returns support for locating SDK artifacts
+ *
+ * @return SdkLocator
+ */
+ @Override
+ public SdkLocator getSdkLocator() {
+ return sdkLocator;
+ }
+}
diff --git a/org.eclipse.dartboard/src/org/eclipse/dartboard/os/linux/PlatformSdkLocator.java b/org.eclipse.dartboard/src/org/eclipse/dartboard/os/linux/PlatformSdkLocator.java
new file mode 100644
index 0000000..70c37bb
--- /dev/null
+++ b/org.eclipse.dartboard/src/org/eclipse/dartboard/os/linux/PlatformSdkLocator.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright (c) 2020 vogella GmbH and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Andrew Bowley
+ *******************************************************************************/
+package org.eclipse.dartboard.os.linux;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.concurrent.ExecutionException;
+
+import org.eclipse.dartboard.util.SdkLocator;
+
+public class PlatformSdkLocator extends SdkLocator {
+
+ public PlatformSdkLocator() {
+ }
+
+ @Override
+ public String resolveToolPath(String sdkLocation, String name) {
+ return sdkLocation + File.separator + "bin" + File.separator + name; //$NON-NLS-1$
+ }
+
+ @Override
+ public String resolveExecutablePath(String sdkLocation, String name) {
+ return resolveToolPath(sdkLocation, name);
+ }
+
+ @Override
+ public String[] getLocationCommand(String program, boolean interactive) throws ExecutionException {
+ String shell = null;
+ try {
+ shell = getShell();
+ } catch (IOException | InterruptedException e) {
+ throw new ExecutionException("Error obtaining shell command", e); //$NON-NLS-1$
+ }
+ if (interactive) {
+ return new String[] { shell, "-i", "-c", "which " + program }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ } else {
+ return new String[] { shell, "-c", "which " + program }; //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+
+ public static String getShell() throws IOException, InterruptedException {
+ ProcessBuilder builder = new ProcessBuilder("/bin/sh", "-c", "echo $SHELL"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ Process process = builder.start();
+ process.waitFor();
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
+ String location = reader.readLine();
+ return location;
+ }
+ }
+
+}
diff --git a/org.eclipse.dartboard/src/org/eclipse/dartboard/os/windows/PlatformDartSdkChecker.java b/org.eclipse.dartboard/src/org/eclipse/dartboard/os/windows/PlatformDartSdkChecker.java
new file mode 100644
index 0000000..2477292
--- /dev/null
+++ b/org.eclipse.dartboard/src/org/eclipse/dartboard/os/windows/PlatformDartSdkChecker.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2020 vogella GmbH and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Andrew Bowley
+ *******************************************************************************/
+package org.eclipse.dartboard.os.windows;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.dartboard.util.DartSdkChecker;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * Checks if a given Windows location contains a Dart SDK
+ *
+ * @author Andrew Bowley
+ *
+ */
+@SuppressWarnings("nls")
+public class PlatformDartSdkChecker extends DartSdkChecker {
+
+ /**
+ * Construct PlatformDartSdkChecker object
+ *
+ * @param shell Parent shell of owner or null if none
+ * @param isFlutter Flag set true if Dart SDK is inside Flutter
+ */
+ public PlatformDartSdkChecker(Shell shell, boolean isFlutter) {
+ super(shell, !isFlutter ? "bin"
+ : "bin" + File.separator + "cache" + File.separator + "dart-sdk" + File.separator + "bin");
+ }
+
+ @Override
+ public String[] getDartVersionCommands(String executablePath) {
+ return new String[] { "cmd", "/c", executablePath, "--version" };
+ }
+
+ @Override
+ public List getBlacklist() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public String getDartExecutable() {
+ return "dart.exe"; //$NON-NLS-1$
+ }
+
+}
diff --git a/org.eclipse.dartboard/src/org/eclipse/dartboard/os/windows/PlatformFactory.java b/org.eclipse.dartboard/src/org/eclipse/dartboard/os/windows/PlatformFactory.java
new file mode 100644
index 0000000..7a76e73
--- /dev/null
+++ b/org.eclipse.dartboard/src/org/eclipse/dartboard/os/windows/PlatformFactory.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 2020 vogella GmbH and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Andrew Bowley
+ *******************************************************************************/
+package org.eclipse.dartboard.os.windows;
+
+import org.eclipse.dartboard.util.DartSdkChecker;
+import org.eclipse.dartboard.util.IPlatformFactory;
+import org.eclipse.dartboard.util.SdkLocator;
+import org.eclipse.swt.widgets.Shell;
+
+ public class PlatformFactory implements IPlatformFactory {
+
+ private final PlatformSdkLocator sdkLocator;
+
+ public PlatformFactory() {
+ sdkLocator = new PlatformSdkLocator();
+ }
+
+ @Override
+ public DartSdkChecker getDartSdkChecker(Shell shell, boolean isFlutter) {
+
+ /**
+ * Returns a Dart SDK checker
+ *
+ * @param shell Parent shell of owner or null if none
+ * @param isFlutter Flag set true if Dart SDK is inside Flutter
+ * @return DartSdkChecker object
+ */
+ return new PlatformDartSdkChecker(shell, isFlutter);
+
+ }
+
+ /**
+ * Returns support for locating SDK artifacts
+ *
+ * @return SdkLocator
+ */
+ @Override
+ public SdkLocator getSdkLocator() {
+ return sdkLocator;
+ }
+}
diff --git a/org.eclipse.dartboard/src/org/eclipse/dartboard/os/windows/PlatformSdkLocator.java b/org.eclipse.dartboard/src/org/eclipse/dartboard/os/windows/PlatformSdkLocator.java
new file mode 100644
index 0000000..d2fc78e
--- /dev/null
+++ b/org.eclipse.dartboard/src/org/eclipse/dartboard/os/windows/PlatformSdkLocator.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2020 vogella GmbH and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Andrew Bowley
+ *******************************************************************************/
+package org.eclipse.dartboard.os.windows;
+
+import java.io.File;
+import java.util.concurrent.ExecutionException;
+
+import org.eclipse.dartboard.util.SdkLocator;
+
+@SuppressWarnings("nls")
+public class PlatformSdkLocator extends SdkLocator {
+
+ public PlatformSdkLocator() {
+ }
+
+ @Override
+ public String resolveToolPath(String sdkLocation, String name) {
+ if (!name.endsWith(".bat"))
+ name += ".bat";
+ return sdkLocation + File.separator + "bin" + File.separator + name;
+ }
+
+ @Override
+ public String resolveExecutablePath(String sdkLocation, String name) {
+ if (!name.endsWith(".exe"))
+ name += ".exe";
+ return sdkLocation + File.separator + "bin" + File.separator + name;
+ }
+
+ @Override
+ public String[] getLocationCommand(String program, boolean interactive) throws ExecutionException {
+ return new String[] { "cmd", "/c", "where " + program };
+ }
+
+
+}
diff --git a/org.eclipse.dartboard/src/org/eclipse/dartboard/preferences/DartPreferenceInitializer.java b/org.eclipse.dartboard/src/org/eclipse/dartboard/preferences/DartPreferenceInitializer.java
index 1216f5e..c0bed8d 100644
--- a/org.eclipse.dartboard/src/org/eclipse/dartboard/preferences/DartPreferenceInitializer.java
+++ b/org.eclipse.dartboard/src/org/eclipse/dartboard/preferences/DartPreferenceInitializer.java
@@ -13,9 +13,9 @@
*******************************************************************************/
package org.eclipse.dartboard.preferences;
-import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
+import java.util.concurrent.ExecutionException;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.Platform;
@@ -23,7 +23,7 @@
import org.eclipse.dartboard.logging.DartLog;
import org.eclipse.dartboard.messages.Messages;
import org.eclipse.dartboard.util.GlobalConstants;
-import org.eclipse.dartboard.util.SDKLocator;
+import org.eclipse.dartboard.util.PlatformUtil;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.ui.preferences.ScopedPreferenceStore;
@@ -46,15 +46,15 @@ public void initializeDefaultPreferences() {
boolean anySdkFound = false;
if (scopedPreferenceStore.getString(GlobalConstants.P_SDK_LOCATION_FLUTTER).isEmpty()) {
- Optional binLocation;
+ Optional sdkLocation;
try {
- binLocation = SDKLocator.getFlutterLocation(); // $NON-NLS-1$
- } catch (IOException | InterruptedException e) {
- LOG.log(DartLog.createError("Could not retrieve flutter location", e)); //$NON-NLS-1$
- binLocation = Optional.empty();
+ sdkLocation = getFlutterLocation(); // $NON-NLS-1$
+ } catch (ExecutionException e) {
+ LOG.log(DartLog.createError("Could not retrieve flutter location", e.getCause())); //$NON-NLS-1$
+ sdkLocation = Optional.empty();
}
- if (binLocation.isPresent()) {
- Path sdkPath = binLocation.get().getParent();
+ if (sdkLocation.isPresent()) {
+ Path sdkPath = sdkLocation.get();
scopedPreferenceStore.setDefault(GlobalConstants.P_SDK_LOCATION_FLUTTER, sdkPath.toString());
scopedPreferenceStore.setValue(GlobalConstants.P_FLUTTER_ENABLED, true);
anySdkFound = true;
@@ -67,15 +67,15 @@ public void initializeDefaultPreferences() {
anySdkFound = true;
}
if (scopedPreferenceStore.getString(GlobalConstants.P_SDK_LOCATION_DART).isEmpty()) {
- Optional binLocation;
+ Optional sdkLocation;
try {
- binLocation = SDKLocator.getDartLocation(); // $NON-NLS-1$
- } catch (IOException | InterruptedException e) {
- LOG.log(DartLog.createError("Could not retrieve flutter location", e)); //$NON-NLS-1$
- binLocation = Optional.empty();
+ sdkLocation = getDartLocation(); // $NON-NLS-1$
+ } catch (ExecutionException e) {
+ LOG.log(DartLog.createError("Could not retrieve flutter location", e.getCause())); //$NON-NLS-1$
+ sdkLocation = Optional.empty();
}
- if (binLocation.isPresent()) {
- Path sdkPath = binLocation.get().getParent();
+ if (sdkLocation.isPresent()) {
+ Path sdkPath = sdkLocation.get();
scopedPreferenceStore.setDefault(GlobalConstants.P_SDK_LOCATION_DART, sdkPath.toString());
anySdkFound = true;
}
@@ -88,4 +88,28 @@ public void initializeDefaultPreferences() {
scopedPreferenceStore.setDefault(GlobalConstants.P_SYNC_PUB, true);
scopedPreferenceStore.setDefault(GlobalConstants.P_OFFLINE_PUB, false);
}
+
+ /**
+ * Returns a {@link Path} containing the location of the Dart SDK folder.
+ *
+ * This method finds the location of the Dart SDK on the system, if installed.
+ * On *nix based systems it tries to locate the Dart binary by using the
+ * {@code which} command. Typically the output is a symbolic link to the actual
+ * binary. Since the Dart SDK installation folder contains more binaries that we
+ * need, we resolve the symbolic link and return the path to the parent of the
+ * /bin directory inside the SDK installation folder.
+ *
+ * On Windows this method uses the where command to locate the binary.
+ *
+ * @return - An {@link Optional} of {@link Path} containing the path to the
+ * {@code /bin} folder inside the Dart SDK installation directory or
+ * empty if the SDK is not found on the host machine.
+ */
+ public static Optional getDartLocation() throws ExecutionException {
+ return PlatformUtil.getInstance().getLocation("dart", false); //$NON-NLS-1$
+ }
+
+ public static Optional getFlutterLocation() throws ExecutionException {
+ return PlatformUtil.getInstance().getLocation("flutter", true); //$NON-NLS-1$
+ }
}
diff --git a/org.eclipse.dartboard/src/org/eclipse/dartboard/preferences/DartSDKLocationFieldEditor.java b/org.eclipse.dartboard/src/org/eclipse/dartboard/preferences/DartSDKLocationFieldEditor.java
index 0cdd02c..421925d 100644
--- a/org.eclipse.dartboard/src/org/eclipse/dartboard/preferences/DartSDKLocationFieldEditor.java
+++ b/org.eclipse.dartboard/src/org/eclipse/dartboard/preferences/DartSDKLocationFieldEditor.java
@@ -10,54 +10,178 @@
*
* Contributors:
* Jonas Hungershausen
+ * Andrew Bowley
*******************************************************************************/
package org.eclipse.dartboard.preferences;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.nio.file.Files;
-import java.nio.file.InvalidPathException;
import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Semaphore;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.Platform;
import org.eclipse.dartboard.logging.DartLog;
import org.eclipse.dartboard.messages.Messages;
+import org.eclipse.dartboard.util.DartSdkChecker;
import org.eclipse.dartboard.util.GlobalConstants;
+import org.eclipse.dartboard.util.PlatformUtil;
import org.eclipse.jface.preference.DirectoryFieldEditor;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Shell;
-import com.google.common.collect.Lists;
-
+/**
+ * Field editor to configure Dart SDK. Combines a text control with a browse
+ * button to pop up a directory selection dialog. The user can type or paste
+ * into the text control as an alternative to browsing to the directory.
+ *
+ * @author jonas
+ * @author Andrew Bowley
+ */
public class DartSDKLocationFieldEditor extends DirectoryFieldEditor {
- private static final ILog LOG = Platform.getLog(DartSDKLocationFieldEditor.class);
+ private static final ILog LOG = Platform.getLog(DartSDKLocationFieldEditor.class);
+
+ /** Dart SDK location checker */
+ private final DartSdkChecker dartSdkChecker;
+
+ /** Flag to disable validation while directory browsing in progress */
+ private volatile boolean inChangeDirectory;
+ /**
+ * Valid status flag replaces same flag in super class so enable/disable 'Apply'
+ * buttons work correctly
+ */
+ private boolean isValid;
+ /**
+ * Directory Dialog returned value. The 'inChangeDirectory' flag is cleared when
+ * this variable is set .
+ */
+ private String newDirectory;
- public DartSDKLocationFieldEditor(String preferencesKey, String label, Composite parent) {
+ /** Semaphore used to prevent validation reentry */
+ private Semaphore validationGuard;
+
+ /**
+ * Construct DartSDKLocationFieldEditor object
+ *
+ * @param preferencesKey Storage key
+ * @param labelText the label text of the field editor
+ * @param parent the parent of the field editor's control
+ */
+ public DartSDKLocationFieldEditor(String preferencesKey, String label, Composite parent) {
super(preferencesKey, label, parent);
+ Shell shell = parent.getShell();
+ dartSdkChecker = PlatformUtil.getInstance().getDartSdkChecker(shell, false);
setValidateStrategy(VALIDATE_ON_KEY_STROKE);
+ // Automatically invalidate an empty value
+ setEmptyStringAllowed(false);
+ inChangeDirectory = false;
+ // Semaphore used to prevent validation reentry, which may costly as it may
+ // include executing a shell command
+ validationGuard = new Semaphore(1);
}
+ public void setEnabled(boolean enabled) {
+ getTextControl().setEnabled(enabled);
+ }
+
+ /**
+ * Returns valid flag. Super isValid() has too much latency to be useable.
+ *
+ * @return boolean
+ */
+ @Override
+ public boolean isValid() {
+ return isValid;
+ }
+
+ /**
+ * Sets the preference store used by this field editor.
+ *
+ * @param store the preference store, or null
if none
+ * @see #getPreferenceStore
+ */
+ @Override
+ public void setPreferenceStore(IPreferenceStore store) {
+ super.setPreferenceStore(store);
+ if (store != null)
+ // Check if default exists
+ isValid = doCheckState();
+ else
+ isValid = true;
+ }
+
+ /**
+ * Handle Browse button pressed
+ *
+ * @return Directory Dialog, which may be null if the user cancels
+ */
+ @Override
+ protected String changePressed() {
+ // Flag Directory Dialog being displayed for doCheckState() logic.
+ // The focus change triggered by dismissing the dialog can preempt the update
+ // of the text field
+ newDirectory = null;
+ inChangeDirectory = true;
+ newDirectory = super.changePressed();
+ if (newDirectory == null)
+ inChangeDirectory = false;
+ return newDirectory;
+ }
+
+ /**
+ * Checks Flutter SDK location selected/entered by user and returns valid flag
+ *
+ * @return boolean
+ */
@Override
protected boolean doCheckState() {
if (getPreferenceStore().getBoolean(GlobalConstants.P_FLUTTER_ENABLED)
&& getTextControl().getText().isEmpty()) {
return true;
}
- String location = getTextControl().getText();
- boolean isValid = isValidDartSDK(location);
- if (!isValid) {
+ if (inChangeDirectory) {
+ // Waiting for user to select directory
+ if (newDirectory != null) {
+ inChangeDirectory = false;
+ isValid = !newDirectory.isEmpty() && isValidDartSDK(newDirectory);
+ } else
+ // Do not display error message while waiting for result
+ return false;
+ } else {
+ String location = getTextControl().getText();
+ isValid = !location.isEmpty() && isValidDartSDK(location);
+ }
+ if (isValid) {
+ setErrorMessage(null);
+ showMessage(null);
+ } else {
setErrorMessage(Messages.Preference_SDKNotFound_Message);
showErrorMessage();
}
return isValid;
}
+ /**
+ * Adds text control text modification listener
+ *
+ * @param listener ModifyListener
+ */
+ protected void addModifyListener(ModifyListener listener) {
+ // Filter modifications made from using the Browse button.
+ ModifyListener modifyListener = new ModifyListener() {
+
+ @Override
+ public void modifyText(ModifyEvent e) {
+ if (!inChangeDirectory)
+ listener.modifyText(e);
+ }
+ };
+ getTextControl().addModifyListener(modifyListener);
+ }
+
/**
* Checks if a given path is the root directory of a Dart SDK installation.
*
@@ -80,68 +204,17 @@ && getTextControl().getText().isEmpty()) {
* @return false
if the location is not a Dart SDK root directory,
* true
otherwise.
*/
- @SuppressWarnings("nls")
private boolean isValidDartSDK(String location) {
- if (location.isEmpty()) {
- return false;
- }
- boolean isWindows = Platform.OS_WIN32.equals(Platform.getOS());
-
- Path path = null;
- // On Windows if a certain wrong combination of characters are entered a
- // InvalidPathException is thrown. In that case we can assume that the location
- // entered is not a valid Dart SDK directory either.
try {
- path = Paths.get(location).resolve("bin" + File.separator + (isWindows ? "dart.exe" : "dart"));
- } catch (InvalidPathException e) {
+ if (!validationGuard.tryAcquire())
+ // Validation already in progress so just return an optimistic interim result
+ return true;
+ return dartSdkChecker.isValidDartSDK(location);
+ } catch (ExecutionException e) {
+ LOG.log(DartLog.createError("Error verifying Dart SDK", e)); //$NON-NLS-1$
return false;
+ } finally {
+ validationGuard.release();
}
-
- // See https://github.com/eclipse/dartboard/issues/103
- List blacklist = Lists.newArrayList("/bin/dart", "/usr/bin/dart");
- // If the entered file doesn't exist, there is no need to run it
- // Similarly if the file is a directory it can't be the dart executable
- if (!Files.exists(path) || Files.isDirectory(path)
- || (!isWindows && blacklist.contains(path.toString().toLowerCase()))) {
- return false;
- }
- // Follow symbolic links
- try {
- path = path.toRealPath();
- } catch (IOException e1) {
- LOG.log(DartLog.createError("Couldn't follow symlink", e1));
- return false;
- }
-
- String executablePath = path.toAbsolutePath().toString();
-
- String[] commands;
- if (isWindows) {
- commands = new String[] { "cmd", "/c", executablePath, "--version" };
- } else {
- commands = new String[] { "/bin/bash", "-c", executablePath + " --version" };
- }
-
- ProcessBuilder processBuilder = new ProcessBuilder(commands);
-
- processBuilder.redirectErrorStream(true);
- String version = null;
- try (BufferedReader reader = new BufferedReader(
- new InputStreamReader(processBuilder.start().getInputStream()))) {
- version = reader.readLine();
- } catch (IOException e) {
- return false;
- }
-
- return version.startsWith("Dart VM version");
}
-
- protected void addModifyListener(ModifyListener listener) {
- getTextControl().addModifyListener(listener);
- }
-
- public void setEnabled(boolean enabled) {
- getTextControl().setEnabled(enabled);
- }
-
}
diff --git a/org.eclipse.dartboard/src/org/eclipse/dartboard/preferences/FlutterSDKLocationFieldEditor.java b/org.eclipse.dartboard/src/org/eclipse/dartboard/preferences/FlutterSDKLocationFieldEditor.java
index 2cf80f9..ceaa06f 100644
--- a/org.eclipse.dartboard/src/org/eclipse/dartboard/preferences/FlutterSDKLocationFieldEditor.java
+++ b/org.eclipse.dartboard/src/org/eclipse/dartboard/preferences/FlutterSDKLocationFieldEditor.java
@@ -10,95 +10,210 @@
*
* Contributors:
* Jonas Hungershausen
+ * Andrew Bowley
*******************************************************************************/
package org.eclipse.dartboard.preferences;
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.InvalidPathException;
import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Semaphore;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.Platform;
import org.eclipse.dartboard.logging.DartLog;
import org.eclipse.dartboard.messages.Messages;
+import org.eclipse.dartboard.util.DartSdkChecker;
import org.eclipse.dartboard.util.GlobalConstants;
-import org.eclipse.dartboard.util.PlatformUIUtil;
+import org.eclipse.dartboard.util.PlatformUtil;
import org.eclipse.jface.preference.DirectoryFieldEditor;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Shell;
/**
- * @author jonas
+ * Field editor to configure Flutter SDK. Combines a text control with a browse
+ * button to pop up a directory selection dialog. The user can type or paste
+ * into the text control as an alternative to browsing to the directory.
*
+ * @author jonas
+ * @author Andrew Bowley
*/
public class FlutterSDKLocationFieldEditor extends DirectoryFieldEditor {
private static final ILog LOG = Platform.getLog(FlutterSDKLocationFieldEditor.class);
+ /** Dart SDK location checker */
+ private final DartSdkChecker dartSdkChecker;
+ /** Flag to disable validation while directory browsing in progress */
+ private volatile boolean inChangeDirectory;
+ /** Semaphore used to prevent validation reentry */
+ private Semaphore validationGuard;
+
+ /**
+ * Valid status flag replaces same flag in super class so enable/disable 'Apply'
+ * buttons work correctly
+ */
+ private boolean isValid;
+ /**
+ * Directory Dialog returned value. The 'inChangeDirectory' flag is cleared when
+ * this variable is set .
+ */
+ private String newDirectory;
+
+ /**
+ * Construct FlutterSDKLocationFieldEditor object
+ *
+ * @param preferencesKey Storage key
+ * @param labelText the label text of the field editor
+ * @param parent the parent of the field editor's control
+ */
public FlutterSDKLocationFieldEditor(String preferencesKey, String label, Composite parent) {
super(preferencesKey, label, parent);
+ Shell shell = parent.getShell();
+ dartSdkChecker = PlatformUtil.getInstance().getDartSdkChecker(shell, true);
setValidateStrategy(VALIDATE_ON_KEY_STROKE);
+ // Automatically invalidate an empty value
+ setEmptyStringAllowed(false);
+ inChangeDirectory = false;
+ // Semaphore used to prevent validation reentry, which may costly as it may
+ // include executing a shell command
+ validationGuard = new Semaphore(1);
+ }
+
+ public void setEnabled(boolean enabled) {
+ getTextControl().setEnabled(enabled);
+ }
+
+ /**
+ * Returns valid flag. Super isValid() has too much latency to be useable.
+ *
+ * @return boolean
+ */
+ @Override
+ public boolean isValid() {
+ return isValid;
+ }
+
+ /**
+ * Sets the preference store used by this field editor.
+ *
+ * @param store the preference store, or null
if none
+ * @see #getPreferenceStore
+ */
+ @Override
+ public void setPreferenceStore(IPreferenceStore store) {
+ super.setPreferenceStore(store);
+ if (store != null)
+ // Check if default exists
+ isValid = doCheckState();
+ else
+ isValid = true;
+ }
+
+ /**
+ * Handle Browse button pressed
+ *
+ * @return Directory Dialog, whiech may be null if the user cancels
+ */
+ @Override
+ protected String changePressed() {
+ // Flag Directory Dialog being displayed for doCheckState() logic.
+ // The focus change triggered by dismissing the dialog can preempt the update
+ // of the text field
+ newDirectory = null;
+ inChangeDirectory = true;
+ newDirectory = super.changePressed();
+ if (newDirectory == null)
+ inChangeDirectory = false;
+ return newDirectory;
}
+ /**
+ * Checks Flutter SDK location selected/entered by user and returns valid flag
+ *
+ * @return boolean
+ */
@Override
protected boolean doCheckState() {
if (!getPreferenceStore().getBoolean(GlobalConstants.P_FLUTTER_ENABLED)) {
return true;
}
- String location = getTextControl().getText();
- Optional optionalPath = getPath(location);
- if (!optionalPath.isPresent()) {
+ if (inChangeDirectory) {
+ // Waiting for user to select directory
+ if (newDirectory != null) {
+ inChangeDirectory = false;
+ isValid = !newDirectory.isEmpty() && isValidFlutterSDK(newDirectory);
+ } else
+ // Do not display error message while waiting for result
+ return false;
+ } else {
+ String location = getTextControl().getText();
+ isValid = !location.isEmpty() && isValidFlutterSDK(location);
+ }
+ if (isValid) {
+ setErrorMessage(null);
+ showMessage(null);
+ } else {
setErrorMessage(Messages.Preference_SDKNotFound_Message);
showErrorMessage();
- return false;
}
- return true;
+ return isValid;
}
- private Optional getPath(String location) {
-
- if (location.isEmpty()) {
- return Optional.empty();
- }
+ /**
+ * Adds text control text modification listener
+ *
+ * @param listener ModifyListener
+ */
+ protected void addModifyListener(ModifyListener listener) {
+ // Filter modifications made from using the Browse button.
+ ModifyListener modifyListener = new ModifyListener() {
- Path path = null;
- // On Windows if a certain wrong combination of characters are entered a
- // InvalidPathException is thrown. In that case we can assume that the location
- // entered is not a valid Dart SDK directory either.
- try {
- path = Paths.get(location)
- .resolve("bin" + File.separator + (PlatformUIUtil.IS_WINDOWS ? "flutter.bat" : "flutter")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
- } catch (InvalidPathException e) {
- return Optional.empty();
- }
+ @Override
+ public void modifyText(ModifyEvent e) {
+ if (!inChangeDirectory)
+ listener.modifyText(e);
+ }
+ };
+ getTextControl().addModifyListener(modifyListener);
+ }
- // See https://github.com/eclipse/dartboard/issues/103
- // List blacklist = Lists.newArrayList("/bin/dart", "/usr/bin/dart");
- // If the entered file doesn't exist, there is no need to run it
- // Similarly if the file is a directory it can't be the dart executable
- if (!Files.exists(path) || Files.isDirectory(path)) {
- return Optional.empty();
- }
- // Follow symbolic links
+ /**
+ * Checks if a given path is the root directory of a Dart SDK installation.
+ *
+ * Returns false if the path does not exist or the given location can not be
+ * converted to a {@link Path}.
+ *
+ * Similarly if the Path is not a directory, false is returned.
+ *
+ * If the location is a symbolic link but it can not be resolved, false is
+ * returned.
+ *
+ * If the process to test the version string returned by the Dart executable can
+ * not be executed, false is returned.
+ *
+ * Finally, if the returned version string does not start with "Dart VM
+ * version", false is returned.
+ *
+ * @param location - A {@link String} that should be checked to be a Dart SDK
+ * root directory.
+ * @return false
if the location is not a Dart SDK root directory,
+ * true
otherwise.
+ */
+ private boolean isValidFlutterSDK(String location) {
try {
- path = path.toRealPath();
- } catch (IOException e1) {
- LOG.log(DartLog.createError("Couldn't follow symlink", e1)); //$NON-NLS-1$
- return Optional.empty();
+ if (!validationGuard.tryAcquire())
+ // Validation already in progress so just return an optimistic interim result
+ return true;
+ return dartSdkChecker.isValidDartSDK(location);
+ } catch (ExecutionException e) {
+ LOG.log(DartLog.createError("Error verifying Dart SDK", e)); //$NON-NLS-1$
+ return false;
+ } finally {
+ validationGuard.release();
}
- return Optional.of(path);
- }
-
- public void setEnabled(boolean enabled) {
- getTextControl().setEnabled(enabled);
- }
-
- protected void addModifyListener(ModifyListener listener) {
- getTextControl().addModifyListener(listener);
}
}
diff --git a/org.eclipse.dartboard/src/org/eclipse/dartboard/util/BusyCursor.java b/org.eclipse.dartboard/src/org/eclipse/dartboard/util/BusyCursor.java
new file mode 100644
index 0000000..3b09c06
--- /dev/null
+++ b/org.eclipse.dartboard/src/org/eclipse/dartboard/util/BusyCursor.java
@@ -0,0 +1,172 @@
+/*******************************************************************************
+ * Copyright (c) 2020 vogella GmbH and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Andrew Bowley
+ *******************************************************************************/
+package org.eclipse.dartboard.util;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.BusyIndicator;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * Shows a Busy Cursor during a long running process. Based on SWT BusyIndicator
+ * example.
+ *
+ * @author Andrew Bowley
+ */
+public class BusyCursor {
+
+ /**
+ * Runnable which shows busy cursor pending arrival of a return object.
+ * @param the return object type
+ */
+ static final class BusyRunnable implements Runnable {
+
+ private final Shell shell;
+ private final Callable supplier;
+ private T value;
+ private Throwable caught;
+ private volatile boolean terminate;
+
+ public BusyRunnable(Shell shell, Callable supplier) {
+ this.shell = shell;
+ this.supplier = supplier;
+ terminate = false;
+ }
+
+ public T getValue() {
+ return value;
+ }
+
+ public Throwable getCaught() {
+ return caught;
+ }
+
+ @Override
+ public void run() {
+ Display display = shell.getDisplay();
+ Thread thread = new Thread(() -> {
+ try {
+ value = supplier.call();
+ terminate = true;
+ } catch (Exception e) {
+ caught = e;
+ }
+ display.wake();
+ });
+ thread.start();
+ while (!terminate && (caught == null) && !shell.isDisposed()) {
+ if (!display.readAndDispatch())
+ display.sleep();
+ }
+ if (shell.isDisposed())
+ caught = new IllegalStateException("Task cancelled by user"); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Runnable to execute busy cursor display
+ */
+ static final class SyncRunner implements Runnable {
+
+ private BusyRunnable busyRunnable;
+ private Display display;
+
+ public SyncRunner(BusyRunnable busyRunnable, Display display) {
+ this.busyRunnable = busyRunnable;
+ this.display = display;
+ }
+
+ @Override
+ public void run() {
+ BusyIndicator.showWhile(display, busyRunnable);
+ }
+
+ }
+
+ /** Temporary shell created when active is not available */
+ private Shell ownShell;
+ /** Active shell of application. May be null if one not yet created */
+ private Shell activeShell;
+
+ /**
+ * Construct BusyCursor object
+ * @param shell Active shell or null if not available
+ */
+ public BusyCursor(Shell shell) {
+ if (shell == null) {
+ // Create temporary shell to use until the shell is updated by calling #setShell()
+ Display.getDefault().syncExec(new Runnable() {
+
+ @Override
+ public void run() {
+ ownShell = new Shell(SWT.TOOL | SWT.NO_TRIM);
+ }});
+ } else {
+ activeShell = shell;
+ }
+ }
+
+ /**
+ * Sets shell for case active shell only available post-construction
+ * @param shell
+ */
+ public void setShell(Shell shell) {
+ if (ownShell != null) {
+ ownShell.getDisplay().syncExec(new Runnable() {
+
+ @Override
+ public void run() {
+ ownShell.close();
+ }});
+ ownShell = null;
+ }
+ activeShell = shell;
+ }
+
+ /**
+ * Wait for object to be supplied calling from non-UI thread
+ * @param Object type
+ * @param supplier Object supplier
+ * @return object
+ * @throws ExecutionException
+ */
+ public T syncWaitForObject(Callable supplier) throws ExecutionException {
+ Shell shell = activeShell == null ? ownShell : activeShell;
+ BusyRunnable busyRunnable = new BusyRunnable<>(shell, supplier);
+ Display display = shell.getDisplay();
+ display.syncExec(new SyncRunner<>(busyRunnable, display));
+ if (busyRunnable.getCaught() != null)
+ throw new ExecutionException("Task failed", busyRunnable.getCaught()); //$NON-NLS-1$
+ return busyRunnable.getValue();
+ }
+
+ /**
+ * Wait for object to be supplied calling from UI thread
+ * @param Object type
+ * @param supplier Object supplier
+ * @return object
+ * @throws ExecutionException
+ */
+ public T waitForObject(Callable supplier) throws ExecutionException {
+ Shell shell = activeShell == null ? ownShell : activeShell;
+ BusyRunnable busyRunnable = new BusyRunnable<>(shell, supplier);
+ BusyIndicator.showWhile(shell.getDisplay(), busyRunnable);
+ if (busyRunnable.getCaught() != null)
+ throw new ExecutionException("Task failed", busyRunnable.getCaught()); //$NON-NLS-1$
+ return busyRunnable.getValue();
+ }
+}
diff --git a/org.eclipse.dartboard/src/org/eclipse/dartboard/util/DartSdkChecker.java b/org.eclipse.dartboard/src/org/eclipse/dartboard/util/DartSdkChecker.java
new file mode 100644
index 0000000..ee05766
--- /dev/null
+++ b/org.eclipse.dartboard/src/org/eclipse/dartboard/util/DartSdkChecker.java
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * Copyright (c) 2020 vogella GmbH and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Andrew Bowley
+ *******************************************************************************/
+package org.eclipse.dartboard.util;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * Base for checking if a given location contains a Dart SDK. Values subject to
+ * context (Dart or Flutter) and operating system (Linux or Windows) are to be
+ * provided by sub classes.
+ *
+ * @author Andrew Bowley
+ *
+ */
+public abstract class DartSdkChecker {
+
+ private static final String DART_PREFIX = "Dart VM version"; //$NON-NLS-1$
+
+ /** Parent shell required by busy cursor */
+ private final Shell shell;
+ /** Relative path from SDK root to Dart executable, no terminating path separator */
+ private final String relativePath;
+
+ /**
+ * Construct DartSdkChecker object
+ *
+ * @param shell Parent shell or null if not available
+ * @param relativePath Relative path from SDK root to Dart executable
+ */
+ protected DartSdkChecker(Shell shell, String relativePath) {
+ this.shell = shell;
+ this.relativePath = relativePath;
+ }
+
+ /**
+ * Returns arguments to execute a query in a command shell which returns the
+ * Dart version
+ *
+ * @param executablePath Absolute path to Dart executable
+ * @return String[]
+ */
+ public abstract String[] getDartVersionCommands(String executablePath);
+
+ /**
+ * Return black list, or empty list if none. See
+ * https://github.com/eclipse/dartboard/issues/103
+ *
+ * @return List
+ */
+ public abstract List getBlacklist();
+
+ /**
+ * Returns name of Dart executable
+ *
+ * @return filename
+ */
+ public abstract String getDartExecutable();
+
+ /**
+ * Checks if a given path is the root directory of a Dart SDK installation.
+ *
+ * Returns false if the path does not exist or the given location can not be
+ * converted to a {@link Path}.
+ *
+ * Similarly if the Path is not a directory, false is returned.
+ *
+ * If the location is a symbolic link but it can not be resolved, false is
+ * returned.
+ *
+ * If the process to test the version string returned by the Dart executable can
+ * not be executed, false is returned.
+ *
+ * Finally, if the returned version string does not start with "Dart VM
+ * version", false is returned.
+ *
+ * @param location - A {@link String} that should be checked to be a Dart SDK
+ * root directory.
+ * @return false
if the location is not a Dart SDK root directory,
+ * true
otherwise.
+ */
+ @SuppressWarnings("nls")
+ public boolean isValidDartSDK(String location) throws ExecutionException {
+ if (location.isEmpty()) {
+
+ return false;
+ }
+ Path path = null;
+ // On Windows if a certain wrong combination of characters are entered a
+ // InvalidPathException is thrown. In that case we can assume that the location
+ // entered is not a valid Dart SDK directory either.
+ String internalPath =
+ relativePath.isEmpty() ?
+ getDartExecutable() :
+ relativePath + File.separator + getDartExecutable();
+ try {
+ path = Paths.get(location).resolve(internalPath);
+ } catch (InvalidPathException e) {
+
+ return false;
+ }
+
+ // See https://github.com/eclipse/dartboard/issues/103
+ List blacklist = getBlacklist();
+ if (!blacklist.isEmpty() && blacklist.contains(path.toString().toLowerCase())) {
+
+ return false;
+ }
+
+ // If the entered file doesn't exist, there is no need to run it
+ // Similarly if the file is a directory it can't be the dart executable
+ if (!Files.exists(path) || Files.isDirectory(path)) {
+
+ return false;
+ }
+
+ // Follow symbolic links
+ try {
+ path = path.toRealPath();
+ } catch (IOException e1) {
+ throw new ExecutionException("Couldn't follow symlink", e1);
+ }
+
+ // Show busy cursor while running command shell to verify Dart SDK
+ BusyCursor busyCursor = new BusyCursor(shell);
+ final String executablePath = path.toAbsolutePath().toString();
+
+ return busyCursor.waitForObject(new Callable() {
+
+ @Override
+ public Boolean call() throws Exception {
+ return verifyDartSdk(getProcessBuilder(executablePath));
+ }
+ });
+ }
+
+ protected ProcessBuilder getProcessBuilder(String executablePath) {
+ String[] commands = getDartVersionCommands(executablePath);
+ ProcessBuilder processBuilder = new ProcessBuilder(commands);
+ processBuilder.redirectErrorStream(true);
+
+ return processBuilder;
+ }
+
+ private boolean verifyDartSdk(ProcessBuilder processBuilder) {
+ String version = null;
+ try (BufferedReader reader = new BufferedReader(
+ new InputStreamReader(processBuilder.start().getInputStream()))) {
+ version = reader.readLine();
+ } catch (IOException e) {
+ return false;
+ }
+
+ return (version != null) && version.startsWith(DART_PREFIX);
+ }
+}
diff --git a/org.eclipse.dartboard/src/org/eclipse/dartboard/util/IPlatformFactory.java b/org.eclipse.dartboard/src/org/eclipse/dartboard/util/IPlatformFactory.java
new file mode 100644
index 0000000..de04f1b
--- /dev/null
+++ b/org.eclipse.dartboard/src/org/eclipse/dartboard/util/IPlatformFactory.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2020 vogella GmbH and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Andrew Bowley
+ *******************************************************************************/
+package org.eclipse.dartboard.util;
+
+import org.eclipse.swt.widgets.Shell;
+
+ public interface IPlatformFactory {
+
+ /**
+ * Returns a Dart SDK checker
+ *
+ * @param shell Parent shell of owner or null if none
+ * @param isFlutter Flag set true if Dart SDK is inside Flutter
+ * @return DartSdkChecker object
+ */
+ DartSdkChecker getDartSdkChecker(Shell shell, boolean isFlutter);
+
+ /**
+ * Returns support for locating SDK artifacts
+ *
+ * @return SdkLocator
+ */
+ SdkLocator getSdkLocator();
+
+}
\ No newline at end of file
diff --git a/org.eclipse.dartboard/src/org/eclipse/dartboard/util/PlatformUtil.java b/org.eclipse.dartboard/src/org/eclipse/dartboard/util/PlatformUtil.java
new file mode 100644
index 0000000..0a5e1b8
--- /dev/null
+++ b/org.eclipse.dartboard/src/org/eclipse/dartboard/util/PlatformUtil.java
@@ -0,0 +1,140 @@
+/*******************************************************************************
+ * Copyright (c) 2020 vogella GmbH and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Andrew Bowley
+ *******************************************************************************/
+package org.eclipse.dartboard.util;
+
+import java.nio.file.Path;
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+
+import org.eclipse.core.runtime.ILog;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.dartboard.logging.DartLog;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * Platform utilities
+ *
+ * @author Andrew Bowley
+ *
+ */
+public class PlatformUtil {
+
+ private static final ILog LOG = Platform.getLog(PlatformUtil.class);
+
+ private IPlatformFactory platformFactory;
+
+ private static volatile PlatformUtil instance;
+
+ private PlatformUtil() {
+ boolean isWindows = Platform.OS_WIN32.equals(Platform.getOS());
+ platformFactory = isWindows ? new org.eclipse.dartboard.os.windows.PlatformFactory()
+ : new org.eclipse.dartboard.os.linux.PlatformFactory();
+ }
+
+ /**
+ * Returns a Dart SDK checker
+ *
+ * @param shell Parent shell of owner or null if none
+ * @param isFlutter Flag set true if Dart SDK is inside Flutter
+ * @return DartSdkChecker object
+ */
+ public DartSdkChecker getDartSdkChecker(Shell shell, boolean isFlutter) {
+ return platformFactory.getDartSdkChecker(shell, isFlutter);
+ }
+
+ /**
+ * Returns path to given command or null if not found
+ *
+ * @param program Command as entered by user
+ * @return Optional object of parametric type Path
+ * @throws ExecutionException
+ */
+ public Optional getLocation(String program) throws ExecutionException {
+ return platformFactory.getSdkLocator().getLocation(program);
+ }
+
+ /**
+ * Returns path to given command or null if not found
+ *
+ * @param program Command as entered by user
+ * @param interactive Flag if set true allows the user to type commands. Not
+ * supported on Windows.
+ * @return Optional object of parametric type Path
+ * @throws ExecutionException
+ */
+ public Optional getLocation(String program, boolean interactive) throws ExecutionException {
+ return platformFactory.getSdkLocator().getLocation(program, interactive);
+ }
+
+ /**
+ * Returns the path to an executable within the Dart SDK bin directory in a
+ * system agnostic format.
+ *
+ * The difference to {@link #getTool(String)} is that this method returns the
+ * path to an .exe file (on windows).
+ *
+ * These executables are: dart and dartaotruntime
+ *
+ * @param sdkLocation Absolute location of Dart SDK
+ * @param name - The name of the executable
+ * @return The path to the executable valid for the host operating system
+ */
+ public String getExecutable(String sdkLocation, String name) {
+ return platformFactory.getSdkLocator().resolveExecutablePath(sdkLocation, name);
+ }
+
+ /**
+ * Returns the path to a tool within the Dart SDK bin directory in a system
+ * agnostic format. The appropriate file extension, if any, is also accepted eg.
+ * ".bat" on Windows.
+ *
+ * A tool in the Dart SDK bin directory is any of the various executables, that
+ * are .bat files on the windows version of the SDK.
+ *
+ * These tools are: dart2aot, dart2js, dartanalyzer, dartdevc, dartdoc, dartfmt,
+ * pub
+ *
+ * @param sdkLocation Absolute location of Dart SDK
+ * @param name - The name of the tool
+ * @return The path to the tool valid for the host operating system
+ */
+ public String resolveToolPath(String sdkLocation, String name) {
+ return platformFactory.getSdkLocator().resolveToolPath(sdkLocation, name);
+ }
+
+ public static PlatformUtil getInstance() {
+ if (instance == null) {
+
+ synchronized (PlatformUtil.class) {
+ // The following null check is supposedly thread safe when 'instance' is
+ // volatile
+ if (instance == null)
+ instance = new PlatformUtil();
+ }
+ }
+
+ return instance;
+ }
+
+ private static Object getClass(String nameClass) {
+ try {
+ return (Class.forName(nameClass)).newInstance();
+ } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
+ LOG.log(DartLog.createError("Class.forName() failed", e)); //$NON-NLS-1$
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+}
diff --git a/org.eclipse.dartboard/src/org/eclipse/dartboard/util/SDKLocator.java b/org.eclipse.dartboard/src/org/eclipse/dartboard/util/SDKLocator.java
deleted file mode 100644
index a97f730..0000000
--- a/org.eclipse.dartboard/src/org/eclipse/dartboard/util/SDKLocator.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package org.eclipse.dartboard.util;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Optional;
-
-import org.eclipse.core.runtime.Platform;
-
-public class SDKLocator {
-
- public static final boolean IS_WINDOWS = Platform.OS_WIN32.equals(Platform.getOS());
-
- /**
- * Returns a {@link Path} containing the location of the Dart SDK folder.
- *
- * This method finds the location of the Dart SDK on the system, if installed.
- * On *nix based systems it tries to locate the Dart binary by using the
- * {@code which} command. Typically the output is a symbolic link to the actual
- * binary. Since the Dart SDK installation folder contains more binaries that we
- * need, we resolve the symbolic link and return the path to the /bin directory
- * inside the SDK installation folder.
- *
- * On Windows this method uses the where command to locate the binary.
- *
- * @return - An {@link Optional} of {@link Path} containing the path to the
- * {@code /bin} folder inside the Dart SDK installation directory or
- * empty if the SDK is not found on the host machine.
- */
- public static Optional getDartLocation() throws IOException, InterruptedException {
- return getLocation("dart", false); //$NON-NLS-1$
- }
-
- public static Optional getFlutterLocation() throws IOException, InterruptedException {
- return getLocation("flutter", true); //$NON-NLS-1$
- }
-
- public static Optional getLocation(String program, boolean interactive)
- throws IOException, InterruptedException {
- Path path = null;
- String[] command;
- if (IS_WINDOWS) {
- command = new String[] { "cmd", "/c", "where " + program }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
- } else {
- String shell = getShell();
- if (interactive) {
- command = new String[] { shell, "-i", "-c", "which " + program }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
- } else {
- command = new String[] { shell, "-c", "which " + program }; //$NON-NLS-1$ //$NON-NLS-2$
- }
- }
-
- ProcessBuilder processBuilder = new ProcessBuilder();
- processBuilder.command(command);
- Process process = processBuilder.start();
- process.waitFor();
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
- String location = reader.readLine();
-
- if (location != null) {
- path = Paths.get(location);
- path = path.toRealPath().getParent();
- }
- }
-
- // TODO: Try different default installs (need to collect them)
- return Optional.ofNullable(path);
- }
-
- public static String getShell() throws IOException, InterruptedException {
- ProcessBuilder builder = new ProcessBuilder("/bin/sh", "-c", "echo $SHELL"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
- Process process = builder.start();
- process.waitFor();
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
- String location = reader.readLine();
- return location;
- }
- }
-}
diff --git a/org.eclipse.dartboard/src/org/eclipse/dartboard/util/SdkLocator.java b/org.eclipse.dartboard/src/org/eclipse/dartboard/util/SdkLocator.java
new file mode 100644
index 0000000..ef6bb4a
--- /dev/null
+++ b/org.eclipse.dartboard/src/org/eclipse/dartboard/util/SdkLocator.java
@@ -0,0 +1,135 @@
+/*******************************************************************************
+ * Copyright (c) 2020 vogella GmbH and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Andrew Bowley
+ *******************************************************************************/
+package org.eclipse.dartboard.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Base support for locating SDK artifacts
+ *
+ * @author Andrew Bowley
+ *
+ */
+public abstract class SdkLocator {
+
+ protected SdkLocator() {
+ }
+
+ /**
+ * Returns arguments to execute a query in a command shell which returns the
+ * path to a given command
+ *
+ * @param program Command to locate
+ * @param interactive Flag if set true allows the user to type commands. May be
+ * ignored if no OS support.
+ * @return String[]
+ * @throws ExecutionException
+ */
+ public abstract String[] getLocationCommand(String program, boolean interactive) throws ExecutionException;
+
+ /**
+ * Returns the path to a tool within the SDK bin directory in a system agnostic
+ * format. The appropriate file extension, if any, is also accepted eg. ".bat"
+ * on Windows.
+ *
+ * A tool in the Dart SDK bin directory is any of the various executables, that
+ * are .bat files on the windows version of the SDK.
+ *
+ * These tools are: dart2aot, dart2js, dartanalyzer, dartdevc, dartdoc, dartfmt,
+ * pub
+ *
+ * @param sdkLocation Absolute location of Dart SDK
+ * @param name - The name of the tool
+ * @return The path to the tool valid for the host operating system
+ */
+ public abstract String resolveToolPath(String sdkLocation, String name);
+
+ /**
+ * Returns the path to an executable within the SDK bin directory in a system
+ * agnostic format.
+ *
+ * The difference to {@link #resolveToolPath(String)} is that, on Windows, this
+ * method returns the path to an .exe file.
+ *
+ * These executables are: dart and dartaotruntime
+ *
+ * @param sdkLocation Absolute location of Dart SDK
+ * @param name - The name of the executable
+ * @return The path to the executable valid for the host operating system
+ */
+ public abstract String resolveExecutablePath(String sdkLocation, String name);
+
+ /**
+ * Returns path to given command or null if not found
+ *
+ * @param program Command as entered by user
+ * @return Optional object of parametric type Path
+ * @throws ExecutionException
+ */
+ public Optional getLocation(String program) throws ExecutionException {
+ return getLocation(program, false);
+ }
+
+ /**
+ * Returns path to SDK home of given command or null if not found. Assumes the
+ * command is in the 'bin' folder directly under home or in home itself
+ *
+ * @param program Command as entered by user
+ * @param interactive Flag if set true allows the user to type commands. Not
+ * supported on Windows.
+ * @return Optional object of parametric type Path
+ * @throws ExecutionException
+ */
+ public Optional getLocation(String program, boolean interactive) throws ExecutionException {
+
+ Path path = null;
+ String[] command = getLocationCommand(program, interactive);
+ ProcessBuilder processBuilder = new ProcessBuilder();
+ processBuilder.command(command);
+ Process process;
+ try {
+ process = processBuilder.start();
+ process.waitFor();
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
+ String location = reader.readLine();
+ if (location != null) {
+ try {
+ path = Paths.get(location);
+ } catch (InvalidPathException e) {
+ return Optional.ofNullable(null);
+ }
+ path = path.toRealPath().getParent();
+ if ((path == null) || (path.getFileName() == null))
+ return Optional.ofNullable(null);
+ if (path.getFileName().toString().equals("bin")) { //$NON-NLS-1$
+ path = path.getParent();
+ if (path == null)
+ return Optional.ofNullable(null);
+ }
+ }
+ // TODO: Try different default installs (need to collect them)
+ return Optional.ofNullable(path);
+ }
+ } catch (IOException | InterruptedException e) {
+ throw new ExecutionException(String.format("Error while locating command %s", program), e); //$NON-NLS-1$
+ }
+ }
+}