2 * Copyright 2000-2012 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.ide.util.projectWizard;
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;
53 import java.io.IOException;
56 public abstract class ModuleBuilder extends AbstractModuleBuilder {
58 public static final ExtensionPointName<ModuleBuilderFactory> EP_NAME = ExtensionPointName.create("com.intellij.moduleBuilder");
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);
64 private String myName;
65 @NonNls private String myModuleFilePath;
66 private String myContentEntryPath;
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());
74 for (ModuleBuilderFactory factory : EP_NAME.getExtensions()) {
75 result.add(factory.createBuilder());
77 return ContainerUtil.filter(result, moduleBuilder -> moduleBuilder.isAvailable());
80 public static void deleteModuleFile(String moduleFilePath) {
81 final File moduleFile = new File(moduleFilePath);
82 if (moduleFile.exists()) {
83 FileUtil.delete(moduleFile);
85 final VirtualFile file = LocalFileSystem.getInstance().findFileByIoFile(moduleFile);
87 file.refresh(false, false);
91 protected boolean isAvailable() {
96 protected final String acceptParameter(String param) {
97 return param != null && param.length() > 0 ? param : null;
100 public String getName() {
105 public void setName(String name) {
106 myName = acceptParameter(name);
111 public String getBuilderId() {
112 ModuleType moduleType = getModuleType();
113 return moduleType == null ? null : moduleType.getId();
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);
123 * Typically delegates to ModuleType (e.g. JavaModuleType) that is more generic than ModuleBuilder
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()}
132 public ModuleWizardStep modifySettingsStep(@NotNull SettingsStep settingsStep) {
133 return modifyStep(settingsStep);
136 public ModuleWizardStep modifyStep(SettingsStep settingsStep) {
137 ModuleType type = getModuleType();
142 final ModuleWizardStep step = type.modifySettingsStep(settingsStep, this);
143 final List<WizardInputField> fields = getAdditionalFields();
144 for (WizardInputField field : fields) {
145 field.addToSettings(settingsStep);
147 return new ModuleWizardStep() {
149 public JComponent getComponent() {
154 public void updateDataModel() {
156 step.updateDataModel();
161 public boolean validate() throws ConfigurationException {
162 for (WizardInputField field : fields) {
163 if (!field.validate()) {
167 return step == null || step.validate();
173 public ModuleWizardStep modifyProjectTypeStep(@NotNull SettingsStep settingsStep) {
174 ModuleType type = getModuleType();
175 return type == null ? null : type.modifyProjectTypeStep(settingsStep, this);
178 protected List<WizardInputField> getAdditionalFields() {
179 return Collections.emptyList();
182 public String getModuleFilePath() {
183 return myModuleFilePath;
187 public void setModuleFilePath(@NonNls String path) {
188 myModuleFilePath = acceptParameter(path);
191 public void addModuleConfigurationUpdater(ModuleConfigurationUpdater updater) {
192 myUpdaters.add(updater);
196 public String getContentEntryPath() {
197 if (myContentEntryPath == null) {
198 final String directory = getModuleFileDirectory();
199 if (directory == null) {
202 new File(directory).mkdirs();
205 return myContentEntryPath;
209 public void setContentEntryPath(String moduleRootPath) {
210 final String path = acceptParameter(moduleRootPath);
213 myContentEntryPath = FileUtil.resolveShortWindowsName(path);
215 catch (IOException e) {
216 myContentEntryPath = path;
220 myContentEntryPath = null;
222 if (myContentEntryPath != null) {
223 myContentEntryPath = myContentEntryPath.replace(File.separatorChar, '/');
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);
237 public String getModuleFileDirectory() {
238 if (myModuleFilePath == null) {
241 final String parent = new File(myModuleFilePath).getParent();
242 if (parent == null) {
245 return parent.replace(File.separatorChar, '/');
249 public Module createModule(@NotNull ModifiableModuleModel moduleModel)
250 throws InvalidDataException, IOException, ModuleWithNameAlreadyExists, JDOMException, ConfigurationException {
251 LOG.assertTrue(myName != null);
252 LOG.assertTrue(myModuleFilePath != null);
254 deleteModuleFile(myModuleFilePath);
255 final ModuleType moduleType = getModuleType();
256 final Module module = moduleModel.newModule(myModuleFilePath, moduleType.getId());
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);
268 modifiableModel.commit();
269 setProjectType(module);
272 private void onModuleInitialized(final Module module) {
273 myDispatcher.getMulticaster().moduleCreated(module);
276 public abstract void setupRootModel(ModifiableRootModel modifiableRootModel) throws ConfigurationException;
278 public abstract ModuleType getModuleType();
280 protected ProjectType getProjectType() {
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);
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();
298 if (runFromProjectWizard) {
299 StartupManager.getInstance(module.getProject()).runWhenProjectIsInitialized(new DumbAwareRunnable() {
302 ApplicationManager.getApplication().runWriteAction(() -> onModuleInitialized(module));
307 onModuleInitialized(module);
312 public void addListener(ModuleBuilderListener listener) {
313 myDispatcher.addListener(listener);
316 public void removeListener(ModuleBuilderListener listener) {
317 myDispatcher.removeListener(listener);
320 public boolean canCreateModule() {
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;
332 public Module commitModule(@NotNull final Project project, @Nullable final ModifiableModuleModel model) {
333 if (canCreateModule()) {
334 if (myName == null) {
335 myName = project.getName();
337 if (myModuleFilePath == null) {
338 myModuleFilePath = project.getBaseDir().getPath() + File.separator + myName + ModuleFileType.DOT_DEFAULT_EXTENSION;
341 return ApplicationManager.getApplication().runWriteAction(new ThrowableComputable<Module, Exception>() {
343 public Module compute() throws Exception {
344 return createAndCommitIfNeeded(project, model, true);
348 catch (Exception ex) {
350 Messages.showErrorDialog(IdeBundle.message("error.adding.module.to.project", ex.getMessage()), IdeBundle.message("title.add.module"));
356 public Icon getBigIcon() {
357 return getModuleType().getBigIcon();
360 public Icon getNodeIcon() {
361 return getModuleType().getNodeIcon(false);
364 public String getDescription() {
365 return getModuleType().getDescription();
368 public String getPresentableName() {
369 return getModuleTypeName();
372 protected String getModuleTypeName() {
373 String name = getModuleType().getName();
374 return StringUtil.trimEnd(name, " Module");
377 public String getGroupName() {
378 return getPresentableName().split(" ")[0];
381 public String getParentGroup() {
385 public int getWeight() { return 0; }
387 public boolean isTemplate() {
391 public boolean isTemplateBased() {
395 public void updateFrom(ModuleBuilder from) {
396 myName = from.getName();
397 myContentEntryPath = from.getContentEntryPath();
398 myModuleFilePath = from.getModuleFilePath();
401 public Sdk getModuleJdk() {
405 public void setModuleJdk(Sdk jdk) {
410 public FrameworkRole getDefaultAcceptableRole() {
411 return getModuleType().getDefaultAcceptableRole();
414 public static abstract class ModuleConfigurationUpdater {
416 public abstract void update(@NotNull Module module, @NotNull ModifiableRootModel rootModel);