75d407f249255d8ffb04be035a153e7f4a5163f7
[idea/community.git] / java / idea-ui / src / com / intellij / openapi / roots / ui / configuration / artifacts / ArtifactsStructureConfigurable.java
1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.openapi.roots.ui.configuration.artifacts;
3
4 import com.intellij.CommonBundle;
5 import com.intellij.ide.JavaUiBundle;
6 import com.intellij.openapi.actionSystem.AnAction;
7 import com.intellij.openapi.actionSystem.AnActionEvent;
8 import com.intellij.openapi.actionSystem.DefaultActionGroup;
9 import com.intellij.openapi.application.WriteAction;
10 import com.intellij.openapi.extensions.ExtensionPointListener;
11 import com.intellij.openapi.extensions.PluginDescriptor;
12 import com.intellij.openapi.module.Module;
13 import com.intellij.openapi.options.ConfigurationException;
14 import com.intellij.openapi.project.DumbAwareAction;
15 import com.intellij.openapi.project.Project;
16 import com.intellij.openapi.roots.ModifiableRootModel;
17 import com.intellij.openapi.roots.impl.libraries.LibraryTableImplUtil;
18 import com.intellij.openapi.roots.libraries.Library;
19 import com.intellij.openapi.roots.libraries.LibraryTable;
20 import com.intellij.openapi.roots.ui.configuration.ModuleEditor;
21 import com.intellij.openapi.roots.ui.configuration.libraryEditor.LibraryEditorListener;
22 import com.intellij.openapi.roots.ui.configuration.projectRoot.*;
23 import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureElement;
24 import com.intellij.openapi.ui.MasterDetailsState;
25 import com.intellij.openapi.ui.Messages;
26 import com.intellij.openapi.ui.NamedConfigurable;
27 import com.intellij.openapi.ui.NonEmptyInputValidator;
28 import com.intellij.openapi.util.Comparing;
29 import com.intellij.packaging.artifacts.*;
30 import com.intellij.packaging.elements.ComplexPackagingElementType;
31 import com.intellij.packaging.elements.CompositePackagingElement;
32 import com.intellij.packaging.elements.PackagingElementType;
33 import com.intellij.packaging.impl.artifacts.ArtifactUtil;
34 import com.intellij.packaging.impl.artifacts.InvalidArtifact;
35 import com.intellij.packaging.impl.artifacts.PackagingElementPath;
36 import com.intellij.packaging.impl.artifacts.PackagingElementProcessor;
37 import com.intellij.packaging.impl.elements.LibraryElementType;
38 import com.intellij.packaging.impl.elements.LibraryPackagingElement;
39 import org.jetbrains.annotations.Nls;
40 import org.jetbrains.annotations.NotNull;
41 import org.jetbrains.annotations.Nullable;
42
43 import javax.swing.*;
44 import java.util.*;
45
46 public class ArtifactsStructureConfigurable extends BaseStructureConfigurable {
47   private ArtifactsStructureConfigurableContextImpl myPackagingEditorContext;
48   private final ArtifactEditorSettings myDefaultSettings = new ArtifactEditorSettings();
49
50   public ArtifactsStructureConfigurable(@NotNull Project project) {
51     super(project, new ArtifactStructureConfigurableState());
52     PackagingElementType.EP_NAME.getPoint().addExtensionPointListener(new ExtensionPointListener<PackagingElementType>() {
53       @Override
54       public void extensionRemoved(@NotNull PackagingElementType extension, @NotNull PluginDescriptor pluginDescriptor) {
55         if (extension instanceof ComplexPackagingElementType && myDefaultSettings.getTypesToShowContent().contains(extension)) {
56           List<ComplexPackagingElementType<?>> updated = new ArrayList<>(myDefaultSettings.getTypesToShowContent());
57           updated.remove(extension);
58           myDefaultSettings.setTypesToShowContent(updated);
59         }
60       }
61     }, false, this);
62   }
63
64   @Override
65   protected String getComponentStateKey() {
66     return "ArtifactsStructureConfigurable.UI";
67   }
68
69   public void init(StructureConfigurableContext context, ModuleStructureConfigurable moduleStructureConfigurable,
70                    ProjectLibrariesConfigurable projectLibrariesConfig, GlobalLibrariesConfigurable globalLibrariesConfig) {
71     super.init(context);
72     myPackagingEditorContext = new ArtifactsStructureConfigurableContextImpl(myContext, myProject, myDefaultSettings, new ArtifactAdapter() {
73       @Override
74       public void artifactAdded(@NotNull Artifact artifact) {
75         final MyNode node = addArtifactNode(artifact);
76         selectNodeInTree(node);
77         myContext.getDaemonAnalyzer().queueUpdate(myPackagingEditorContext.getOrCreateArtifactElement(artifact));
78       }
79     });
80
81     context.getModulesConfigurator().addAllModuleChangeListener(new ModuleEditor.ChangeListener() {
82       @Override
83       public void moduleStateChanged(ModifiableRootModel moduleRootModel) {
84         for (ProjectStructureElement element : getProjectStructureElements()) {
85           myContext.getDaemonAnalyzer().queueUpdate(element);
86         }
87       }
88     });
89
90     final ItemsChangeListener listener = new ItemsChangeListener() {
91       @Override
92       public void itemChanged(@Nullable Object deletedItem) {
93         if (deletedItem instanceof Library || deletedItem instanceof Module) {
94           onElementDeleted();
95         }
96       }
97     };
98     moduleStructureConfigurable.addItemsChangeListener(listener);
99     projectLibrariesConfig.addItemsChangeListener(listener);
100     globalLibrariesConfig.addItemsChangeListener(listener);
101
102     context.addLibraryEditorListener(new LibraryEditorListener() {
103       @Override
104       public void libraryRenamed(@NotNull Library library, String oldName, String newName) {
105         final Artifact[] artifacts = myPackagingEditorContext.getArtifactModel().getArtifacts();
106         for (Artifact artifact : artifacts) {
107           updateLibraryElements(artifact, library, oldName, newName);
108         }
109       }
110
111     });
112   }
113
114   private void updateLibraryElements(final Artifact artifact, final Library library, final String oldName, final String newName) {
115     if (ArtifactUtil.processPackagingElements(myPackagingEditorContext.getRootElement(artifact), LibraryElementType.LIBRARY_ELEMENT_TYPE,
116                                               new PackagingElementProcessor<LibraryPackagingElement>() {
117                                                 @Override
118                                                 public boolean process(@NotNull LibraryPackagingElement element,
119                                                                        @NotNull PackagingElementPath path) {
120                                                   return !isResolvedToLibrary(element, library, oldName);
121                                                 }
122                                               }, myPackagingEditorContext, false, artifact.getArtifactType())) {
123       return;
124     }
125     myPackagingEditorContext.editLayout(artifact, () -> {
126       final ModifiableArtifact modifiableArtifact = myPackagingEditorContext.getOrCreateModifiableArtifactModel().getOrCreateModifiableArtifact(artifact);
127       ArtifactUtil.processPackagingElements(modifiableArtifact, LibraryElementType.LIBRARY_ELEMENT_TYPE, new PackagingElementProcessor<LibraryPackagingElement>() {
128         @Override
129         public boolean process(@NotNull LibraryPackagingElement element, @NotNull PackagingElementPath path) {
130           if (isResolvedToLibrary(element, library, oldName)) {
131             element.setLibraryName(newName);
132           }
133           return true;
134         }
135       }, myPackagingEditorContext, false);
136     });
137     final ArtifactEditorImpl artifactEditor = myPackagingEditorContext.getArtifactEditor(artifact);
138     if (artifactEditor != null) {
139       artifactEditor.rebuildTries();
140     }
141   }
142
143   private static boolean isResolvedToLibrary(LibraryPackagingElement element, Library library, String name) {
144     if (!element.getLibraryName().equals(name)) {
145       return false;
146     }
147
148     final LibraryTable table = library.getTable();
149     if (table != null) {
150       return table.getTableLevel().equals(element.getLevel());
151     }
152     return element.getLevel().equals(LibraryTableImplUtil.MODULE_LEVEL);
153   }
154
155   private void onElementDeleted() {
156     for (ArtifactEditorImpl editor : myPackagingEditorContext.getArtifactEditors()) {
157       editor.getSourceItemsTree().rebuildTree();
158       editor.queueValidation();
159     }
160   }
161
162   @Override
163   protected MasterDetailsState getState() {
164     ((ArtifactStructureConfigurableState)myState).setDefaultArtifactSettings(myDefaultSettings.getState());
165     return super.getState();
166   }
167
168   @Override
169   public void loadState(MasterDetailsState object) {
170     super.loadState(object);
171     myDefaultSettings.loadState(((ArtifactStructureConfigurableState)myState).getDefaultArtifactSettings());
172   }
173
174   @Override
175   @Nls
176   public String getDisplayName() {
177     return JavaUiBundle.message("display.name.artifacts");
178   }
179
180   @Override
181   protected void loadTree() {
182     myTree.setRootVisible(false);
183     myTree.setShowsRootHandles(false);
184     for (Artifact artifact : myPackagingEditorContext.getArtifactModel().getAllArtifactsIncludingInvalid()) {
185       addArtifactNode(artifact);
186     }
187   }
188
189   @NotNull
190   @Override
191   protected Collection<? extends ProjectStructureElement> getProjectStructureElements() {
192     final List<ProjectStructureElement> elements = new ArrayList<>();
193     for (Artifact artifact : myPackagingEditorContext.getArtifactModel().getAllArtifactsIncludingInvalid()) {
194       elements.add(myPackagingEditorContext.getOrCreateArtifactElement(artifact));
195     }
196     return elements;
197   }
198
199   private MyNode addArtifactNode(final Artifact artifact) {
200     final NamedConfigurable<Artifact> configurable;
201     if (artifact instanceof InvalidArtifact) {
202       configurable = new InvalidArtifactConfigurable((InvalidArtifact)artifact, myPackagingEditorContext, TREE_UPDATER);
203     }
204     else {
205       configurable = new ArtifactConfigurable(artifact, myPackagingEditorContext, TREE_UPDATER);
206     }
207     final MyNode node = new MyNode(configurable);
208     addNode(node, myRoot);
209     return node;
210   }
211
212   @Override
213   public void reset() {
214     loadComponentState();
215     myPackagingEditorContext.resetModifiableModel();
216     super.reset();
217   }
218
219   @Override
220   public boolean isModified() {
221     final ModifiableArtifactModel modifiableModel = myPackagingEditorContext.getActualModifiableModel();
222     if (modifiableModel != null && modifiableModel.isModified()) {
223       return true;
224     }
225     return myPackagingEditorContext.getManifestFilesInfo().isManifestFilesModified() || super.isModified();
226   }
227
228   public ArtifactsStructureConfigurableContext getArtifactsStructureContext() {
229     return myPackagingEditorContext;
230   }
231
232   public ModifiableArtifactModel getModifiableArtifactModel() {
233     return myPackagingEditorContext.getOrCreateModifiableArtifactModel();
234   }
235
236   @Override
237   protected AbstractAddGroup createAddAction() {
238     return new AbstractAddGroup(JavaUiBundle.message("add.new.header.text")) {
239       @Override
240       public AnAction @NotNull [] getChildren(@Nullable AnActionEvent e) {
241         final ArtifactType[] types = ArtifactType.getAllTypes();
242         final AnAction[] actions = new AnAction[types.length];
243         for (int i = 0; i < types.length; i++) {
244           actions[i] = createAddArtifactAction(types[i]);
245         }
246         return actions;
247       }
248     };
249   }
250
251   private AnAction createAddArtifactAction(@NotNull final ArtifactType type) {
252     final List<? extends ArtifactTemplate> templates = type.getNewArtifactTemplates(myPackagingEditorContext);
253     final ArtifactTemplate emptyTemplate = new ArtifactTemplate() {
254       @Override
255       public String getPresentableName() {
256         return "Empty";
257       }
258
259       @Override
260       public NewArtifactConfiguration createArtifact() {
261         final String name = "unnamed";
262         return new NewArtifactConfiguration(type.createRootElement(name), name, type);
263       }
264     };
265
266     if (templates.isEmpty()) {
267       return new AddArtifactAction(type, emptyTemplate, type.getPresentableName(), type.getIcon());
268     }
269     final DefaultActionGroup group = DefaultActionGroup.createPopupGroup(() -> type.getPresentableName());
270     group.getTemplatePresentation().setIcon(type.getIcon());
271     group.add(new AddArtifactAction(type, emptyTemplate, emptyTemplate.getPresentableName(), null));
272     group.addSeparator();
273     for (ArtifactTemplate template : templates) {
274       group.add(new AddArtifactAction(type, template, template.getPresentableName(), null));
275     }
276     return group;
277   }
278
279   private void addArtifact(@NotNull ArtifactType type, @NotNull ArtifactTemplate artifactTemplate) {
280     Artifact artifact = ArtifactUtil.addArtifact(myPackagingEditorContext.getOrCreateModifiableArtifactModel(), type, artifactTemplate);
281     selectNodeInTree(findNodeByObject(myRoot, artifact));
282   }
283
284   @NotNull
285   @Override
286   protected List<? extends AnAction> createCopyActions(boolean fromPopup) {
287     final ArrayList<AnAction> actions = new ArrayList<>();
288     actions.add(new CopyArtifactAction());
289     return actions;
290   }
291
292   @Override
293   public void apply() throws ConfigurationException {
294     myPackagingEditorContext.saveEditorSettings();
295     checkForEmptyAndDuplicatedNames(JavaUiBundle.message("configurable.artifact.prefix"), CommonBundle.getErrorTitle(), ArtifactConfigurableBase.class);
296     super.apply();
297
298     myPackagingEditorContext.getManifestFilesInfo().saveManifestFiles();
299     final ModifiableArtifactModel modifiableModel = myPackagingEditorContext.getActualModifiableModel();
300     if (modifiableModel != null) {
301       WriteAction.run(() -> modifiableModel.commit());
302     }
303     myPackagingEditorContext.resetModifiableModel();
304     reloadTreeNodes();
305     restoreLastSelection();
306   }
307
308   @Override
309   public void disposeUIResources() {
310     myPackagingEditorContext.saveEditorSettings();
311     super.disposeUIResources();
312     myPackagingEditorContext.disposeUIResources();
313   }
314
315   @Override
316   protected void updateSelection(@Nullable NamedConfigurable configurable) {
317     boolean selectionChanged = !Comparing.equal(myCurrentConfigurable, configurable);
318     if (selectionChanged && myCurrentConfigurable instanceof ArtifactConfigurable) {
319       ArtifactEditorImpl editor = myPackagingEditorContext.getArtifactEditor(((ArtifactConfigurable)myCurrentConfigurable).getArtifact());
320       if (editor != null) {
321         editor.getLayoutTreeComponent().saveElementProperties();
322       }
323     }
324     super.updateSelection(configurable);
325     if (selectionChanged && configurable instanceof ArtifactConfigurable) {
326       ArtifactEditorImpl editor = myPackagingEditorContext.getArtifactEditor(((ArtifactConfigurable)configurable).getArtifact());
327       if (editor != null) {
328         editor.getLayoutTreeComponent().resetElementProperties();
329       }
330     }
331   }
332
333   @Override
334   public String getHelpTopic() {
335     final String topic = super.getHelpTopic();
336     return topic != null ? topic : "reference.settingsdialog.project.structure.artifacts";
337   }
338
339   @Override
340   protected List<? extends RemoveConfigurableHandler<?>> getRemoveHandlers() {
341     return Collections.singletonList(new ArtifactRemoveHandler());
342   }
343
344   @Override
345   @NotNull
346   public String getId() {
347     return "project.artifacts";
348   }
349
350   @Override
351   public void dispose() {
352   }
353
354   private class ArtifactRemoveHandler extends RemoveConfigurableHandler<Artifact> {
355     ArtifactRemoveHandler() {
356       super(ArtifactConfigurableBase.class);
357     }
358
359     @Override
360     public boolean remove(@NotNull Collection<? extends Artifact> artifacts) {
361       for (Artifact artifact : artifacts) {
362         myPackagingEditorContext.getOrCreateModifiableArtifactModel().removeArtifact(artifact);
363         myContext.getDaemonAnalyzer().removeElement(myPackagingEditorContext.getOrCreateArtifactElement(artifact));
364       }
365       return true;
366     }
367   }
368
369   private class AddArtifactAction extends DumbAwareAction {
370     private final ArtifactType myType;
371     private final ArtifactTemplate myArtifactTemplate;
372
373     AddArtifactAction(@NotNull ArtifactType type, @NotNull ArtifactTemplate artifactTemplate, final @NotNull String actionText,
374                              final Icon icon) {
375       super(actionText, null, icon);
376       myType = type;
377       myArtifactTemplate = artifactTemplate;
378     }
379
380     @Override
381     public void actionPerformed(@NotNull AnActionEvent e) {
382       addArtifact(myType, myArtifactTemplate);
383     }
384   }
385
386   private final class CopyArtifactAction extends AnAction {
387    private CopyArtifactAction() {
388       super(CommonBundle.messagePointer("button.copy"), CommonBundle.messagePointer("button.copy"), COPY_ICON);
389     }
390
391     @Override
392     public void actionPerformed(@NotNull final AnActionEvent e) {
393       final Object o = getSelectedObject();
394       if (o instanceof Artifact) {
395         final Artifact selected = (Artifact)o;
396         ModifiableArtifactModel artifactModel = myPackagingEditorContext.getOrCreateModifiableArtifactModel();
397         String suggestedName = ArtifactUtil.generateUniqueArtifactName(selected.getName(), artifactModel);
398         final String newName = Messages.showInputDialog(JavaUiBundle.message("label.enter.artifact.name"),
399                                                         JavaUiBundle.message("dialog.title.copy.artifact"),
400                                                         COPY_ICON,
401                                                         suggestedName,
402                                                         new NonEmptyInputValidator());
403         if (newName == null) return;
404
405         CompositePackagingElement<?> rootCopy = ArtifactUtil.copyFromRoot(selected.getRootElement(), myProject);
406         artifactModel.addArtifact(newName, selected.getArtifactType(), rootCopy);
407       }
408     }
409
410     @Override
411     public void update(@NotNull final AnActionEvent e) {
412       if (myTree.getSelectionPaths() == null || myTree.getSelectionPaths().length != 1) {
413         e.getPresentation().setEnabled(false);
414       }
415       else {
416         e.getPresentation().setEnabled(getSelectedObject() instanceof Artifact);
417       }
418     }
419   }
420 }