generate visitor internal action
[fedora-idea.git] / source / com / intellij / internal / GenerateVisitorByHierarchyAction.java
blob54c2313aa80c35e5b153f6ec188656431bc0dfa3
1 /*
2 * User: anna
3 * Date: 28-Jun-2007
4 */
5 package com.intellij.internal;
7 import com.intellij.codeInsight.generation.GenerateMembersUtil;
8 import com.intellij.codeInsight.generation.GenerationInfo;
9 import com.intellij.codeInsight.generation.PsiGenerationInfo;
10 import com.intellij.ide.IdeView;
11 import com.intellij.ide.util.PackageChooserDialog;
12 import com.intellij.ide.util.PackageUtil;
13 import com.intellij.openapi.actionSystem.AnAction;
14 import com.intellij.openapi.actionSystem.AnActionEvent;
15 import com.intellij.openapi.actionSystem.DataKeys;
16 import com.intellij.openapi.actionSystem.PlatformDataKeys;
17 import com.intellij.openapi.application.Result;
18 import com.intellij.openapi.command.CommandProcessor;
19 import com.intellij.openapi.command.WriteCommandAction;
20 import com.intellij.openapi.editor.Document;
21 import com.intellij.openapi.fileTypes.StdFileTypes;
22 import com.intellij.openapi.project.Project;
23 import com.intellij.openapi.ui.DialogWrapper;
24 import com.intellij.openapi.util.Condition;
25 import com.intellij.openapi.util.Ref;
26 import com.intellij.psi.*;
27 import com.intellij.psi.search.GlobalSearchScope;
28 import com.intellij.psi.search.PackageScope;
29 import com.intellij.psi.search.searches.ClassInheritorsSearch;
30 import com.intellij.ui.DocumentAdapter;
31 import com.intellij.ui.EditorTextField;
32 import com.intellij.util.IncorrectOperationException;
33 import com.intellij.util.containers.ContainerUtil;
34 import gnu.trove.THashMap;
35 import gnu.trove.THashSet;
37 import javax.swing.*;
38 import javax.swing.event.DocumentEvent;
39 import java.awt.*;
40 import java.util.*;
41 import java.util.List;
43 public class GenerateVisitorByHierarchyAction extends AnAction {
44 public GenerateVisitorByHierarchyAction() {
45 super("Generate Hierarchy Visitor");
48 public void actionPerformed(AnActionEvent e) {
49 final Ref<String> visitorNameRef = Ref.create("MyVisitor");
50 final Ref<PsiClass> parentClassRef = Ref.create(null);
51 final Project project = e.getData(PlatformDataKeys.PROJECT);
52 assert project != null;
53 final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
54 final PsiNameHelper helper = psiFacade.getNameHelper();
55 final PackageChooserDialog dialog = new PackageChooserDialog("Choose Visitor Name, Package and Parent Class", project) {
57 updateOKAction();
59 protected JComponent createCenterPanel() {
60 final JPanel panel = new JPanel(new BorderLayout());
61 panel.add(super.createCenterPanel(), BorderLayout.CENTER);
62 panel.add(createNamePanel(), BorderLayout.NORTH);
63 panel.add(createBaseClassPanel(), BorderLayout.SOUTH);
64 return panel;
67 private JComponent createNamePanel() {
68 final JPanel panel = new JPanel(new BorderLayout());
69 panel.add(new JLabel("Visitor Name"), BorderLayout.WEST);
70 final JTextField nameField = new JTextField(visitorNameRef.get());
71 nameField.getDocument().addDocumentListener(new DocumentAdapter() {
72 protected void textChanged(final DocumentEvent e) {
73 visitorNameRef.set(nameField.getText());
74 updateOKAction();
76 });
77 panel.add(nameField, BorderLayout.CENTER);
78 return panel;
81 private JComponent createBaseClassPanel() {
82 final JPanel panel = new JPanel(new BorderLayout());
83 panel.add(new JLabel("Hierarchy Base Class"), BorderLayout.WEST);
84 final PsiElementFactory factory = psiFacade.getElementFactory();
85 final PsiTypeCodeFragment codeFragment = factory.createTypeCodeFragment("", null, true, true);
86 final Document document = PsiDocumentManager.getInstance(project).getDocument(codeFragment);
87 final EditorTextField editorTextField = new EditorTextField(document, project, StdFileTypes.JAVA);
88 editorTextField.addDocumentListener(new com.intellij.openapi.editor.event.DocumentAdapter() {
89 public void documentChanged(final com.intellij.openapi.editor.event.DocumentEvent e) {
90 parentClassRef.set(null);
91 try {
92 final PsiType psiType = codeFragment.getType();
93 final PsiClass psiClass = psiType instanceof PsiClassType ? ((PsiClassType)psiType).resolve() : null;
94 parentClassRef.set(psiClass);
96 catch (PsiTypeCodeFragment.IncorrectTypeException e1) {
97 // ok
99 updateOKAction();
102 panel.add(editorTextField.getComponent(), BorderLayout.CENTER);
103 return panel;
106 private void updateOKAction() {
107 PsiDocumentManager.getInstance(project).commitAllDocuments();
108 final String message;
109 if (!helper.isQualifiedName(visitorNameRef.get())) {
110 message = "Visitor name is not valid";
112 else if (parentClassRef.isNull()) {
113 message = "Hierarchy parent should be specified";
115 else if (parentClassRef.get().isAnnotationType() || parentClassRef.get().isEnum()) {
116 message = "Hierarchy parent should be interface or class";
118 else message = null;
119 setErrorText(message);
120 setOKActionEnabled(message == null);
124 final PsiElement element = DataKeys.PSI_ELEMENT.getData(e.getDataContext());
125 if (element instanceof PsiPackage) {
126 dialog.selectPackage(((PsiPackage)element).getQualifiedName());
128 else if (element instanceof PsiDirectory) {
129 final PsiPackage aPackage = JavaDirectoryService.getInstance().getPackage((PsiDirectory)element);
130 if (aPackage != null) {
131 dialog.selectPackage(aPackage.getQualifiedName());
134 dialog.show();
135 if (dialog.getExitCode() != DialogWrapper.OK_EXIT_CODE ||
136 dialog.getSelectedPackage() == null ||
137 dialog.getSelectedPackage().getQualifiedName().length() == 0 ||
138 parentClassRef.isNull()) return;
139 final PsiPackage aPackage = dialog.getSelectedPackage();
140 final PsiClass psiClass = parentClassRef.get();
141 final String visitorName = visitorNameRef.get();
142 final String visitorQName = PsiNameHelper.getShortClassName(visitorName).equals(visitorName)? aPackage.getQualifiedName()+"."+visitorName : visitorName;
143 generateVisitorClass(visitorQName, aPackage, psiClass);
144 final IdeView ideView = DataKeys.IDE_VIEW.getData(e.getDataContext());
145 final PsiClass visitorClass = JavaPsiFacade.getInstance(project).findClass(visitorQName, GlobalSearchScope.projectScope(project));
146 if (ideView != null && visitorClass != null) {
147 ideView.selectElement(visitorClass);
151 public void update(final AnActionEvent e) {
152 e.getPresentation().setEnabled(e.getData(PlatformDataKeys.PROJECT) != null);
155 private static void generateVisitorClass(final String visitorName, final PsiPackage aPackage, final PsiClass baseClass) {
156 final THashMap<PsiClass, Set<PsiClass>> classes = new THashMap<PsiClass, Set<PsiClass>>();
157 for (PsiClass aClass : ClassInheritorsSearch.search(baseClass, new PackageScope(aPackage, false, false), true).findAll()) {
158 if (aClass.hasModifierProperty(PsiModifier.ABSTRACT) == baseClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
159 final List<PsiClass> implementors =
160 ContainerUtil.findAll(ClassInheritorsSearch.search(aClass).findAll(), new Condition<PsiClass>() {
161 public boolean value(final PsiClass psiClass) {
162 return !psiClass.hasModifierProperty(PsiModifier.ABSTRACT);
165 classes.put(aClass, new THashSet<PsiClass>(implementors));
168 final THashMap<PsiClass, Set<PsiClass>> pathMap = new THashMap<PsiClass, Set<PsiClass>>();
169 for (PsiClass aClass : classes.keySet()) {
170 final Set<PsiClass> superClasses = new LinkedHashSet<PsiClass>();
171 for (PsiClass superClass : aClass.getSupers()) {
172 if (superClass.isInheritor(baseClass, true)) {
173 superClasses.add(superClass);
174 final Set<PsiClass> superImplementors = classes.get(superClass);
175 if (superImplementors != null) {
176 superImplementors.removeAll(classes.get(aClass));
180 if (superClasses.isEmpty()) {
181 superClasses.add(baseClass);
183 pathMap.put(aClass, superClasses);
185 pathMap.put(baseClass, Collections.<PsiClass>emptySet());
186 final ArrayList<PsiFile> psiFiles = new ArrayList<PsiFile>();
187 for (Set<PsiClass> implementors : classes.values()) {
188 for (PsiClass psiClass : implementors) {
189 psiFiles.add(psiClass.getContainingFile());
192 final Project project = baseClass.getProject();
193 final PsiClass visitorClass = JavaPsiFacade.getInstance(project).findClass(visitorName, GlobalSearchScope.projectScope(project));
194 if (visitorClass != null) {
195 psiFiles.add(visitorClass.getContainingFile());
197 new WriteCommandAction(project, psiFiles.toArray(new PsiFile[psiFiles.size()])) {
199 protected void run(final Result result) throws Throwable {
200 CommandProcessor.getInstance().markCurrentCommandAsComplex(project);
201 if (visitorClass == null) {
202 final String shortClassName = PsiNameHelper.getShortClassName(visitorName);
203 final String packageName = visitorName.substring(0, visitorName.length() - shortClassName.length() - 1);
204 final PsiDirectory directory = PackageUtil.findOrCreateDirectoryForPackage(project, packageName, null, false);
205 if (directory != null) {
206 final PsiClass visitorClass = JavaDirectoryService.getInstance().createClass(directory, shortClassName);
207 generateVisitorClass(visitorClass, classes, pathMap);
210 else {
211 generateVisitorClass(visitorClass, classes, pathMap);
214 }.execute();
217 private static void generateVisitorClass(final PsiClass visitorClass, final Map<PsiClass, Set<PsiClass>> classes,
218 final THashMap<PsiClass, Set<PsiClass>> pathMap) throws Throwable {
219 final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(visitorClass.getProject()).getElementFactory();
220 for (PsiClass psiClass : classes.keySet()) {
221 final PsiMethod method = elementFactory.createMethodFromText(
222 "public void accept(final " + visitorClass.getQualifiedName() + " visitor) { visitor.visit" + psiClass.getName() + "(this); }", psiClass);
223 for (PsiClass implementor : classes.get(psiClass)) {
224 addOrReplaceMethod(method, implementor);
228 final THashSet<PsiClass> visitedClasses = new THashSet<PsiClass>();
229 final LinkedList<PsiClass> toProcess = new LinkedList<PsiClass>(classes.keySet());
230 while (!toProcess.isEmpty()) {
231 final PsiClass psiClass = toProcess.removeFirst();
232 if (!visitedClasses.add(psiClass)) continue;
233 final Set<PsiClass> pathClasses = pathMap.get(psiClass);
234 toProcess.addAll(pathClasses);
235 final StringBuilder methodText = new StringBuilder();
237 methodText.append("public void visit").append(psiClass.getName()).append("(final ").append(psiClass.getQualifiedName()).append(" o) {");
238 boolean first = true;
239 for (PsiClass pathClass : pathClasses) {
240 if (first) first = false;
241 else methodText.append("// ");
242 methodText.append("visit").append(pathClass.getName()).append("(o);\n");
244 methodText.append("}");
245 final PsiMethod method = elementFactory.createMethodFromText(methodText.toString(), psiClass);
246 addOrReplaceMethod(method, visitorClass);
251 private static void addOrReplaceMethod(final PsiMethod method, final PsiClass implementor) throws IncorrectOperationException {
252 final PsiMethod accept = implementor.findMethodBySignature(method, false);
253 if (accept != null) {
254 accept.replace(method);
256 else {
257 GenerateMembersUtil.insertMembersAtOffset(implementor.getContainingFile(), implementor.getLastChild().getTextOffset(), Collections.<GenerationInfo>singletonList(new PsiGenerationInfo<PsiMethod>(method)));