Cleanup: NotNull/Nullable
[idea/community.git] / java / idea-ui / src / com / intellij / ide / util / projectWizard / JdkChooserPanel.java
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.ide.util.projectWizard;
3
4 import com.intellij.ide.IdeBundle;
5 import com.intellij.openapi.application.ApplicationManager;
6 import com.intellij.openapi.application.ModalityState;
7 import com.intellij.openapi.project.Project;
8 import com.intellij.openapi.project.ProjectBundle;
9 import com.intellij.openapi.project.ProjectManager;
10 import com.intellij.openapi.projectRoots.*;
11 import com.intellij.openapi.projectRoots.impl.JavaHomeFinder;
12 import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl;
13 import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil;
14 import com.intellij.openapi.projectRoots.ui.ProjectJdksEditor;
15 import com.intellij.openapi.roots.ProjectRootManager;
16 import com.intellij.openapi.roots.ui.OrderEntryAppearanceService;
17 import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable;
18 import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel;
19 import com.intellij.openapi.ui.DialogWrapper;
20 import com.intellij.openapi.ui.LoadingDecorator;
21 import com.intellij.openapi.util.Comparing;
22 import com.intellij.openapi.util.registry.Registry;
23 import com.intellij.openapi.vfs.LocalFileSystem;
24 import com.intellij.openapi.vfs.VirtualFile;
25 import com.intellij.openapi.wm.ex.WindowManagerEx;
26 import com.intellij.ui.*;
27 import com.intellij.ui.components.JBList;
28 import com.intellij.util.ArrayUtil;
29 import com.intellij.util.containers.ContainerUtil;
30 import com.intellij.util.lang.JavaVersion;
31 import com.intellij.util.ui.StatusText;
32 import gnu.trove.TIntArrayList;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
35
36 import javax.swing.*;
37 import javax.swing.event.ListSelectionEvent;
38 import javax.swing.event.ListSelectionListener;
39 import java.awt.*;
40 import java.awt.event.ActionEvent;
41 import java.awt.event.MouseEvent;
42 import java.util.List;
43 import java.util.*;
44
45 public class JdkChooserPanel extends JPanel {
46   private final @Nullable Project myProject;
47   private final DefaultListModel<Sdk> myListModel;
48   private final JBList<Sdk> myList;
49   private final LoadingDecorator myLoadingDecorator;
50   private Sdk myCurrentJdk;
51   private SdkType[] myAllowedJdkTypes = null;
52
53   public JdkChooserPanel(@Nullable final Project project) {
54     super(new BorderLayout());
55     myProject = project;
56     myListModel = new DefaultListModel<>();
57     myList = new JBList<>(myListModel);
58     myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
59     myList.setCellRenderer(new ColoredListCellRenderer<Sdk>() {
60       @Override
61       protected void customizeCellRenderer(@NotNull JList<? extends Sdk> list, Sdk value, int index, boolean selected, boolean hasFocus) {
62         OrderEntryAppearanceService.getInstance().forJdk(value, false, selected, true).customize(this);
63       }
64     });
65
66     myList.addListSelectionListener(e -> myCurrentJdk = myList.getSelectedValue());
67     new ClickListener() {
68       @Override
69       public boolean onClick(@NotNull MouseEvent e, int clickCount) {
70         if (myProject == null) {
71           editJdkTable();
72           return true;
73         }
74         return false;
75       }
76     }.installOn(myList);
77
78     JPanel panel = new JPanel(new BorderLayout()) {
79       @Override
80       public Dimension getPreferredSize() {
81         Dimension size = super.getPreferredSize();
82         size.height = Math.max(size.height, myList.getVisibleRowCount() * myList.getFixedCellHeight());
83         return size;
84       }
85     };
86     panel.add(ScrollPaneFactory.createScrollPane(myList), BorderLayout.CENTER);
87     myLoadingDecorator = new LoadingDecorator(panel, project, 0, true);
88     myLoadingDecorator.setLoadingText("Looking for JDKs...");
89     add(myLoadingDecorator.getComponent(), BorderLayout.CENTER);
90     if (myListModel.getSize() > 0) {
91       myList.setSelectedIndex(0);
92     }
93   }
94
95   /**
96    * Sets the JDK types which may be shown in the panel.
97    *
98    * @param allowedJdkTypes the array of JDK types which may be shown, or null if all JDK types are allowed.
99    */
100   public void setAllowedJdkTypes(@Nullable final SdkType[] allowedJdkTypes) {
101     myAllowedJdkTypes = allowedJdkTypes;
102   }
103
104   public Sdk getChosenJdk() {
105     return myCurrentJdk;
106   }
107
108   public Object[] getAllJdks() {
109     return myListModel.toArray();
110   }
111
112   public void editJdkTable() {
113     ProjectJdksEditor editor = new ProjectJdksEditor(myList.getSelectedValue(),
114                                                      myProject != null ? myProject : ProjectManager.getInstance().getDefaultProject(),
115                                                      myList);
116     if (editor.showAndGet()) {
117       Sdk selectedJdk = editor.getSelectedJdk();
118       updateList(selectedJdk, null);
119     }
120   }
121
122   public void updateList(final Sdk selectedJdk, final @Nullable SdkType type) {
123     updateList(selectedJdk, type, null);
124   }
125
126   public void updateList(final Sdk selectedJdk, final @Nullable SdkType type, final @Nullable Sdk[] globalSdks) {
127     final int[] selectedIndices = myList.getSelectedIndices();
128     fillList(type, globalSdks);
129     // restore selection
130     if (selectedJdk != null) {
131       TIntArrayList list = new TIntArrayList();
132       for (int i = 0; i < myListModel.size(); i++) {
133         Sdk jdk = myListModel.getElementAt(i);
134         if (Comparing.strEqual(jdk.getName(), selectedJdk.getName())){
135           list.add(i);
136         }
137       }
138       final int[] indicesToSelect = list.toNativeArray();
139       if (indicesToSelect.length > 0) {
140         myList.setSelectedIndices(indicesToSelect);
141       }
142       else if (myList.getModel().getSize() > 0) {
143         myList.setSelectedIndex(0);
144       }
145     }
146     else {
147       if (selectedIndices.length > 0) {
148         myList.setSelectedIndices(selectedIndices);
149       }
150       else {
151         myList.setSelectedIndex(0);
152       }
153     }
154
155     myCurrentJdk = myList.getSelectedValue();
156   }
157
158   public JList getPreferredFocusedComponent() {
159     return myList;
160   }
161
162   public void fillList(final @Nullable SdkType type, final @Nullable Sdk[] globalSdks) {
163     final ArrayList<Sdk> knownJdks = new ArrayList<>();
164     if (myProject == null || myProject.isDefault()) {
165       final Sdk[] allJdks = globalSdks != null ? globalSdks : ProjectJdkTable.getInstance().getAllJdks();
166       knownJdks.addAll(getCompatibleJdks(type, Arrays.asList(allJdks)));
167     }
168     else {
169       final ProjectSdksModel projectJdksModel = ProjectStructureConfigurable.getInstance(myProject).getProjectJdksModel();
170       if (!projectJdksModel.isInitialized()){ //should be initialized
171         projectJdksModel.reset(myProject);
172       }
173       final Collection<Sdk> collection = projectJdksModel.getProjectSdks().values();
174       knownJdks.addAll(getCompatibleJdks(type, collection));
175     }
176     final ArrayList<Sdk> allJdks = new ArrayList<>(knownJdks);
177
178     if (Registry.is("autodetect.all.jdks") && (type == null || type instanceof JavaSdkType)) {
179       myList.getEmptyText().setText("");
180       myLoadingDecorator.startLoading(false);
181       ApplicationManager.getApplication().executeOnPooledThread(() -> {
182         List<String> suggestedPaths = JavaHomeFinder.suggestHomePaths();
183         suggestedPaths.removeAll(ContainerUtil.map(knownJdks, sdk -> sdk.getHomePath()));//remove all known path to avoid duplicates
184         ApplicationManager.getApplication().invokeLater(() -> {
185           for (String homePath : suggestedPaths) {
186             VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(homePath);
187             if (virtualFile != null) {
188               JavaSdk sdkType = JavaSdk.getInstance();
189               JavaVersion version = JavaVersion.tryParse(sdkType.getVersionString(homePath));
190               String suggestedName = version != null ? version.toString() : "";
191               Sdk jdk = sdkType.createJdk(suggestedName, homePath, false);
192               if (jdk instanceof ProjectJdkImpl) {
193                 ProjectJdkImpl tmp = SdkConfigurationUtil.createSdk(allJdks.toArray(new Sdk[0]), virtualFile, sdkType, null, suggestedName);
194                 String improvedName = tmp.getName();
195                 ((ProjectJdkImpl)jdk).setName(improvedName);
196               }
197               allJdks.add(jdk);
198             }
199           }
200           updateListModel(allJdks, knownJdks);
201           myLoadingDecorator.stopLoading();
202           myList.getEmptyText().setText(StatusText.DEFAULT_EMPTY_TEXT);
203         }, ModalityState.any());
204       });
205     } else {
206       updateListModel(allJdks, knownJdks);
207     }
208   }
209
210   private void updateListModel(ArrayList<Sdk> allJdks, ArrayList<Sdk> knownJdks) {
211     Sdk oldSelection = myList.getSelectedValue();
212
213     myListModel.clear();
214     Collections.sort(allJdks, (o1, o2) -> {
215       boolean unknown1 = !knownJdks.contains(o1);
216       boolean unknown2 = !knownJdks.contains(o2);
217       if (unknown1 != unknown2) {
218         return unknown1 ? 1 : -1;
219       }
220       String v1 = o1.getVersionString();
221       String v2 = o2.getVersionString();
222       if (v1 != null & v2 != null) {
223         try {
224           return -JavaVersion.parse(v1).compareTo(JavaVersion.parse(v2));
225         }
226         catch (IllegalArgumentException ignored) {
227           //
228         }
229       }
230       return -o1.getName().compareToIgnoreCase(o2.getName());
231     });
232     for (Sdk jdk : allJdks) {
233       myListModel.addElement(jdk);
234     }
235     if (oldSelection != null) {
236       ScrollingUtil.selectItem(myList, oldSelection);
237     }
238   }
239
240   private List<Sdk> getCompatibleJdks(final @Nullable SdkType type, final Collection<Sdk> collection) {
241     final Set<Sdk> compatibleJdks = new HashSet<>();
242     for (Sdk projectJdk : collection) {
243       if (isCompatibleJdk(projectJdk, type)) {
244         compatibleJdks.add(projectJdk);
245       }
246     }
247     return new ArrayList<>(compatibleJdks);
248   }
249
250   private boolean isCompatibleJdk(final Sdk projectJdk, final @Nullable SdkType type) {
251     if (type != null) {
252       return projectJdk.getSdkType() == type;
253     }
254     if (myAllowedJdkTypes != null) {
255       return ArrayUtil.indexOf(myAllowedJdkTypes, projectJdk.getSdkType()) >= 0;
256     }
257     return true;
258   }
259
260   public JComponent getDefaultFocusedComponent() {
261     return myList;
262   }
263
264   public void selectJdk(@Nullable Sdk defaultJdk) {
265     if (defaultJdk != null) {
266       ScrollingUtil.selectItem(myList, defaultJdk);
267     }
268   }
269
270   public void addSelectionListener(final ListSelectionListener listener) {
271     myList.addListSelectionListener(listener);
272   }
273
274   private static Sdk showDialog(final Project project, String title, final Component parent, Sdk jdkToSelect) {
275     final JdkChooserPanel jdkChooserPanel = new JdkChooserPanel(project);
276     jdkChooserPanel.fillList(null, null);
277     final MyDialog dialog = jdkChooserPanel.new MyDialog(parent);
278     if (title != null) {
279       dialog.setTitle(title);
280     }
281     if (jdkToSelect != null) {
282       jdkChooserPanel.selectJdk(jdkToSelect);
283     }
284     else {
285       ScrollingUtil.ensureSelectionExists(jdkChooserPanel.myList);
286     }
287     new DoubleClickListener() {
288       @Override
289       protected boolean onDoubleClick(MouseEvent e) {
290         dialog.clickDefaultButton();
291         return true;
292       }
293     }.installOn(jdkChooserPanel.myList);
294     return dialog.showAndGet() ? jdkChooserPanel.getChosenJdk() : null;
295   }
296
297   public static Sdk chooseAndSetJDK(final Project project) {
298     final Sdk projectJdk = ProjectRootManager.getInstance(project).getProjectSdk();
299     final Sdk jdk = showDialog(project, ProjectBundle.message("module.libraries.target.jdk.select.title"), WindowManagerEx.getInstanceEx().getFrame(project), projectJdk);
300     String path = jdk != null ? jdk.getHomePath() : null;
301     if (path == null) {
302       return null;
303     }
304     ApplicationManager.getApplication().runWriteAction(() -> {
305       ProjectJdkTable table = ProjectJdkTable.getInstance();
306       List<Sdk> sdks = table.getSdksOfType(jdk.getSdkType());
307       if (ContainerUtil.find(sdks, sdk -> path.equals(sdk.getHomePath())) == null) {
308         table.addJdk(jdk);//this jdk is unknown yet and so it has to be added to Platform-level table now
309       }
310       ProjectRootManager.getInstance(project).setProjectSdk(jdk);
311     });
312     return jdk;
313   }
314
315   public class MyDialog extends DialogWrapper implements ListSelectionListener {
316
317     public MyDialog(Component parent) {
318       super(parent, true);
319       setTitle(IdeBundle.message("title.select.jdk"));
320       init();
321       myList.addListSelectionListener(this);
322       updateOkButton();
323     }
324
325     @Override
326     protected String getDimensionServiceKey() {
327       return "#com.intellij.ide.util.projectWizard.JdkChooserPanel.MyDialog";
328     }
329
330     @Override
331     public void valueChanged(ListSelectionEvent e) {
332       updateOkButton();
333     }
334
335     private void updateOkButton() {
336       setOKActionEnabled(myList.getSelectedValue() != null);
337     }
338
339     @Override
340     public void dispose() {
341       myList.removeListSelectionListener(this);
342       super.dispose();
343     }
344
345     @Override
346     protected JComponent createCenterPanel() {
347       return JdkChooserPanel.this;
348     }
349
350     @Override
351     @NotNull
352     protected Action[] createActions() {
353       return new Action[]{new ConfigureAction(), getOKAction(), getCancelAction()};
354     }
355
356     @Override
357     public JComponent getPreferredFocusedComponent() {
358       return myList;
359     }
360
361     private final class ConfigureAction extends AbstractAction {
362       ConfigureAction() {
363         super(IdeBundle.message("button.configure.e"));
364         putValue(Action.MNEMONIC_KEY, new Integer('E'));
365       }
366
367       @Override
368       public void actionPerformed(ActionEvent e) {
369         editJdkTable();
370       }
371     }
372   }
373 }