6ee75b2b1a6fa428f5e08d506db16704ac2d73a3
[idea/community.git] / plugins / devkit / src / actions / GeneratePluginClassAction.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 org.jetbrains.idea.devkit.actions;
17
18 import com.intellij.ide.IdeView;
19 import com.intellij.ide.actions.CreateElementActionBase;
20 import com.intellij.openapi.actionSystem.*;
21 import com.intellij.openapi.module.Module;
22 import com.intellij.openapi.module.ModuleManager;
23 import com.intellij.openapi.module.ModuleType;
24 import com.intellij.openapi.project.Project;
25 import com.intellij.openapi.roots.OrderEntry;
26 import com.intellij.openapi.roots.ProjectFileIndex;
27 import com.intellij.openapi.roots.ProjectRootManager;
28 import com.intellij.openapi.vfs.VirtualFile;
29 import com.intellij.psi.JavaDirectoryService;
30 import com.intellij.psi.PsiClass;
31 import com.intellij.psi.PsiDirectory;
32 import com.intellij.psi.PsiElement;
33 import com.intellij.psi.xml.XmlFile;
34 import com.intellij.util.IncorrectOperationException;
35 import org.jetbrains.annotations.NonNls;
36 import org.jetbrains.annotations.NotNull;
37 import org.jetbrains.annotations.Nullable;
38 import org.jetbrains.idea.devkit.DevKitBundle;
39 import org.jetbrains.idea.devkit.module.PluginModuleType;
40 import org.jetbrains.idea.devkit.util.ChooseModulesDialog;
41 import org.jetbrains.idea.devkit.util.DescriptorUtil;
42
43 import javax.swing.*;
44 import java.util.*;
45
46 /**
47  * @author yole
48  */
49 public abstract class GeneratePluginClassAction extends CreateElementActionBase implements DescriptorUtil.Patcher {
50   protected final Set<XmlFile> myFilesToPatch = new HashSet<XmlFile>();
51
52   // length == 1 is important to make MyInputValidator close the dialog when
53   // module selection is canceled. That's some weird interface actually...
54   private static final PsiElement[] CANCELED = new PsiElement[1];
55
56   public GeneratePluginClassAction(String text, String description, @Nullable Icon icon) {
57     super(text, description, icon);
58   }
59
60   @NotNull protected final PsiElement[] invokeDialog(Project project, PsiDirectory directory) {
61     try {
62       final PsiElement[] psiElements = invokeDialogImpl(project, directory);
63       return psiElements == CANCELED ? PsiElement.EMPTY_ARRAY : psiElements;
64     } finally {
65       myFilesToPatch.clear();
66     }
67   }
68
69   protected abstract PsiElement[] invokeDialogImpl(Project project, PsiDirectory directory);
70
71   private void addPluginModule(Module module) {
72     final XmlFile pluginXml = PluginModuleType.getPluginXml(module);
73     if (pluginXml != null) myFilesToPatch.add(pluginXml);
74   }
75
76   public void update(final AnActionEvent e) {
77     super.update(e);
78     final Presentation presentation = e.getPresentation();
79     if (presentation.isEnabled()) {
80       final DataContext context = e.getDataContext();
81       Module module = LangDataKeys.MODULE.getData(context);
82       if (module == null || !PluginModuleType.isPluginModuleOrDependency(module)) {
83         presentation.setEnabled(false);
84         presentation.setVisible(false);
85       }
86       final IdeView view = LangDataKeys.IDE_VIEW.getData(e.getDataContext());
87       final Project project = PlatformDataKeys.PROJECT.getData(e.getDataContext());
88       if (view != null && project != null) {
89         // from com.intellij.ide.actions.CreateClassAction.update()
90         ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(project).getFileIndex();
91         PsiDirectory[] dirs = view.getDirectories();
92         for (PsiDirectory dir : dirs) {
93           if (projectFileIndex.isInSourceContent(dir.getVirtualFile()) && JavaDirectoryService.getInstance().getPackage(dir) != null) {
94             return;
95           }
96         }
97
98         presentation.setEnabled(false);
99         presentation.setVisible(false);
100       }
101     }
102   }
103
104   @Nullable
105   protected static Module getModule(PsiDirectory dir) {
106     Project project = dir.getProject();
107     final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
108
109     final VirtualFile vFile = dir.getVirtualFile();
110     if (fileIndex.isInLibrarySource(vFile) || fileIndex.isInLibraryClasses(vFile)) {
111       final List<OrderEntry> orderEntries = fileIndex.getOrderEntriesForFile(vFile);
112       if (orderEntries.isEmpty()) {
113         return null;
114       }
115       Set<Module> modules = new HashSet<Module>();
116       for (OrderEntry orderEntry : orderEntries) {
117         modules.add(orderEntry.getOwnerModule());
118       }
119       final Module[] candidates = modules.toArray(new Module[modules.size()]);
120       Arrays.sort(candidates, ModuleManager.getInstance(project).moduleDependencyComparator());
121       return candidates[0];
122     }
123     return fileIndex.getModuleForFile(vFile);
124   }
125
126   @NotNull
127   protected PsiElement[] create(String newName, PsiDirectory directory) throws Exception {
128     final Project project = directory.getProject();
129     final Module module = getModule(directory);
130
131     if (module != null) {
132       if (ModuleType.get(module) == PluginModuleType.getInstance()) {
133         addPluginModule(module);
134       }
135       else {
136         final List<Module> candidateModules = PluginModuleType.getCandidateModules(module);
137         final Iterator<Module> it = candidateModules.iterator();
138         while (it.hasNext()) {
139           Module m = it.next();
140           if (PluginModuleType.getPluginXml(m) == null) it.remove();
141         }
142
143         if (candidateModules.size() == 1) {
144           addPluginModule(candidateModules.get(0));
145         }
146         else {
147           final ChooseModulesDialog dialog = new ChooseModulesDialog(project, candidateModules, getTemplatePresentation().getDescription());
148           dialog.show();
149           if (!dialog.isOK()) {
150             // create() should return CANCELED now
151             return CANCELED;
152           }
153           else {
154             final List<Module> modules = dialog.getSelectedModules();
155             for (Module m : modules) {
156               addPluginModule(m);
157             }
158           }
159         }
160       }
161     }
162
163     if (myFilesToPatch.size() == 0) {
164       throw new IncorrectOperationException(DevKitBundle.message("error.no.plugin.xml"));
165     }
166     if (myFilesToPatch.size() == 0) {
167       // user canceled module selection
168       return CANCELED;
169     }
170
171     final PsiClass klass = JavaDirectoryService.getInstance().createClass(directory, newName, getClassTemplateName());
172
173     DescriptorUtil.patchPluginXml(this, klass, myFilesToPatch.toArray(new XmlFile[myFilesToPatch.size()]));
174
175     return new PsiElement[] {klass};
176   }
177
178   @NonNls protected abstract String getClassTemplateName();
179 }