61dedb00fca3e441eaed36379d98dd19fa9f50d7
[idea/community.git] / platform / lang-api / src / com / intellij / ide / util / projectWizard / ModuleBuilder.java
1 /*
2  * Copyright 2000-2012 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.util.projectWizard;
17
18 import com.intellij.ide.IdeBundle;
19 import com.intellij.ide.highlighter.ModuleFileType;
20 import com.intellij.ide.util.frameworkSupport.FrameworkRole;
21 import com.intellij.openapi.application.ApplicationManager;
22 import com.intellij.openapi.diagnostic.Logger;
23 import com.intellij.openapi.extensions.ExtensionPointName;
24 import com.intellij.openapi.module.*;
25 import com.intellij.openapi.options.ConfigurationException;
26 import com.intellij.openapi.project.DumbAwareRunnable;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.project.ProjectType;
29 import com.intellij.openapi.project.ProjectTypeService;
30 import com.intellij.openapi.projectRoots.Sdk;
31 import com.intellij.openapi.roots.ContentEntry;
32 import com.intellij.openapi.roots.ModifiableRootModel;
33 import com.intellij.openapi.roots.ModuleRootManager;
34 import com.intellij.openapi.roots.ui.configuration.ModulesProvider;
35 import com.intellij.openapi.startup.StartupManager;
36 import com.intellij.openapi.ui.Messages;
37 import com.intellij.openapi.util.Condition;
38 import com.intellij.openapi.util.InvalidDataException;
39 import com.intellij.openapi.util.ThrowableComputable;
40 import com.intellij.openapi.util.io.FileUtil;
41 import com.intellij.openapi.util.text.StringUtil;
42 import com.intellij.openapi.vfs.LocalFileSystem;
43 import com.intellij.openapi.vfs.VirtualFile;
44 import com.intellij.util.EventDispatcher;
45 import com.intellij.util.containers.ContainerUtil;
46 import org.jdom.JDOMException;
47 import org.jetbrains.annotations.NonNls;
48 import org.jetbrains.annotations.NotNull;
49 import org.jetbrains.annotations.Nullable;
50
51 import javax.swing.*;
52 import java.io.File;
53 import java.io.IOException;
54 import java.util.*;
55
56 public abstract class ModuleBuilder extends AbstractModuleBuilder {
57
58   public static final ExtensionPointName<ModuleBuilderFactory> EP_NAME = ExtensionPointName.create("com.intellij.moduleBuilder");
59
60   private static final Logger LOG = Logger.getInstance("#com.intellij.ide.util.projectWizard.ModuleBuilder");
61   private final Set<ModuleConfigurationUpdater> myUpdaters = new HashSet<>();
62   private final EventDispatcher<ModuleBuilderListener> myDispatcher = EventDispatcher.create(ModuleBuilderListener.class);
63   protected Sdk myJdk;
64   private String myName;
65   @NonNls private String myModuleFilePath;
66   private String myContentEntryPath;
67
68   @NotNull
69   public static List<ModuleBuilder> getAllBuilders() {
70     final ArrayList<ModuleBuilder> result = new ArrayList<>();
71     for (final ModuleType moduleType : ModuleTypeManager.getInstance().getRegisteredTypes()) {
72       result.add(moduleType.createModuleBuilder());
73     }
74     for (ModuleBuilderFactory factory : EP_NAME.getExtensions()) {
75       result.add(factory.createBuilder());
76     }
77     return ContainerUtil.filter(result, moduleBuilder -> moduleBuilder.isAvailable());
78   }
79
80   public static void deleteModuleFile(String moduleFilePath) {
81     final File moduleFile = new File(moduleFilePath);
82     if (moduleFile.exists()) {
83       FileUtil.delete(moduleFile);
84     }
85     final VirtualFile file = LocalFileSystem.getInstance().findFileByIoFile(moduleFile);
86     if (file != null) {
87       file.refresh(false, false);
88     }
89   }
90
91   protected boolean isAvailable() {
92     return true;
93   }
94
95   @Nullable
96   protected final String acceptParameter(String param) {
97     return param != null && param.length() > 0 ? param : null;
98   }
99
100   public String getName() {
101     return myName;
102   }
103
104   @Override
105   public void setName(String name) {
106     myName = acceptParameter(name);
107   }
108
109   @Override
110   @Nullable
111   public String getBuilderId() {
112     ModuleType moduleType = getModuleType();
113     return moduleType == null ? null : moduleType.getId();
114   }
115
116   @Override
117   public ModuleWizardStep[] createWizardSteps(@NotNull WizardContext wizardContext, @NotNull ModulesProvider modulesProvider) {
118     ModuleType moduleType = getModuleType();
119     return moduleType == null ? ModuleWizardStep.EMPTY_ARRAY : moduleType.createWizardSteps(wizardContext, this, modulesProvider);
120   }
121
122   /**
123    * Typically delegates to ModuleType (e.g. JavaModuleType) that is more generic than ModuleBuilder
124    *
125    * @param settingsStep step to be modified
126    * @return callback ({@link com.intellij.ide.util.projectWizard.ModuleWizardStep#validate()}
127    *         and {@link com.intellij.ide.util.projectWizard.ModuleWizardStep#updateDataModel()}
128    *         will be invoked)
129    */
130   @Override
131   @Nullable
132   public ModuleWizardStep modifySettingsStep(@NotNull SettingsStep settingsStep) {
133     return modifyStep(settingsStep);
134   }
135
136   public ModuleWizardStep modifyStep(SettingsStep settingsStep) {
137     ModuleType type = getModuleType();
138     if (type == null) {
139       return null;
140     }
141     else {
142       final ModuleWizardStep step = type.modifySettingsStep(settingsStep, this);
143       final List<WizardInputField> fields = getAdditionalFields();
144       for (WizardInputField field : fields) {
145         field.addToSettings(settingsStep);
146       }
147       return new ModuleWizardStep() {
148         @Override
149         public JComponent getComponent() {
150           return null;
151         }
152
153         @Override
154         public void updateDataModel() {
155           if (step != null) {
156             step.updateDataModel();
157           }
158         }
159
160         @Override
161         public boolean validate() throws ConfigurationException {
162           for (WizardInputField field : fields) {
163             if (!field.validate()) {
164               return false;
165             }
166           }
167           return step == null || step.validate();
168         }
169       };
170     }
171   }
172
173   public ModuleWizardStep modifyProjectTypeStep(@NotNull SettingsStep settingsStep) {
174     ModuleType type = getModuleType();
175     return type == null ? null : type.modifyProjectTypeStep(settingsStep, this);
176   }
177
178   protected List<WizardInputField> getAdditionalFields() {
179     return Collections.emptyList();
180   }
181
182   public String getModuleFilePath() {
183     return myModuleFilePath;
184   }
185
186   @Override
187   public void setModuleFilePath(@NonNls String path) {
188     myModuleFilePath = acceptParameter(path);
189   }
190
191   public void addModuleConfigurationUpdater(ModuleConfigurationUpdater updater) {
192     myUpdaters.add(updater);
193   }
194
195   @Nullable
196   public String getContentEntryPath() {
197     if (myContentEntryPath == null) {
198       final String directory = getModuleFileDirectory();
199       if (directory == null) {
200         return null;
201       }
202       new File(directory).mkdirs();
203       return directory;
204     }
205     return myContentEntryPath;
206   }
207
208   @Override
209   public void setContentEntryPath(String moduleRootPath) {
210     final String path = acceptParameter(moduleRootPath);
211     if (path != null) {
212       try {
213         myContentEntryPath = FileUtil.resolveShortWindowsName(path);
214       }
215       catch (IOException e) {
216         myContentEntryPath = path;
217       }
218     }
219     else {
220       myContentEntryPath = null;
221     }
222     if (myContentEntryPath != null) {
223       myContentEntryPath = myContentEntryPath.replace(File.separatorChar, '/');
224     }
225   }
226
227   protected @Nullable ContentEntry doAddContentEntry(ModifiableRootModel modifiableRootModel) {
228     final String contentEntryPath = getContentEntryPath();
229     if (contentEntryPath == null) return null;
230     new File(contentEntryPath).mkdirs();
231     final VirtualFile moduleContentRoot = LocalFileSystem.getInstance().refreshAndFindFileByPath(contentEntryPath.replace('\\', '/'));
232     if (moduleContentRoot == null) return null;
233     return modifiableRootModel.addContentEntry(moduleContentRoot);
234   }
235
236   @Nullable
237   public String getModuleFileDirectory() {
238     if (myModuleFilePath == null) {
239       return null;
240     }
241     final String parent = new File(myModuleFilePath).getParent();
242     if (parent == null) {
243       return null;
244     }
245     return parent.replace(File.separatorChar, '/');
246   }
247
248   @NotNull
249   public Module createModule(@NotNull ModifiableModuleModel moduleModel)
250     throws InvalidDataException, IOException, ModuleWithNameAlreadyExists, JDOMException, ConfigurationException {
251     LOG.assertTrue(myName != null);
252     LOG.assertTrue(myModuleFilePath != null);
253
254     deleteModuleFile(myModuleFilePath);
255     final ModuleType moduleType = getModuleType();
256     final Module module = moduleModel.newModule(myModuleFilePath, moduleType.getId());
257     setupModule(module);
258
259     return module;
260   }
261
262   protected void setupModule(Module module) throws ConfigurationException {
263     final ModifiableRootModel modifiableModel = ModuleRootManager.getInstance(module).getModifiableModel();
264     setupRootModel(modifiableModel);
265     for (ModuleConfigurationUpdater updater : myUpdaters) {
266       updater.update(module, modifiableModel);
267     }
268     modifiableModel.commit();
269     setProjectType(module);
270   }
271
272   private void onModuleInitialized(final Module module) {
273     myDispatcher.getMulticaster().moduleCreated(module);
274   }
275
276   public abstract void setupRootModel(ModifiableRootModel modifiableRootModel) throws ConfigurationException;
277
278   public abstract ModuleType getModuleType();
279
280   protected ProjectType getProjectType() {
281     return null;
282   }
283
284   protected void setProjectType(Module module) {
285     ProjectType projectType = getProjectType();
286     if (projectType != null && ProjectTypeService.getProjectType(module.getProject()) == null) {
287       ProjectTypeService.setProjectType(module.getProject(), projectType);
288     }
289   }
290
291   @NotNull
292   public Module createAndCommitIfNeeded(@NotNull Project project, @Nullable ModifiableModuleModel model, boolean runFromProjectWizard)
293     throws InvalidDataException, ConfigurationException, IOException, JDOMException, ModuleWithNameAlreadyExists {
294     final ModifiableModuleModel moduleModel = model != null ? model : ModuleManager.getInstance(project).getModifiableModel();
295     final Module module = createModule(moduleModel);
296     if (model == null) moduleModel.commit();
297
298     if (runFromProjectWizard) {
299       StartupManager.getInstance(module.getProject()).runWhenProjectIsInitialized(new DumbAwareRunnable() {
300         @Override
301         public void run() {
302           ApplicationManager.getApplication().runWriteAction(() -> onModuleInitialized(module));
303         }
304       });
305     }
306     else {
307       onModuleInitialized(module);
308     }
309     return module;
310   }
311
312   public void addListener(ModuleBuilderListener listener) {
313     myDispatcher.addListener(listener);
314   }
315
316   public void removeListener(ModuleBuilderListener listener) {
317     myDispatcher.removeListener(listener);
318   }
319
320   public boolean canCreateModule() {
321     return true;
322   }
323
324   @Override
325   @Nullable
326   public List<Module> commit(@NotNull final Project project, final ModifiableModuleModel model, final ModulesProvider modulesProvider) {
327     final Module module = commitModule(project, model);
328     return module != null ? Collections.singletonList(module) : null;
329   }
330
331   @Nullable
332   public Module commitModule(@NotNull final Project project, @Nullable final ModifiableModuleModel model) {
333     if (canCreateModule()) {
334       if (myName == null) {
335         myName = project.getName();
336       }
337       if (myModuleFilePath == null) {
338         myModuleFilePath = project.getBaseDir().getPath() + File.separator + myName + ModuleFileType.DOT_DEFAULT_EXTENSION;
339       }
340       try {
341         return ApplicationManager.getApplication().runWriteAction(new ThrowableComputable<Module, Exception>() {
342           @Override
343           public Module compute() throws Exception {
344             return createAndCommitIfNeeded(project, model, true);
345           }
346         });
347       }
348       catch (Exception ex) {
349         LOG.warn(ex);
350         Messages.showErrorDialog(IdeBundle.message("error.adding.module.to.project", ex.getMessage()), IdeBundle.message("title.add.module"));
351       }
352     }
353     return null;
354   }
355
356   public Icon getBigIcon() {
357     return getModuleType().getBigIcon();
358   }
359
360   public Icon getNodeIcon() {
361     return getModuleType().getNodeIcon(false);
362   }
363
364   public String getDescription() {
365     return getModuleType().getDescription();
366   }
367
368   public String getPresentableName() {
369     return getModuleTypeName();
370   }
371
372   protected String getModuleTypeName() {
373     String name = getModuleType().getName();
374     return StringUtil.trimEnd(name, " Module");
375   }
376
377   public String getGroupName() {
378     return getPresentableName().split(" ")[0];
379   }
380
381   public String getParentGroup() {
382     return null;
383   }
384
385   public int getWeight() { return 0; }
386
387   public boolean isTemplate() {
388     return false;
389   }
390
391   public boolean isTemplateBased() {
392     return false;
393   }
394
395   public void updateFrom(ModuleBuilder from) {
396     myName = from.getName();
397     myContentEntryPath = from.getContentEntryPath();
398     myModuleFilePath = from.getModuleFilePath();
399   }
400
401   public Sdk getModuleJdk() {
402     return myJdk;
403   }
404
405   public void setModuleJdk(Sdk jdk) {
406     myJdk = jdk;
407   }
408
409   @NotNull
410   public FrameworkRole getDefaultAcceptableRole() {
411     return getModuleType().getDefaultAcceptableRole();
412   }
413
414   public static abstract class ModuleConfigurationUpdater {
415
416     public abstract void update(@NotNull Module module, @NotNull ModifiableRootModel rootModel);
417
418   }
419 }