Merge branch 'IDEA-CR-10038'
[idea/community.git] / platform / platform-impl / src / com / intellij / ide / impl / ProjectUtil.java
1 /*
2  * Copyright 2000-2016 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 package com.intellij.ide.impl;
17
18 import com.intellij.CommonBundle;
19 import com.intellij.ide.GeneralSettings;
20 import com.intellij.ide.IdeBundle;
21 import com.intellij.ide.RecentProjectsManager;
22 import com.intellij.ide.highlighter.ProjectFileType;
23 import com.intellij.openapi.application.ApplicationManager;
24 import com.intellij.openapi.application.ApplicationNamesInfo;
25 import com.intellij.openapi.application.ModalityState;
26 import com.intellij.openapi.components.ServiceKt;
27 import com.intellij.openapi.components.StorageScheme;
28 import com.intellij.openapi.components.impl.stores.IComponentStore;
29 import com.intellij.openapi.components.impl.stores.IProjectStore;
30 import com.intellij.openapi.diagnostic.Logger;
31 import com.intellij.openapi.project.Project;
32 import com.intellij.openapi.project.ProjectManager;
33 import com.intellij.openapi.project.ex.ProjectManagerEx;
34 import com.intellij.openapi.ui.Messages;
35 import com.intellij.openapi.util.ActionCallback;
36 import com.intellij.openapi.util.InvalidDataException;
37 import com.intellij.openapi.util.io.FileUtil;
38 import com.intellij.openapi.vfs.LocalFileSystem;
39 import com.intellij.openapi.vfs.VirtualFile;
40 import com.intellij.openapi.wm.*;
41 import com.intellij.projectImport.ProjectOpenProcessor;
42 import com.intellij.ui.AppIcon;
43 import com.intellij.util.SystemProperties;
44 import org.jdom.JDOMException;
45 import org.jetbrains.annotations.NotNull;
46 import org.jetbrains.annotations.Nullable;
47
48 import javax.swing.*;
49 import java.awt.*;
50 import java.io.File;
51 import java.io.IOException;
52
53 /**
54  * @author Eugene Belyaev
55  */
56 public class ProjectUtil {
57   private static final Logger LOG = Logger.getInstance("#com.intellij.ide.impl.ProjectUtil");
58
59   private ProjectUtil() { }
60
61   public static void updateLastProjectLocation(final String projectFilePath) {
62     File lastProjectLocation = new File(projectFilePath);
63     if (lastProjectLocation.isFile()) {
64       lastProjectLocation = lastProjectLocation.getParentFile(); // for directory-based project storage
65     }
66     if (lastProjectLocation == null) { // the immediate parent of the ipr file
67       return;
68     }
69     lastProjectLocation = lastProjectLocation.getParentFile(); // the candidate directory to be saved
70     if (lastProjectLocation == null) {
71       return;
72     }
73     String path = lastProjectLocation.getPath();
74     try {
75       path = FileUtil.resolveShortWindowsName(path);
76     }
77     catch (IOException e) {
78       LOG.info(e);
79       return;
80     }
81     RecentProjectsManager.getInstance().setLastProjectCreationLocation(path.replace(File.separatorChar, '/'));
82   }
83
84   /**
85    * @param project cannot be null
86    */
87   public static boolean closeAndDispose(@NotNull final Project project) {
88     return ProjectManagerEx.getInstanceEx().closeAndDispose(project);
89   }
90
91   /**
92    * @param path                project file path
93    * @param projectToClose      currently active project
94    * @param forceOpenInNewFrame forces opening in new frame
95    * @return project by path if the path was recognized as IDEA project file or one of the project formats supported by
96    *         installed importers (regardless of opening/import result)
97    *         null otherwise
98    */
99   @Nullable
100   public static Project openOrImport(@NotNull String path, Project projectToClose, boolean forceOpenInNewFrame) {
101     VirtualFile virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(path);
102     if (virtualFile == null) return null;
103     virtualFile.refresh(false, false);
104
105     Project existing = findAndFocusExistingProjectForPath(path);
106     if (existing != null) return existing;
107
108     ProjectOpenProcessor strong = ProjectOpenProcessor.getStrongImportProvider(virtualFile);
109     if (strong != null) {
110       return strong.doOpenProject(virtualFile, projectToClose, forceOpenInNewFrame);
111     }
112
113     if (path.endsWith(ProjectFileType.DOT_DEFAULT_EXTENSION) ||
114         virtualFile.isDirectory() && virtualFile.findChild(Project.DIRECTORY_STORE_FOLDER) != null) {
115       return openProject(path, projectToClose, forceOpenInNewFrame);
116     }
117
118     if (virtualFile.isDirectory()) {
119       for (VirtualFile child : virtualFile.getChildren()) {
120         final String childPath = child.getPath();
121         if (childPath.endsWith(ProjectFileType.DOT_DEFAULT_EXTENSION)) {
122           return openProject(childPath, projectToClose, forceOpenInNewFrame);
123         }
124       }
125     }
126
127     ProjectOpenProcessor provider = ProjectOpenProcessor.getImportProvider(virtualFile);
128     if (provider != null) {
129       final Project project = provider.doOpenProject(virtualFile, projectToClose, forceOpenInNewFrame);
130
131       if (project != null) {
132         ApplicationManager.getApplication().invokeLater(new Runnable() {
133           @Override
134           public void run() {
135             if (!project.isDisposed()) {
136               final ToolWindow toolWindow = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.PROJECT_VIEW);
137               if (toolWindow != null) {
138                 toolWindow.activate(null);
139               }
140             }
141           }
142         }, ModalityState.NON_MODAL);
143       }
144
145       return project;
146     }
147
148     return null;
149   }
150
151   @Nullable
152   public static Project openProject(final String path, @Nullable Project projectToClose, boolean forceOpenInNewFrame) {
153     File file = new File(path);
154     if (!file.exists()) {
155       Messages.showErrorDialog(IdeBundle.message("error.project.file.does.not.exist", path), CommonBundle.getErrorTitle());
156       return null;
157     }
158
159     if (file.isDirectory() && !new File(file, Project.DIRECTORY_STORE_FOLDER).exists()) {
160       String message = IdeBundle.message("error.project.file.does.not.exist", new File(file, Project.DIRECTORY_STORE_FOLDER).getPath());
161       Messages.showErrorDialog(message, CommonBundle.getErrorTitle());
162       return null;
163     }
164
165     Project existing = findAndFocusExistingProjectForPath(path);
166     if (existing != null) return existing;
167
168     Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
169     if (!forceOpenInNewFrame && openProjects.length > 0) {
170       int exitCode = confirmOpenNewProject(false);
171       if (exitCode == GeneralSettings.OPEN_PROJECT_SAME_WINDOW) {
172         final Project toClose = projectToClose != null ? projectToClose : openProjects[openProjects.length - 1];
173         if (!closeAndDispose(toClose)) return null;
174       }
175       else if (exitCode != GeneralSettings.OPEN_PROJECT_NEW_WINDOW) {
176         return null;
177       }
178     }
179
180     if (isRemotePath(path) && !RecentProjectsManager.getInstance().hasPath(path)) {
181       String msg = IdeBundle.message("warning.load.project.from.share", path);
182       String title = IdeBundle.message("title.load.project.from.share");
183       if (!confirmLoadingFromRemotePath(msg, title)) {
184         return null;
185       }
186     }
187
188     ProjectManagerEx projectManager = ProjectManagerEx.getInstanceEx();
189     Project project = null;
190     try {
191       project = projectManager.loadAndOpenProject(path);
192     }
193     catch (IOException e) {
194       Messages.showMessageDialog(IdeBundle.message("error.cannot.load.project", e.getMessage()),
195                                  IdeBundle.message("title.cannot.load.project"), Messages.getErrorIcon());
196     }
197     catch (JDOMException e) {
198       LOG.info(e);
199       Messages.showMessageDialog(IdeBundle.message("error.project.file.is.corrupted"), IdeBundle.message("title.cannot.load.project"),
200                                  Messages.getErrorIcon());
201     }
202     catch (InvalidDataException e) {
203       LOG.info(e);
204       Messages.showMessageDialog(IdeBundle.message("error.project.file.is.corrupted"), IdeBundle.message("title.cannot.load.project"),
205                                  Messages.getErrorIcon());
206     }
207     return project;
208   }
209
210   public static boolean confirmLoadingFromRemotePath(@NotNull String path,
211                                                      @NotNull @PropertyKey(resourceBundle = IdeBundle.BUNDLE) String msgKey,
212                                                      @NotNull @PropertyKey(resourceBundle = IdeBundle.BUNDLE) String titleKey) {
213     return showYesNoDialog(IdeBundle.message(msgKey, path), titleKey);
214   }
215
216   public static boolean showYesNoDialog(@NotNull String message, @NotNull @PropertyKey(resourceBundle = IdeBundle.BUNDLE) String titleKey) {
217     final Window window = getActiveFrameOrWelcomeScreen();
218     final Icon icon = Messages.getWarningIcon();
219     String title = IdeBundle.message(titleKey);
220     final int answer = window == null ? Messages.showYesNoDialog(message, title, icon) : Messages.showYesNoDialog(window, message, title, icon);
221     return answer == Messages.YES;
222   }
223
224   public static Window getActiveFrameOrWelcomeScreen() {
225     Window window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
226     if (window != null)  return window;
227
228     for (Frame frame : Frame.getFrames()) {
229       if (frame instanceof IdeFrame && frame.isVisible()) {
230         return frame;
231       }
232     }
233
234     return null;
235   }
236
237   public static boolean isRemotePath(@NotNull String path) {
238     return path.contains("://") || path.contains("\\\\");
239   }
240
241   @Nullable
242   private static Project findAndFocusExistingProjectForPath(String path) {
243     Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
244     for (Project project : openProjects) {
245       if (!project.isDefault() && isSameProject(path, project)) {
246         focusProjectWindow(project, false);
247         return project;
248       }
249     }
250     return null;
251   }
252
253   /**
254    * @return {@link com.intellij.ide.GeneralSettings#OPEN_PROJECT_SAME_WINDOW}
255    *         {@link com.intellij.ide.GeneralSettings#OPEN_PROJECT_NEW_WINDOW}
256    *         {@link com.intellij.openapi.ui.Messages#CANCEL} - if user canceled the dialog
257    * @param isNewProject
258    */
259   public static int confirmOpenNewProject(boolean isNewProject) {
260     final GeneralSettings settings = GeneralSettings.getInstance();
261     int confirmOpenNewProject = ApplicationManager.getApplication().isUnitTestMode() ? GeneralSettings.OPEN_PROJECT_NEW_WINDOW : settings.getConfirmOpenNewProject();
262     if (confirmOpenNewProject == GeneralSettings.OPEN_PROJECT_ASK) {
263       if (isNewProject) {
264         int exitCode = Messages.showYesNoDialog(IdeBundle.message("prompt.open.project.in.new.frame"),
265                                                 IdeBundle.message("title.new.project"),
266                                                 IdeBundle.message("button.existingframe"),
267                                                 IdeBundle.message("button.newframe"),
268                                                 Messages.getQuestionIcon(),
269                                                 new ProjectNewWindowDoNotAskOption());
270         return exitCode == Messages.YES ? GeneralSettings.OPEN_PROJECT_SAME_WINDOW : GeneralSettings.OPEN_PROJECT_NEW_WINDOW;
271       }
272       else {
273         int exitCode = Messages.showYesNoCancelDialog(IdeBundle.message("prompt.open.project.in.new.frame"),
274                                                       IdeBundle.message("title.open.project"),
275                                                       IdeBundle.message("button.existingframe"),
276                                                       IdeBundle.message("button.newframe"),
277                                                       CommonBundle.getCancelButtonText(),
278                                                       Messages.getQuestionIcon(),
279                                                       new ProjectNewWindowDoNotAskOption());
280         return exitCode == Messages.YES ? GeneralSettings.OPEN_PROJECT_SAME_WINDOW :
281                exitCode == Messages.NO ? GeneralSettings.OPEN_PROJECT_NEW_WINDOW : Messages.CANCEL;
282       }
283     }
284     return confirmOpenNewProject;
285   }
286
287   public static boolean isSameProject(String path, @NotNull Project project) {
288     IProjectStore projectStore = (IProjectStore)ServiceKt.getStateStore(project);
289
290     String toOpen = FileUtil.toSystemIndependentName(path);
291     String existing = projectStore.getProjectFilePath();
292
293     String existingBaseDir = projectStore.getProjectBasePath();
294     if (existingBaseDir == null) {
295       // could be null if not yet initialized
296       return false;
297     }
298
299     final File openFile = new File(toOpen);
300     if (openFile.isDirectory()) {
301       return FileUtil.pathsEqual(toOpen, existingBaseDir);
302     }
303     if (StorageScheme.DIRECTORY_BASED == projectStore.getStorageScheme()) {
304       // todo: check if IPR is located not under the project base dir
305       return FileUtil.pathsEqual(FileUtil.toSystemIndependentName(openFile.getParentFile().getPath()), existingBaseDir);
306     }
307
308     return FileUtil.pathsEqual(toOpen, existing);
309   }
310
311   public static void focusProjectWindow(final Project p, boolean executeIfAppInactive) {
312     FocusCommand cmd = new FocusCommand() {
313       @NotNull
314       @Override
315       public ActionCallback run() {
316         JFrame f = WindowManager.getInstance().getFrame(p);
317         if (f != null) {
318           f.toFront();
319           //f.requestFocus();
320         }
321         return ActionCallback.DONE;
322       }
323     };
324
325     if (executeIfAppInactive) {
326       AppIcon.getInstance().requestFocus((IdeFrame)WindowManager.getInstance().getFrame(p));
327       cmd.run();
328     } else {
329       IdeFocusManager.getInstance(p).requestFocus(cmd, true);
330     }
331   }
332
333   public static String getBaseDir() {
334     final String lastProjectLocation = RecentProjectsManager.getInstance().getLastProjectCreationLocation();
335     if (lastProjectLocation != null) {
336       return lastProjectLocation.replace('/', File.separatorChar);
337     }
338     final String userHome = SystemProperties.getUserHome();
339     //noinspection HardCodedStringLiteral
340     return userHome.replace('/', File.separatorChar) + File.separator + ApplicationNamesInfo.getInstance().getLowercaseProductName() +
341            "Projects";
342   }
343
344   public static boolean isDirectoryBased(@NotNull Project project) {
345     IComponentStore store = ServiceKt.getStateStore(project);
346     return store instanceof IProjectStore && StorageScheme.DIRECTORY_BASED.equals(((IProjectStore)store).getStorageScheme());
347   }
348 }