don't add SDK if setupSdk() failed (PY-1285)
[idea/community.git] / platform / lang-impl / src / com / intellij / openapi / projectRoots / impl / SdkConfigurationUtil.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.openapi.projectRoots.impl;
18
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.application.ModalityState;
21 import com.intellij.openapi.fileChooser.FileChooser;
22 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
23 import com.intellij.openapi.fileChooser.FileChooserDialog;
24 import com.intellij.openapi.fileChooser.FileChooserFactory;
25 import com.intellij.openapi.module.Module;
26 import com.intellij.openapi.module.ModuleManager;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.project.ProjectBundle;
29 import com.intellij.openapi.project.ProjectManager;
30 import com.intellij.openapi.projectRoots.ProjectJdkTable;
31 import com.intellij.openapi.projectRoots.Sdk;
32 import com.intellij.openapi.projectRoots.SdkAdditionalData;
33 import com.intellij.openapi.projectRoots.SdkType;
34 import com.intellij.openapi.roots.ModifiableRootModel;
35 import com.intellij.openapi.roots.ModuleRootManager;
36 import com.intellij.openapi.roots.ProjectRootManager;
37 import com.intellij.openapi.ui.Messages;
38 import com.intellij.openapi.ui.popup.JBPopupFactory;
39 import com.intellij.openapi.ui.popup.ListPopup;
40 import com.intellij.openapi.ui.popup.ListPopupStep;
41 import com.intellij.openapi.ui.popup.PopupStep;
42 import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
43 import com.intellij.openapi.util.Computable;
44 import com.intellij.openapi.util.io.FileUtil;
45 import com.intellij.openapi.vfs.LocalFileSystem;
46 import com.intellij.openapi.vfs.VirtualFile;
47 import com.intellij.util.ArrayUtil;
48 import com.intellij.util.Consumer;
49 import org.jetbrains.annotations.NotNull;
50 import org.jetbrains.annotations.Nullable;
51
52 import javax.swing.*;
53 import java.awt.*;
54 import java.util.*;
55 import java.util.List;
56
57 /**
58  * @author yole
59  */
60 public class SdkConfigurationUtil {
61   private SdkConfigurationUtil() {
62   }
63
64   @Nullable
65   public static Sdk createSdk(final Project project, final Sdk[] existingSdks, final SdkType... sdkTypes) {
66     if (sdkTypes.length == 0) return null;
67     final FileChooserDescriptor descriptor = createCompositeDescriptor(sdkTypes);
68     final FileChooserDialog dialog = FileChooserFactory.getInstance().createFileChooser(descriptor, project);
69     String suggestedPath = sdkTypes [0].suggestHomePath();
70     VirtualFile suggestedDir = suggestedPath == null
71                                ? null
72                                :  LocalFileSystem.getInstance().findFileByPath(FileUtil.toSystemIndependentName(suggestedPath));
73     final VirtualFile[] selection = dialog.choose(suggestedDir, project);
74     if (selection.length > 0) {
75       for (SdkType sdkType : sdkTypes) {
76         if (sdkType.isValidSdkHome(selection[0].getPath())) {
77           return setupSdk(existingSdks, selection[0], sdkType, false, null, null);
78         }
79       }
80     }
81     return null;
82   }
83
84   private static FileChooserDescriptor createCompositeDescriptor(final SdkType... sdkTypes) {
85     FileChooserDescriptor descriptor0 = sdkTypes [0].getHomeChooserDescriptor();
86     FileChooserDescriptor descriptor = new FileChooserDescriptor(descriptor0.isChooseFiles(), descriptor0.isChooseFolders(),
87                                                                  descriptor0.isChooseJars(), descriptor0.isChooseJarsAsFiles(),
88                                                                  descriptor0.isChooseJarContents(), descriptor0.isChooseMultiple()) {
89
90       @Override
91       public void validateSelectedFiles(final VirtualFile[] files) throws Exception {
92         if (files.length > 0) {
93           for (SdkType type : sdkTypes) {
94             if (type.isValidSdkHome(files[0].getPath())) {
95               return;
96             }
97           }
98         }
99         String message = files.length > 0 && files[0].isDirectory()
100                          ? ProjectBundle.message("sdk.configure.home.invalid.error", sdkTypes [0].getPresentableName())
101                          : ProjectBundle.message("sdk.configure.home.file.invalid.error", sdkTypes [0].getPresentableName());
102         throw new Exception(message);
103       }
104     };
105     descriptor.setTitle(descriptor0.getTitle());
106     return descriptor;
107   }
108
109   public static void addSdk(@NotNull final Sdk sdk) {
110     ApplicationManager.getApplication().runWriteAction(new Runnable() {
111       @Override
112       public void run() {
113         ProjectJdkTable.getInstance().addJdk(sdk);
114       }
115     });
116   }
117
118   public static void removeSdk(final Sdk sdk) {
119     ApplicationManager.getApplication().runWriteAction(new Runnable() {
120       public void run() {
121         ProjectJdkTable.getInstance().removeJdk(sdk);
122       }
123     });
124   }
125
126   @Nullable
127   public static Sdk setupSdk(final Sdk[] allSdks,
128                              final VirtualFile homeDir, final SdkType sdkType, final boolean silent,
129                              @Nullable final SdkAdditionalData additionalData,
130                              @Nullable final String customSdkSuggestedName) {
131     final List<Sdk> sdksList = Arrays.asList(allSdks);
132
133     final ProjectJdkImpl sdk;
134     try {
135       final String sdkName = customSdkSuggestedName == null
136                              ? createUniqueSdkName(sdkType, homeDir.getPath(), sdksList)
137                              : createUniqueSdkName(customSdkSuggestedName, sdksList);
138       sdk = new ProjectJdkImpl(sdkName, sdkType);
139
140       if (additionalData != null) {
141         // additional initialization.
142         // E.g. some ruby sdks must be initialized before
143         // setupSdkPaths() method invocation
144         sdk.setSdkAdditionalData(additionalData);
145       }
146
147       sdk.setHomePath(homeDir.getPath());
148       sdkType.setupSdkPaths(sdk);
149     }
150     catch (Exception e) {
151       if (!silent) {
152         Messages.showErrorDialog("Error configuring SDK: " +
153                                  e.getMessage() +
154                                  ".\nPlease make sure that " +
155                                  FileUtil.toSystemDependentName(homeDir.getPath()) +
156                                  " is a valid home path for this SDK type.", "Error configuring SDK");
157       }
158       return null;
159     }
160     return sdk;
161   }
162
163   public static void setDirectoryProjectSdk(final Project project, final Sdk sdk) {
164     ApplicationManager.getApplication().runWriteAction(new Runnable() {
165       public void run() {
166         ProjectRootManager.getInstance(project).setProjectJdk(sdk);
167         final Module[] modules = ModuleManager.getInstance(project).getModules();
168         if (modules.length > 0) {
169           final ModifiableRootModel model = ModuleRootManager.getInstance(modules[0]).getModifiableModel();
170           model.inheritSdk();
171           model.commit();
172         }
173       }
174     });
175   }
176
177   public static void configureDirectoryProjectSdk(final Project project, final SdkType... sdkTypes) {
178     Sdk existingSdk = ProjectRootManager.getInstance(project).getProjectJdk();
179     if (existingSdk != null && ArrayUtil.contains(existingSdk.getSdkType(), sdkTypes)) {
180       return;
181     }
182
183     Sdk sdk = findOrCreateSdk(sdkTypes);
184     if (sdk != null) {
185       setDirectoryProjectSdk(project, sdk);
186     }
187   }
188
189   @Nullable
190   public static Sdk findOrCreateSdk(final SdkType... sdkTypes) {
191     final Project defaultProject = ProjectManager.getInstance().getDefaultProject();
192     final Sdk sdk = ProjectRootManager.getInstance(defaultProject).getProjectJdk();
193     if (sdk != null) {
194       for (SdkType type : sdkTypes) {
195         if (sdk.getSdkType() == type) {
196           return sdk;
197         }
198       }
199     }
200     for (SdkType type : sdkTypes) {
201       List<Sdk> sdks = ProjectJdkTable.getInstance().getSdksOfType(type);
202       if (sdks.size() > 0) {
203         return sdks.get(0);
204       }
205     }
206     for (SdkType sdkType : sdkTypes) {
207       final String suggestedHomePath = sdkType.suggestHomePath();
208       if (suggestedHomePath != null && sdkType.isValidSdkHome(suggestedHomePath)) {
209         VirtualFile sdkHome = ApplicationManager.getApplication().runWriteAction(new Computable<VirtualFile>() {
210           public VirtualFile compute() {
211             return LocalFileSystem.getInstance().refreshAndFindFileByPath(suggestedHomePath);
212           }
213         });
214         if (sdkHome != null) {
215           final Sdk newSdk = setupSdk(ProjectJdkTable.getInstance().getAllJdks(), sdkHome, sdkType, true, null, null);
216           if (newSdk != null) {
217             addSdk(newSdk);
218           }
219           return newSdk;
220         }
221       }
222     }
223     return null;
224   }
225
226   public static String createUniqueSdkName(SdkType type, String home, final Collection<Sdk> sdks) {
227     return createUniqueSdkName(type.suggestSdkName(null, home), sdks);
228   }
229
230   public static String createUniqueSdkName(final String suggestedName, final Collection<Sdk> sdks) {
231     final Set<String> names = new HashSet<String>();
232     for (Sdk jdk : sdks) {
233       names.add(jdk.getName());
234     }
235     String newSdkName = suggestedName;
236     int i = 0;
237     while (names.contains(newSdkName)) {
238       newSdkName = suggestedName + " (" + (++i) + ")";
239     }
240     return newSdkName;
241   }
242
243   @Nullable
244   public static String selectSdkHome(final Component parentComponent, final SdkType sdkType){
245     final FileChooserDescriptor descriptor = sdkType.getHomeChooserDescriptor();
246     VirtualFile[] files = FileChooser.chooseFiles(parentComponent, descriptor, getSuggestedSdkRoot(sdkType));
247     if (files.length != 0){
248       final String path = files[0].getPath();
249       if (sdkType.isValidSdkHome(path)) return path;
250       String adjustedPath = sdkType.adjustSelectedSdkHome(path);
251       return sdkType.isValidSdkHome(adjustedPath) ? adjustedPath : null;
252     }
253     return null;
254   }
255
256   @Nullable
257   public static VirtualFile getSuggestedSdkRoot(SdkType sdkType) {
258     final String homepath = sdkType.suggestHomePath();
259     if (homepath == null) return null;
260     return LocalFileSystem.getInstance().findFileByPath(homepath);
261   }
262
263   public static void suggestAndAddSdk(final Project project,
264                                        SdkType sdkType,
265                                        final Sdk[] existingSdks,
266                                        JComponent popupOwner,
267                                        Consumer<Sdk> callback) {
268     Collection<String> sdkHomes = sdkType.suggestHomePaths();
269     List<String> suggestedSdkHomes = filterExistingPaths(sdkType, sdkHomes, existingSdks);
270     if (suggestedSdkHomes.size() > 0) {
271       suggestedSdkHomes.add(null);
272       showSuggestedHomesPopup(project, sdkType, existingSdks, suggestedSdkHomes, popupOwner, callback);
273     }
274     else {
275       Sdk sdk = createSdk(project, existingSdks, sdkType);
276       callback.consume(sdk);
277     }
278   }
279
280   private static void showSuggestedHomesPopup(final Project project,
281                                               final SdkType sdkType,
282                                               final Sdk[] existingSdks,
283                                               List<String> suggestedSdkHomes,
284                                               JComponent popupOwner,
285                                               final Consumer<Sdk> callback) {
286     ListPopupStep sdkHomesStep = new BaseListPopupStep<String>("Select Interpreter Path", suggestedSdkHomes) {
287       @NotNull
288       @Override
289       public String getTextFor(String value) {
290         return value == null ? "Specify Other..." : FileUtil.toSystemDependentName(value);
291       }
292
293       @Override
294       public PopupStep onChosen(String selectedValue, boolean finalChoice) {
295         if (selectedValue == null) {
296           ApplicationManager.getApplication().invokeLater(new Runnable() {
297             @Override
298             public void run() {
299               Sdk sdk = createSdk(project, existingSdks, sdkType);
300               callback.consume(sdk);
301             }
302           }, ModalityState.current());
303         }
304         else {
305           Sdk sdk = setupSdk(existingSdks, LocalFileSystem.getInstance().findFileByPath(selectedValue),
306                                                   sdkType, false, null, null);
307           callback.consume(sdk);
308         }
309         return FINAL_CHOICE;
310       }
311     };
312     final ListPopup popup = JBPopupFactory.getInstance().createListPopup(sdkHomesStep);
313     popup.showUnderneathOf(popupOwner);
314   }
315
316   private static List<String> filterExistingPaths(SdkType sdkType, Collection<String> sdkHomes, final Sdk[] sdks) {
317     List<String> result = new ArrayList<String>();
318     for (String sdkHome : sdkHomes) {
319       if (findByPath(sdkType, sdks, sdkHome) == null) {
320         result.add(sdkHome);
321       }
322     }
323     return result;
324   }
325
326   @Nullable
327   private static Sdk findByPath(SdkType sdkType, Sdk[] sdks, String sdkHome) {
328     for (Sdk sdk : sdks) {
329       if (sdk.getSdkType() == sdkType &&
330           FileUtil.pathsEqual(FileUtil.toSystemIndependentName(sdk.getHomePath()), FileUtil.toSystemIndependentName(sdkHome))) {
331         return sdk;
332       }
333     }
334     return null;
335   }
336 }