/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.idea.welcome;
import com.android.tools.idea.wizard.ScopedStateStore;
import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.projectRoots.JavaSdk;
import com.intellij.openapi.projectRoots.JavaSdkVersion;
import com.intellij.openapi.projectRoots.JdkUtil;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.JBColor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Wizard step for specifying JDK location.
*/
public class JdkLocationStep extends FirstRunWizardStep {
private static final String MAC_JDKS_DIR = "/Library/Java/JavaVirtualMachines/";
private static final String MAC_JDK_CONTENT_PATH = "/Contents/Home";
private static final String WINDOWS_JDKS_DIR = "C:\\Program Files\\Java";
private static final String JDK_URL = "http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html";
private final ScopedStateStore.Key<String> myPathKey;
private JPanel myContents;
private TextFieldWithBrowseButton myJdkPath;
private JButton myDownloadPageLink;
private JLabel myError;
private JButton myDetectButton;
private JLabel myDetectLabel;
// Show errors only after the user touched the value
private boolean myUserInput = false;
public JdkLocationStep(ScopedStateStore.Key<String> pathKey) {
super("Java Settings");
myPathKey = pathKey;
myDownloadPageLink.setText(getLinkText());
WelcomeUIUtils.makeButtonAHyperlink(myDownloadPageLink, JDK_URL);
myDownloadPageLink.getParent().invalidate();
setComponent(myContents);
myError.setForeground(JBColor.red);
FileChooserDescriptor folderDescriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor();
myJdkPath.addBrowseFolderListener("Select JDK Location", "Select compatible JDK location", null, folderDescriptor);
myError.setText(null);
// Does not seem like there's reliable default for JDK locations on Linux...
// Hence, "Detect" is only available on Windows/Mac
if (SystemInfo.isMac || SystemInfo.isWindows) {
myDetectButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
ProgressManager.getInstance().run(new DetectJdkTask());
}
});
}
else {
myDetectButton.setVisible(false);
myDetectLabel.setVisible(false);
}
}
private static String getLinkText() {
// TODO ARM support?
if (SystemInfo.isMac) {
return "Mac OS X x64";
}
else if (SystemInfo.isLinux) {
return SystemInfo.is32Bit ? "Linux x86" : "Linux x64";
}
else if (SystemInfo.isWindows) {
return SystemInfo.is32Bit ? "Windows x86" : "Windows x64";
}
else {
return SystemInfo.OS_NAME;
}
}
private static boolean isJdk7(String path) {
String jdkVersion = JavaSdk.getJdkVersion(path);
if (jdkVersion != null) {
JavaSdkVersion version = JavaSdk.getInstance().getVersion(jdkVersion);
if (version != null && !version.isAtLeast(JavaSdkVersion.JDK_1_7)) {
return false;
}
}
return true;
}
@Nullable
private static String validateJdkLocation(@Nullable String location) {
if (StringUtil.isEmpty(location)) {
return "Path is empty";
}
if (!JdkUtil.checkForJdk(new File(location))) {
return "Path specified is not a valid JDK location";
}
if (!isJdk7(location)) {
return "JDK 7.0 or newer is required";
}
return null;
}
@NotNull
private static Iterable<String> getCandidatePaths() {
if (SystemInfo.isMac) {
return getMacCandidateJdks();
}
else if (SystemInfo.isWindows) {
return getWindowsCandidateJdks();
}
else {
// No default location for Linux...
return Collections.emptyList();
}
}
@NotNull
private static Iterable<String> getMacCandidateJdks() {
// See http://docs.oracle.com/javase/7/docs/webnotes/install/mac/mac-jdk.html
return getCandidatePaths(MAC_JDKS_DIR, MAC_JDK_CONTENT_PATH);
}
@NotNull
private static Iterable<String> getWindowsCandidateJdks() {
// See http://docs.oracle.com/javase/7/docs/webnotes/install/windows/jdk-installation-windows.html
return getCandidatePaths(WINDOWS_JDKS_DIR, "");
}
private static Iterable<String> getCandidatePaths(String basedir, final String suffix) {
final File location = new File(basedir);
if (location.isDirectory()) {
return Iterables.transform(Arrays.asList(location.list()), new Function<String, String>() {
@Override
public String apply(@Nullable String dir) {
return new File(location, dir + suffix).getAbsolutePath();
}
});
}
return Collections.emptyList();
}
@Override
public boolean validate() {
String path = myState.get(myPathKey);
if (!StringUtil.isEmpty(path)) {
myUserInput = true;
}
String message = validateJdkLocation(path);
if (myUserInput) {
setErrorHtml(message);
}
return StringUtil.isEmpty(message);
}
@Override
public void init() {
register(myPathKey, myJdkPath);
}
@Override
public JComponent getPreferredFocusedComponent() {
return myJdkPath;
}
@NotNull
@Override
public JLabel getMessageLabel() {
return myError;
}
private class DetectJdkTask extends Task.Modal {
private final AtomicBoolean myCancelled = new AtomicBoolean(false);
private String myPath = null;
public DetectJdkTask() {
super(null, "Detect JDK", true);
}
@Override
public void run(@NotNull ProgressIndicator indicator) {
indicator.setIndeterminate(true);
String topVersion = null;
String chosenPath = null;
for (String path : getCandidatePaths()) {
if (myCancelled.get()) {
return;
}
if (StringUtil.isEmpty(validateJdkLocation(path))) {
String version = JavaSdk.getInstance().getVersionString(path);
if (topVersion == null || version == null || topVersion.compareTo(version) < 0) {
topVersion = version;
chosenPath = path;
}
}
}
myPath = chosenPath;
}
@Override
public void onSuccess() {
if (myPath != null) {
myState.put(myPathKey, myPath);
}
super.onSuccess();
}
@Override
public void onCancel() {
myCancelled.set(true);
}
}
}