2 * Copyright 2000-2016 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.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;
51 import java.io.IOException;
54 public abstract class ModuleBuilder extends AbstractModuleBuilder {
56 public static final ExtensionPointName<ModuleBuilderFactory> EP_NAME = ExtensionPointName.create("com.intellij.moduleBuilder");
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);
62 private String myName;
63 @NonNls private String myModuleFilePath;
64 private String myContentEntryPath;
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());
72 for (ModuleBuilderFactory factory : EP_NAME.getExtensions()) {
73 result.add(factory.createBuilder());
75 return ContainerUtil.filter(result, moduleBuilder -> moduleBuilder.isAvailable());
78 public static void deleteModuleFile(String moduleFilePath) {
79 final File moduleFile = new File(moduleFilePath);
80 if (moduleFile.exists()) {
81 FileUtil.delete(moduleFile);
83 final VirtualFile file = LocalFileSystem.getInstance().findFileByIoFile(moduleFile);
85 file.refresh(false, false);
89 protected boolean isAvailable() {
94 protected static String acceptParameter(String param) {
95 return param != null && param.length() > 0 ? param : null;
98 public String getName() {
103 public void setName(String name) {
104 myName = acceptParameter(name);
109 public String getBuilderId() {
110 ModuleType moduleType = getModuleType();
111 return moduleType == null ? null : moduleType.getId();
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);
121 * Typically delegates to ModuleType (e.g. JavaModuleType) that is more generic than ModuleBuilder
123 * @param settingsStep step to be modified
124 * @return callback ({@link ModuleWizardStep#validate()}
125 * and {@link ModuleWizardStep#updateDataModel()}
130 public ModuleWizardStep modifySettingsStep(@NotNull SettingsStep settingsStep) {
131 return modifyStep(settingsStep);
134 public ModuleWizardStep modifyStep(SettingsStep settingsStep) {
135 ModuleType type = getModuleType();
140 final ModuleWizardStep step = type.modifySettingsStep(settingsStep, this);
141 final List<WizardInputField> fields = getAdditionalFields();
142 for (WizardInputField field : fields) {
143 field.addToSettings(settingsStep);
145 return new ModuleWizardStep() {
147 public JComponent getComponent() {
152 public void updateDataModel() {
154 step.updateDataModel();
159 public boolean validate() throws ConfigurationException {
160 for (WizardInputField field : fields) {
161 if (!field.validate()) {
165 return step == null || step.validate();
171 public ModuleWizardStep modifyProjectTypeStep(@NotNull SettingsStep settingsStep) {
172 ModuleType type = getModuleType();
173 return type == null ? null : type.modifyProjectTypeStep(settingsStep, this);
176 protected List<WizardInputField> getAdditionalFields() {
177 return Collections.emptyList();
180 public String getModuleFilePath() {
181 return myModuleFilePath;
185 public void setModuleFilePath(@NonNls String path) {
186 myModuleFilePath = acceptParameter(path);
189 public void addModuleConfigurationUpdater(ModuleConfigurationUpdater updater) {
190 myUpdaters.add(updater);
194 public String getContentEntryPath() {
195 if (myContentEntryPath == null) {
196 final String directory = getModuleFileDirectory();
197 if (directory == null) {
200 new File(directory).mkdirs();
203 return myContentEntryPath;
207 public void setContentEntryPath(String moduleRootPath) {
208 final String path = acceptParameter(moduleRootPath);
211 myContentEntryPath = FileUtil.resolveShortWindowsName(path);
213 catch (IOException e) {
214 myContentEntryPath = path;
218 myContentEntryPath = null;
220 if (myContentEntryPath != null) {
221 myContentEntryPath = myContentEntryPath.replace(File.separatorChar, '/');
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);
235 public String getModuleFileDirectory() {
236 if (myModuleFilePath == null) {
239 final String parent = new File(myModuleFilePath).getParent();
240 if (parent == null) {
243 return parent.replace(File.separatorChar, '/');
247 public Module createModule(@NotNull ModifiableModuleModel moduleModel)
248 throws InvalidDataException, IOException, ModuleWithNameAlreadyExists, JDOMException, ConfigurationException {
249 LOG.assertTrue(myName != null);
250 LOG.assertTrue(myModuleFilePath != null);
252 deleteModuleFile(myModuleFilePath);
253 final ModuleType moduleType = getModuleType();
254 final Module module = moduleModel.newModule(myModuleFilePath, moduleType.getId());
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);
266 modifiableModel.commit();
267 setProjectType(module);
270 private void onModuleInitialized(final Module module) {
271 myDispatcher.getMulticaster().moduleCreated(module);
274 public abstract void setupRootModel(ModifiableRootModel modifiableRootModel) throws ConfigurationException;
276 public abstract ModuleType getModuleType();
278 protected ProjectType getProjectType() {
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);
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();
296 if (runFromProjectWizard) {
297 StartupManager.getInstance(module.getProject()).runWhenProjectIsInitialized(
298 () -> ApplicationManager.getApplication().runWriteAction(() -> onModuleInitialized(module)));
301 onModuleInitialized(module);
306 public void addListener(ModuleBuilderListener listener) {
307 myDispatcher.addListener(listener);
310 public void removeListener(ModuleBuilderListener listener) {
311 myDispatcher.removeListener(listener);
314 public boolean canCreateModule() {
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;
326 public Module commitModule(@NotNull final Project project, @Nullable final ModifiableModuleModel model) {
327 if (canCreateModule()) {
328 if (myName == null) {
329 myName = project.getName();
331 if (myModuleFilePath == null) {
332 myModuleFilePath = project.getBaseDir().getPath() + File.separator + myName + ModuleFileType.DOT_DEFAULT_EXTENSION;
335 return ApplicationManager.getApplication().runWriteAction(
336 (ThrowableComputable<Module, Exception>)() -> createAndCommitIfNeeded(project, model, true));
338 catch (Exception ex) {
340 Messages.showErrorDialog(IdeBundle.message("error.adding.module.to.project", ex.getMessage()), IdeBundle.message("title.add.module"));
346 public Icon getBigIcon() {
347 return getModuleType().getBigIcon();
350 public Icon getNodeIcon() {
351 return getModuleType().getNodeIcon(false);
354 public String getDescription() {
355 return getModuleType().getDescription();
358 public String getPresentableName() {
359 return getModuleTypeName();
362 protected String getModuleTypeName() {
363 String name = getModuleType().getName();
364 return StringUtil.trimEnd(name, " Module");
367 public String getGroupName() {
368 return getPresentableName().split(" ")[0];
371 public String getParentGroup() {
375 public int getWeight() { return 0; }
377 public boolean isTemplate() {
381 public boolean isTemplateBased() {
385 public void updateFrom(ModuleBuilder from) {
386 myName = from.getName();
387 myContentEntryPath = from.getContentEntryPath();
388 myModuleFilePath = from.getModuleFilePath();
391 public Sdk getModuleJdk() {
395 public void setModuleJdk(Sdk jdk) {
400 public FrameworkRole getDefaultAcceptableRole() {
401 return getModuleType().getDefaultAcceptableRole();
404 public static abstract class ModuleConfigurationUpdater {
406 public abstract void update(@NotNull Module module, @NotNull ModifiableRootModel rootModel);