org.eclipse.che.plugin.java.testing.JavaTestFinder.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.che.plugin.java.testing.JavaTestFinder.java

Source

/*
 * Copyright (c) 2012-2017 Red Hat, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Red Hat, Inc. - initial API and implementation
 */
package org.eclipse.che.plugin.java.testing;

import static com.google.common.base.Strings.isNullOrEmpty;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;

import com.google.inject.Singleton;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IImportDeclaration;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IRegion;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** Class which finds test classes and test methods for java test frameworks. */
@Singleton
public class JavaTestFinder {
    private static final Logger LOG = LoggerFactory.getLogger(JavaTestFinder.class);

    /**
     * Finds test method related to the cursor position.
     *
     * @param compilationUnit compilation unit of class
     * @param cursorOffset cursor position
     * @return declaration of test method which should be ran. (Example:
     *     full.qualified.name.of.Class#methodName)
     */
    public List<String> findTestMethodDeclaration(ICompilationUnit compilationUnit, int cursorOffset) {
        IType primaryType = compilationUnit.findPrimaryType();
        String qualifiedName = primaryType.getFullyQualifiedName();
        try {
            IJavaElement element = compilationUnit.getElementAt(cursorOffset);
            if (element instanceof IMethod) {
                IMethod method = (IMethod) element;
                qualifiedName = qualifiedName + '#' + method.getElementName();
            }
        } catch (JavaModelException e) {
            LOG.debug("Can't read a method.", e);
        }
        return singletonList(qualifiedName);
    }

    /**
     * Finds test class declaration.
     *
     * @param compilationUnit compilation unit of class
     * @return declaration of test class which should be ran.
     */
    public List<String> findTestClassDeclaration(ICompilationUnit compilationUnit) {
        IType primaryType = compilationUnit.findPrimaryType();
        return singletonList(primaryType.getFullyQualifiedName());
    }

    /**
     * Finds test classes in package.
     *
     * @param javaProject java project
     * @param packagePath package path
     * @param testMethodAnnotation java annotation which describes test method in the test framework
     * @param testClassAnnotation java annotation which describes test class in the test framework
     * @return list of test classes which should be ran.
     */
    public List<String> findClassesInPackage(IJavaProject javaProject, String packagePath,
            String testMethodAnnotation, String testClassAnnotation) {
        IPackageFragment packageFragment = null;
        try {
            packageFragment = javaProject.findPackageFragment(new Path(packagePath));
        } catch (JavaModelException e) {
            LOG.info("Can't find package.", e);
        }
        return packageFragment == null ? emptyList()
                : findClassesInContainer(packageFragment, testMethodAnnotation, testClassAnnotation);
    }

    /**
     * Finds test classes in project.
     *
     * @param project java project
     * @param testMethodAnnotation java annotation which describes test method in the test framework
     * @param testClassAnnotation java annotation which describes test class in the test framework
     * @return list of test classes which should be ran.
     */
    public List<String> findClassesInProject(IJavaProject project, String testMethodAnnotation,
            String testClassAnnotation) {
        return findClassesInContainer(project, testMethodAnnotation, testClassAnnotation);
    }

    /**
     * Check if a method is test method.
     *
     * @param method method which should be checked
     * @param compilationUnit parent of the method
     * @param testAnnotation java annotation which describes test method in the test framework
     * @return {@code true} if the method is test method
     */
    public boolean isTest(IMethod method, ICompilationUnit compilationUnit, String testAnnotation) {
        try {
            IAnnotation[] annotations = method.getAnnotations();
            IAnnotation test = null;
            for (IAnnotation annotation : annotations) {
                String annotationElementName = annotation.getElementName();
                if ("Test".equals(annotationElementName)) {
                    test = annotation;
                    break;
                }
                if (testAnnotation.equals(annotationElementName)) {
                    return true;
                }
            }
            return test != null && isImportOfTestAnnotationExist(compilationUnit, testAnnotation);
        } catch (JavaModelException e) {
            LOG.info("Can't read method's annotations.", e);
            return false;
        }
    }

    private boolean isImportOfTestAnnotationExist(ICompilationUnit compilationUnit, String testAnnotation) {
        try {
            IImportDeclaration[] imports = compilationUnit.getImports();
            for (IImportDeclaration importDeclaration : imports) {
                String elementName = importDeclaration.getElementName();
                if (testAnnotation.equals(elementName)) {
                    return true;
                }
                if (importDeclaration.isOnDemand()
                        && testAnnotation.startsWith(elementName.substring(0, elementName.length() - 3))) { //remove .*
                    return true;
                }
            }
        } catch (JavaModelException e) {
            LOG.info("Can't read class imports.", e);
            return false;
        }
        return false;
    }

    private List<String> findClassesInContainer(IJavaElement container, String testMethodAnnotation,
            String testClassAnnotation) {
        List<String> result = new LinkedList<>();
        IRegion region = getRegion(container);
        try {
            ITypeHierarchy hierarchy = JavaCore.newTypeHierarchy(region, null, null);
            IType[] allClasses = hierarchy.getAllClasses();

            // search for all types with references to RunWith and Test and all subclasses
            HashSet<IType> candidates = new HashSet<>(allClasses.length);
            SearchRequestor requestor = new AnnotationSearchRequestor(hierarchy, candidates);

            IJavaSearchScope scope = SearchEngine.createJavaSearchScope(allClasses, IJavaSearchScope.SOURCES);
            int matchRule = SearchPattern.R_CASE_SENSITIVE;

            SearchPattern testPattern = SearchPattern.createPattern(testMethodAnnotation,
                    IJavaSearchConstants.ANNOTATION_TYPE, IJavaSearchConstants.ANNOTATION_TYPE_REFERENCE,
                    matchRule);

            SearchPattern runWithPattern = isNullOrEmpty(testClassAnnotation) ? testPattern
                    : SearchPattern.createPattern(testClassAnnotation, IJavaSearchConstants.ANNOTATION_TYPE,
                            IJavaSearchConstants.ANNOTATION_TYPE_REFERENCE, matchRule);

            SearchPattern annotationsPattern = SearchPattern.createOrPattern(runWithPattern, testPattern);
            SearchParticipant[] searchParticipants = new SearchParticipant[] {
                    SearchEngine.getDefaultSearchParticipant() };
            new SearchEngine().search(annotationsPattern, searchParticipants, scope, requestor, null);

            // find all classes in the region
            for (IType candidate : candidates) {
                if (isAccessibleClass(candidate) && !Flags.isAbstract(candidate.getFlags())
                        && region.contains(candidate)) {
                    result.add(candidate.getFullyQualifiedName());
                }
            }
        } catch (CoreException e) {

            LOG.info("Can't build project hierarchy.", e);
        }

        return result;
    }

    private IRegion getRegion(IJavaElement element) {
        IRegion result = JavaCore.newRegion();
        if (element.getElementType() == IJavaElement.JAVA_PROJECT) {
            //for projects only add the contained source folders
            try {
                IPackageFragmentRoot[] packageFragmentRoots = ((IJavaProject) element).getPackageFragmentRoots();
                for (IPackageFragmentRoot packageFragmentRoot : packageFragmentRoots) {
                    if (!packageFragmentRoot.isArchive()) {
                        result.add(packageFragmentRoot);
                    }
                }
            } catch (JavaModelException e) {
                LOG.info("Can't read source folders.", e);
            }
        } else {
            result.add(element);
        }
        return result;
    }

    private static boolean isAccessibleClass(IType type) throws JavaModelException {
        int flags = type.getFlags();
        if (Flags.isInterface(flags)) {
            return false;
        }
        IJavaElement parent = type.getParent();
        while (true) {
            if (parent instanceof ICompilationUnit || parent instanceof IClassFile) {
                return true;
            }
            if (!(parent instanceof IType) || !Flags.isStatic(flags) || !Flags.isPublic(flags)) {
                return false;
            }
            flags = ((IType) parent).getFlags();
            parent = parent.getParent();
        }
    }
}