package bndtools.launch; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.debug.ui.DebugUITools; import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.IAnnotation; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IParent; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeHierarchy; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor; import org.eclipse.jdt.ui.JavaUI; import org.eclipse.jface.text.ITextSelection; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import aQute.lib.strings.Strings; import bndtools.launch.api.AbstractLaunchShortcut; public class JUnitShortcut extends AbstractLaunchShortcut { public JUnitShortcut() { super(LaunchConstants.LAUNCH_ID_OSGI_JUNIT); } /* * This is called when the launch starts in the editor. If a method or type is selected * in a Java Editor, we will test that type/method. */ @Override public void launch(IEditorPart editor, String mode) { try { // // Check if a method is selected. It is more precise then the // older next method that adapts the editor to a JavaElement // this in general returns a Compilation Unit // if (editor instanceof JavaEditor) { IJavaElement element = getSelectedJavaElement((JavaEditor) editor); if (element == null) { IEditorInput input = editor.getEditorInput(); element = (IJavaElement) input.getAdapter(IJavaElement.class); } if (element != null) { launchJavaElements(Collections.singletonList(element), mode); return; } } } catch (CoreException e) { e.printStackTrace(); } // // Did not succeed to get a JavaElement, we try the original way // super.launch(editor, mode); } @Override protected void launchJavaElements(List<IJavaElement> elements, String mode) throws CoreException { assert elements != null && elements.size() > 0; IProject targetProject = elements.get(0).getJavaProject().getProject(); IPath projectPath = targetProject.getFullPath().makeRelative(); ILaunchConfiguration config = findLaunchConfig(projectPath); ILaunchConfigurationWorkingCopy wc = null; if (config == null) { wc = createConfiguration(projectPath, targetProject); } else { wc = config.getWorkingCopy(); } if (wc != null) { customise(elements, wc); config = wc.doSave(); DebugUITools.launch(config, mode); } } /* * From the Java element, we traverse over all the children of the given element * until we hit a Type. Types are then added if they are either a JUnit 3 * (implements junit.framework.Test) or JUnit 4 (one of the methods has an org.junit * annotation) * */ private static void customise(List<IJavaElement> elements, ILaunchConfigurationWorkingCopy config) throws JavaModelException { assert elements != null; Set<String> testNames = new HashSet<String>(); for (IJavaElement element : elements) { gatherTests(testNames, element, config); } if (!testNames.isEmpty()) { config.setAttribute(OSGiJUnitLaunchDelegate.ORG_BNDTOOLS_TESTNAMES, Strings.join(" ", testNames)); } } /* * Recursive method that traverses the children of a Java Element and gathers test case types. */ private static void gatherTests(Set<String> testNames, IJavaElement element, ILaunchConfigurationWorkingCopy config) throws JavaModelException { assert element != null; if (element.isReadOnly()) // not interested in in non-source return; switch (element.getElementType()) { /* * For a type, we only have to check if it is testable. */ case IJavaElement.TYPE : { IType type = (IType) element; if (isTestable(type)) { testNames.add((type).getFullyQualifiedName()); } } break; /* * Normally we do not traverse to methods but we can call the customise with a method */ case IJavaElement.METHOD : { IMethod method = (IMethod) element; String typeName = ((IType) method.getParent()).getFullyQualifiedName(); String methodName = method.getElementName(); testNames.add(typeName + ":" + methodName); } break; /* * Default we go deeper if the element is a parent */ default : { if (element instanceof IParent) for (IJavaElement type : ((IParent) element).getChildren()) { gatherTests(testNames, type, config); } } break; } } /* * Check if this element can be tested */ private static boolean isTestable(IType type) throws JavaModelException { assert type != null; int flags = type.getFlags(); // // Must be a class, not abstract, and public // if (!type.isClass() || Flags.isAbstract(flags) || !Flags.isPublic(flags)) return false; // // One of the super classes/interfaces must be a Junit class/interface // ITypeHierarchy hierarchy = type.newSupertypeHierarchy(null); for (IType z : hierarchy.getAllSupertypes(type)) { if (z.getFullyQualifiedName().startsWith("junit.")) return true; } if (isJUnit4(type)) return true; for (IType z : hierarchy.getAllSuperclasses(type)) { if (isJUnit4(z)) return true; } return false; } /* * * Check if any of the methods has an annotation * from the org.junit package * */ private static boolean isJUnit4(IType z) throws JavaModelException { for (IMethod m : z.getMethods()) { if (Flags.isPublic(m.getFlags())) { for (IAnnotation annotation : m.getAnnotations()) { String[][] names = m.getDeclaringType().resolveType(annotation.getElementName()); for (String[] pair : names) { if (pair[0].contains("org.junit")) return true; } } } } return false; } /* * Helper method to find out the selected Java Element from a Java Editor */ private IJavaElement getSelectedJavaElement(JavaEditor editor) throws JavaModelException { IJavaElement elem = JavaUI.getEditorInputJavaElement(editor.getEditorInput()); if (elem instanceof ICompilationUnit) { ITextSelection selection = (ITextSelection) editor.getSelectionProvider().getSelection(); if (selection != null) return ((ICompilationUnit) elem).getElementAt(selection.getOffset()); } return null; } }