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