/* * Copyright 2003-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jetbrains.mps.plugin; import com.intellij.CommonBundle; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DataKeys; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.Result; import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.ui.InputValidator; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.ui.DocumentAdapter; import com.intellij.ui.ScrollPaneFactory; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.event.DocumentEvent; import java.awt.*; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URI; import java.util.*; import java.util.List; import java.util.regex.Pattern; /** * fyodor, Aug 3, 2010 */ public class CollectJUnitTestsFromPatternsAction extends AnAction { @Override public void update(AnActionEvent e) { super.update(e); e.getPresentation().setVisible(false); Editor editor = e.getData(DataKeys.EDITOR); if (editor == null) return; Project project = e.getData(DataKeys.PROJECT); if (project == null) return; int offset = editor.getCaretModel().getOffset(); PsiFile file = PsiDocumentManager.getInstance(project).getCachedPsiFile(editor.getDocument()); if (file == null) return; PsiElement element = file.findElementAt(offset); PsiClass pc = getPsiClass(element); if (pc != null) e.getPresentation().setVisible(true); } @Override public void actionPerformed(AnActionEvent e) { Editor editor = e.getData(DataKeys.EDITOR); if (editor == null) return; final Project project = e.getData(DataKeys.PROJECT); if (project == null) return; int offset = editor.getCaretModel().getOffset(); PsiFile file = PsiDocumentManager.getInstance(project).getCachedPsiFile(editor.getDocument()); if (file == null) return; PsiElement element = file.findElementAt(offset); final PsiClass pc = getPsiClass(element); if (pc == null) return; File baseDir = getProjectBaseDir(project); String ptns = showInputDialog(project, "I want cookie! Give me the cookie!", "Cookie monster"); if (ptns == null) return; Map<File, Set<String>> includePathsMap = new HashMap<File, Set<String>>(); Map<File, Set<String>> excludePathsMap = new HashMap<File, Set<String>>(); final List<String> errors = new ArrayList<String>(); for (String ptn : ptns.split("\\n")) { FilePattern fp; try { fp = FilePattern.fromString(ptn); } catch (FilePatternParseException ex) { errors.add("Bad cookie: \"" + ptn + "\""); continue; } ModulesSourcePath msp = new ModulesSourcePath(fp.modulePtn, project); PatternFileSearcher pfs = new PatternFileSearcher(fp.filePtn, Collections.unmodifiableList(msp.getSourcePath())); for (Pair<File, String> pear : pfs.getRelativePaths()) { Map<File, Set<String>> pathsMap = fp.include ? includePathsMap : excludePathsMap; Set<String> paths = pathsMap.get(pear.getFirst()); if (paths == null) { paths = new HashSet<String>(); pathsMap.put(pear.getFirst(), paths); } paths.add(pear.getSecond()); } } final Set<String> suiteClasses = new TreeSet<String>(); for (Map.Entry<File, Set<String>> en : includePathsMap.entrySet()) { Set<String> include = en.getValue(); Set<String> exclude = excludePathsMap.get(en.getKey()); if (exclude != null) { include.removeAll(exclude); } for (String p : include) { suiteClasses.add(filePathToJavaClass(p)); } } final WriteAction<String> action = new WriteAction<String>() { @Override protected void run(Result<String> stringResult) throws Throwable { stringResult.setResult(null); StringBuilder sb = new StringBuilder("@SuiteClassSymbols({"); String sep = ""; for (String sc : suiteClasses) { sb.append(sep).append("\"").append(sc).append("\""); sep = ",\n"; } sb.append("})"); try { PsiJavaParserFacade javaParserFacade = JavaPsiFacade.getInstance(project).getParserFacade(); PsiAnnotation newann = javaParserFacade.createAnnotationFromText(sb.toString(), pc.getParent()); PsiAnnotation oldann = null; for (PsiAnnotation ann : pc.getModifierList().getAnnotations()) { if (String.valueOf(ann.getQualifiedName()).contains("SuiteClassSymbols")) { oldann = ann; break; } } if (oldann != null) { oldann.replace(newann); } else { pc.getParent().addBefore(newann, pc); } } catch (Exception ex) { stringResult.setResult(ex.toString()); } } }; if (!suiteClasses.isEmpty()) { CommandProcessor.getInstance().executeCommand(project, new Runnable() { public void run() { String error = action.execute().getResultObject(); if (error != null) { errors.add(error); } } }, e.getPresentation().getText(), null); } else { errors.add("No cookie, no work. :-|"); } if (!errors.isEmpty()) { StringBuilder sb = new StringBuilder(""); String sep = ""; for (String er : errors) { sb.append(sep).append(er); sep = "\n"; } Messages.showErrorDialog(project, sb.toString(), "I have a bad feeling about this"); } } private File getProjectBaseDir(Project prj) { if (prj != null) { VirtualFile bvf = prj.getBaseDir(); if (bvf != null && bvf.isInLocalFileSystem()) { return new File(URI.create(bvf.getUrl())); } } return null; } private PsiClass getPsiClass(PsiElement element) { PsiClass pc = null; while (element != null) { pc = element instanceof PsiClass ? (PsiClass) element : null; if (pc != null) break; element = element.getParent(); } return pc; } private String filePathToJavaClass(String filePath) { filePath = filePath.substring(0, filePath.lastIndexOf(".java")); return filePath.replaceAll(File.separator, "."); } public static String showInputDialog(Project project, @Nls String message, @Nls String title) { final Application application = ApplicationManager.getApplication(); if (application.isUnitTestMode() || application.isHeadlessEnvironment()) { return null; } else { MyMessages.TextAreaInputDialog dialog = new MyMessages.TextAreaInputDialog(project, message, title, null, "", null, new String[]{CommonBundle.getOkButtonText(), CommonBundle.getCancelButtonText()}, 0); dialog.show(); return dialog.getInputString(); } } private static class FilePatternParseException extends Exception { public FilePatternParseException(String msg) { super(msg); } } private static class FilePattern { public final boolean include; public final String modulePtn; public final String filePtn; private FilePattern(boolean include, String modulePtn, String filePtn) { this.include = include; this.modulePtn = modulePtn; this.filePtn = filePtn; } public static FilePattern fromString(String ptn) throws FilePatternParseException { if (ptn.length() == 0) throw new FilePatternParseException("Empty pattern"); boolean include = true; char sign = ptn.charAt(0); if (sign == '-' || sign == '+') { include = sign == '+'; ptn = ptn.substring(1); } String modulePtn = ""; int si = ptn.indexOf(":"); if (si >= 0) { modulePtn = ptn.substring(0, si); ptn = ptn.substring(si + 1); } if (ptn.length() == 0) throw new FilePatternParseException("Empty file pattern"); return new FilePattern(include, modulePtn, ptn); } public String toString() { return (include ? "+" : "-") + (modulePtn.length() == 0 ? "" : modulePtn + ":") + filePtn; } } private static class PatternFileSearcher { private String pattern; private Iterable<File> sourcePath; public PatternFileSearcher(String pattern, Iterable<File> sourcePath) { this.sourcePath = sourcePath; this.pattern = pattern; } public Iterable<Pair<File, String>> getRelativePaths() { Pattern ptn = Pattern.compile(FileUtil.convertAntToRegexp(pattern)); List<Pair<File, String>> res = new ArrayList<Pair<File, String>>(); for (File spd : sourcePath) { List<File> files = new ArrayList<File>(); FileUtil.collectMatchedFiles(spd, ptn, files); for (File match : files) { res.add(new Pair<File, String>(spd, FileUtil.getRelativePath(spd, match))); } } return Collections.unmodifiableList(res); } } private static class ModulesSourcePath { private Iterable<Module> modules; public ModulesSourcePath(String namePattern, Project prj) { this.modules = getModules(namePattern, prj); } public List<File> getSourcePath() { List<File> res = new ArrayList<File>(); for (Module module : modules) { for (VirtualFile svf : ModuleRootManager.getInstance(module).getSourceRoots()) { if (svf.isInLocalFileSystem()) { File fsf = new File(URI.create(svf.getUrl())); res.add(fsf); } } } return res; } private static Iterable<Module> getModules(String namePattern, Project prj) { List<Module> mdls = new ArrayList<Module>(); if (namePattern == null || namePattern.length() == 0) { namePattern = ".*"; } else { namePattern = namePattern.replaceAll("\\*", ".*"); } Pattern ptn = Pattern.compile(namePattern); for (Module mdl : ModuleManager.getInstance(prj).getModules()) { if (ptn.matcher(mdl.getName()).matches()) { mdls.add(mdl); } } return Collections.unmodifiableList(mdls); } } private static class MyMessages extends Messages { private static class TextAreaInputDialog extends InputDialog { private JTextArea textArea; public TextAreaInputDialog(Project project, String message, String title, Icon icon, String initialValue, InputValidator validator, String[] options, int defaultOption) { super(project, message, title, icon, initialValue, validator, options, defaultOption); } protected JComponent createCenterPanel() { return null; } protected JComponent createNorthPanel() { JComponent panel = super.createNorthPanel(); JComponent textField = null; JPanel messagePanel = null; try { textField = (JComponent) InputDialog.class.getMethod("getTextField").invoke(this); } catch (InvocationTargetException ignore) { } catch (NoSuchMethodException ignore) { } catch (IllegalAccessException ignore) { } if (textField != null) { messagePanel = (JPanel) textField.getParent(); messagePanel.remove(textField); // xe-xe textArea = new JTextArea(10, 50); textArea.setWrapStyleWord(true); textArea.setLineWrap(true); textArea.getDocument().addDocumentListener(new DocumentAdapter() { protected void textChanged(final DocumentEvent e) { } }); JComponent scrollPane = null; try { Method createScrollPane = ScrollPaneFactory.class.getMethod("createScrollPane", Component.class); scrollPane = (JScrollPane) createScrollPane.invoke(null, textArea); } catch (InvocationTargetException ignore) { } catch (NoSuchMethodException ignore) { } catch (IllegalAccessException ignore) { } if (scrollPane != null) { messagePanel.add(scrollPane, BorderLayout.SOUTH); } } return panel; } public JComponent getPreferredFocusedComponent() { return textArea; } @Nullable public String getInputString() { if (getExitCode() == 0) { return textArea.getText().trim(); } else { return null; } } } } }