org.eclipse.buildship.ui.view.execution.OpenTestSourceFileJob.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.buildship.ui.view.execution.OpenTestSourceFileJob.java

Source

/*
 * Copyright (c) 2015 the original author or authors.
 * 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:
 *     Simon Scholz (vogella GmbH) - initial API and implementation and initial documentation
 */

package org.eclipse.buildship.ui.view.execution;

import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;

import org.gradle.tooling.events.OperationDescriptor;
import org.gradle.tooling.events.test.JvmTestOperationDescriptor;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Files;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.jdt.internal.corext.util.JavaConventionsUtil;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.FindReplaceDocumentAdapter;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.editors.text.TextFileDocumentProvider;

import org.eclipse.buildship.ui.UiPlugin;
import org.eclipse.buildship.ui.util.editor.EditorUtils;

/**
 * Opens the test source files for the given
 * {@link org.eclipse.buildship.ui.view.execution.OperationItem} test nodes. Knows how to handle
 * both Java and Groovy test source files.
 */
public final class OpenTestSourceFileJob extends Job {

    private final ImmutableList<OperationItem> operationItems;

    public OpenTestSourceFileJob(List<OperationItem> operationItems) {
        super("Opening test source files");
        this.operationItems = ImmutableList.copyOf(operationItems);
    }

    @Override
    protected IStatus run(IProgressMonitor monitor) {
        SubMonitor subMonitor = SubMonitor.convert(monitor, this.operationItems.size());
        try {
            for (OperationItem operationItem : this.operationItems) {
                if (!monitor.isCanceled()) {
                    searchForTestSource(operationItem, subMonitor.newChild(1));
                } else {
                    Bundle bundle = FrameworkUtil.getBundle(getClass());
                    return new Status(IStatus.CANCEL, bundle.getSymbolicName(), IStatus.CANCEL,
                            "Opening test source files was canceled.", null);
                }
            }
        } finally {
            monitor.done();
        }
        return Status.OK_STATUS;
    }

    private void searchForTestSource(OperationItem operationItem, IProgressMonitor monitor) {
        OperationDescriptor operationDescriptor = (OperationDescriptor) operationItem
                .getAdapter(OperationDescriptor.class);
        if (operationDescriptor instanceof JvmTestOperationDescriptor) {
            JvmTestOperationDescriptor testOperationDescriptor = (JvmTestOperationDescriptor) operationDescriptor;
            String className = testOperationDescriptor.getClassName();
            if (className != null) {
                String methodName = testOperationDescriptor.getMethodName();
                searchForTestSource(className, methodName, monitor);
            }
        }
    }

    private void searchForTestSource(String className, String methodName, IProgressMonitor monitor) {
        monitor.setTaskName(String.format("Open test source file for class %s.", className));
        try {
            // search for Java file
            SearchEngine searchEngine = new SearchEngine();
            SearchPattern pattern = SearchPattern.createPattern(className, IJavaSearchConstants.TYPE,
                    IJavaSearchConstants.DECLARATIONS, SearchPattern.R_EXACT_MATCH);
            ShowTestSourceFileSearchRequester requester = new ShowTestSourceFileSearchRequester(methodName);
            searchEngine.search(pattern, new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() },
                    SearchEngine.createWorkspaceScope(), requester, monitor);

            // if no Java file has been found, search for Groovy file
            if (!requester.isFoundJavaTestSourceFile()) {
                IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
                workspaceRoot.accept(
                        new ShowTestSourceFileResourceVisitor(methodName, className, ImmutableList.of("groovy"))); //$NON-NLS-1$
            }
        } catch (CoreException e) {
            UiPlugin.logger().error(e.getMessage(), e);
        } finally {
            monitor.done();
        }
    }

    /**
     * Match the type and potentially also the method name.
     */
    private final class ShowTestSourceFileSearchRequester extends SearchRequestor {

        private final String methodName;
        private final AtomicBoolean foundJavaTestSourceFile;

        private ShowTestSourceFileSearchRequester(String methodName) {
            this.methodName = methodName;
            this.foundJavaTestSourceFile = new AtomicBoolean(false);
        }

        @Override
        public void acceptSearchMatch(SearchMatch match) throws CoreException {
            if (match.getElement() instanceof IType) {
                this.foundJavaTestSourceFile.set(true);

                IType classElement = (IType) match.getElement();
                IJavaElement methodElement = findMethod(this.methodName, classElement);
                openInEditor(methodElement != null ? methodElement : classElement);
            }
        }

        private IJavaElement findMethod(String methodName, IType type) {
            // abort search for invalid method names
            IStatus status = JavaConventionsUtil.validateMethodName(methodName, type);
            if (!status.isOK()) {
                return null;
            }

            // find parameter-less method by name
            IMethod method = type.getMethod(methodName, new String[0]);
            if (method != null && method.exists()) {
                return method;
            }

            // search textually by name (for custom runner with test methods having parameters)
            try {
                for (IMethod methodItem : type.getMethods()) {
                    if (methodItem.getElementName().equals(methodName)) {
                        return methodItem;
                    }
                }
                return null;
            } catch (JavaModelException e) {
                // ignore and treat as no method being found
                return null;
            }
        }

        private void openInEditor(final IJavaElement javaElement) {
            PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {

                @Override
                public void run() {
                    try {
                        JavaUI.openInEditor(javaElement);
                    } catch (Exception e) {
                        String message = String.format("Cannot open Java element %s in editor.", javaElement);
                        UiPlugin.logger().error(message, e);
                    }
                }
            });
        }

        private boolean isFoundJavaTestSourceFile() {
            return this.foundJavaTestSourceFile.get();
        }

    }

    /**
     * Find the file for the given class name as a resource in the workspace.
     */
    private static final class ShowTestSourceFileResourceVisitor implements IResourceVisitor {

        private static final String BIN_FOLDER_NAME = "bin"; //$NON-NLS-1$

        private final String methodName;
        private final String className;
        private final ImmutableList<String> fileExtensions;

        private ShowTestSourceFileResourceVisitor(String methodName, String className,
                List<String> fileExtensions) {
            this.methodName = methodName;
            this.className = Preconditions.checkNotNull(className);
            this.fileExtensions = ImmutableList.copyOf(fileExtensions);
        }

        @Override
        public boolean visit(final IResource resource) throws CoreException {
            // short-circuit if the resource is not a file with one of the requested extensions
            if (resource.getType() != IResource.FILE
                    || !this.fileExtensions.contains(resource.getFileExtension())) {
                return true;
            }

            // prepare to compare package path of the requested class name with the project path of
            // the given resource
            String classNameToPath = this.className.replaceAll(Pattern.quote("."), "/"); //$NON-NLS-1$ //$NON-NLS-2$
            String projectRelativePath = resource.getProjectRelativePath().toString();

            // short-circuit if the resource is in the bin folder or if the paths do not match
            if (projectRelativePath.startsWith(BIN_FOLDER_NAME) || !projectRelativePath.contains(classNameToPath)) {
                return true;
            }

            // short-circuit if the resource does not map to a file
            @SuppressWarnings({ "cast", "RedundantCast" })
            final IFile file = (IFile) resource.getAdapter(IFile.class);
            if (file == null) {
                return true;
            }

            // open the requested class and optionally mark the requested method
            Display display = PlatformUI.getWorkbench().getDisplay();
            display.asyncExec(new Runnable() {

                @Override
                public void run() {
                    IEditorPart editor = EditorUtils.openInInternalEditor(file, true);
                    IRegion region = getClassOrMethodRegion(file);
                    if (region != null) {
                        EditorUtils.selectAndReveal(region.getOffset(), region.getLength(), editor, file);
                    }
                }
            });
            return false;
        }

        private org.eclipse.jface.text.IRegion getClassOrMethodRegion(IFile file) {
            // if no method name is available find the class name
            if (this.methodName == null) {
                try {
                    FindReplaceDocumentAdapter documentAdapter = createFindReplaceDocumentAdapter(file);
                    return find(documentAdapter, Files.getNameWithoutExtension(file.getName()));
                } catch (Exception e) {
                    // ignore and treat as no method being found
                    return null;
                }
            }

            // try to find method name and fall back to class name if method name cannot be found
            try {
                FindReplaceDocumentAdapter documentAdapter = createFindReplaceDocumentAdapter(file);
                IRegion region = find(documentAdapter, this.methodName);
                if (region == null) {
                    documentAdapter = createFindReplaceDocumentAdapter(file);
                    return find(documentAdapter, Files.getNameWithoutExtension(file.getName()));
                }
                return region;
            } catch (Exception e) {
                // ignore and treat as no method being found
                return null;
            }
        }

        private FindReplaceDocumentAdapter createFindReplaceDocumentAdapter(IFile file) throws CoreException {
            TextFileDocumentProvider textFileDocumentProvider = new TextFileDocumentProvider();
            textFileDocumentProvider.connect(file);
            IDocument document = textFileDocumentProvider.getDocument(file);
            return new FindReplaceDocumentAdapter(document);
        }

        private IRegion find(FindReplaceDocumentAdapter findReplaceDocumentAdapter, String findString)
                throws BadLocationException {
            return findReplaceDocumentAdapter.find(0, findString, true, true, false, false);
        }

    }

}