org.springframework.tooling.jdt.ls.commons.java.FluxJdtSearch.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.tooling.jdt.ls.commons.java.FluxJdtSearch.java

Source

/*******************************************************************************
 * Copyright (c) 2019 Pivotal, 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
 * https://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Pivotal, Inc. - initial API and implementation
 *******************************************************************************/
package org.springframework.tooling.jdt.ls.commons.java;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchScope;
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.springframework.tooling.jdt.ls.commons.Logger;

import reactor.core.publisher.Flux;
import reactor.core.publisher.ReplayProcessor;
import reactor.util.concurrent.Queues;

/**
 * Helper class to perform a search using Eclipse JDT search engine returning
 * the search results as a Flux.
 * <p>
 * The conversion from Eclipse callback style using {@link SearchRequestor} involves
 * a buffer that allows subscribers to attach to the Flux after the search has already
 * started without loosing results. However, if the buffer overflows then results will
 * be lost.
 * <p>
 * Clients should therfore start consuming the results as soon as possible and avoid
 * blocking the pipeline to avoid the loss of results they may care about.
 * <p>
 * Alternatively, client can specify a large enough buffer size so that the buffer can hold
 * at least as many results as the client may care to retrieve. This will allow the returned
 * Flux to be reused any number of times without timing constraints, provided that the
 * consumer never requests more than the number of buffered results.
 *
 * @author Kris De Volder
 */
public class FluxJdtSearch {

    private static final boolean DEBUG = ("" + Platform.getLocation()).contains("kdvolder");

    private static void debug(String string) {
        if (DEBUG) {
            System.out.println(string);
        }
    }

    private SearchEngine engine = new SearchEngine();
    private IJavaSearchScope scope = SearchEngine.createWorkspaceScope();
    private SearchPattern pattern = null;
    private SearchParticipant[] participants = new SearchParticipant[] {
            SearchEngine.getDefaultSearchParticipant() };
    private int bufferSize = Queues.SMALL_BUFFER_SIZE;
    private boolean useSystemJob = false;
    private int jobPriority = Job.INTERACTIVE;
    private Logger logger;

    public FluxJdtSearch(Logger logger) {
        this.logger = logger;
    }

    public FluxJdtSearch engine(SearchEngine engine) {
        this.engine = engine;
        return this;
    }

    public FluxJdtSearch scope(IJavaSearchScope scope) {
        this.scope = scope;
        return this;
    }

    public FluxJdtSearch bufferSize(int bufferSize) {
        this.bufferSize = bufferSize;
        return this;
    }

    public FluxJdtSearch pattern(SearchPattern pattern) {
        this.pattern = pattern;
        return this;
    }

    /**
     * Create a search scope that includes a given project and its dependencies.
     */
    public static IJavaSearchScope searchScope(IJavaProject javaProject, boolean includeBinaries,
            boolean includeSystemLibs) throws JavaModelException {
        int includeMask = IJavaSearchScope.REFERENCED_PROJECTS | IJavaSearchScope.SOURCES;
        if (includeBinaries) {
            includeMask = includeMask | IJavaSearchScope.APPLICATION_LIBRARIES;
        }
        if (includeSystemLibs) {
            includeMask = includeMask | IJavaSearchScope.SYSTEM_LIBRARIES;
        }
        return SearchEngine.createJavaSearchScope(new IJavaElement[] { javaProject }, includeMask);
    }

    /**
     * Implementation of {@link SearchRequestor} that emits search results to an {@link ReplayProcessor}
     * with replay capability.
     *
     * @author Kris De Volder
     */
    class FluxSearchRequestor extends SearchRequestor {

        private boolean isCanceled = false;
        private ReplayProcessor<SearchMatch> emitter = ReplayProcessor.<SearchMatch>create(bufferSize);
        private Flux<SearchMatch> flux = emitter.doOnCancel(() -> isCanceled = true);

        public Flux<SearchMatch> asFlux() {
            return flux;
        }

        @Override
        public void acceptSearchMatch(SearchMatch match) throws CoreException {
            if (isCanceled) {
                debug("!!!! canceling search !!!!");
                //Stop searching
                throw new OperationCanceledException();
            }
            emitter.onNext(match);
        }

        public void cancel() {
            isCanceled = true;
        }

        public void done() {
            emitter.onComplete();
        }
    }

    protected SearchEngine searchEngine() {
        return new SearchEngine();
    }

    protected SearchParticipant[] participants() {
        return new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() };
    }

    public Flux<SearchMatch> search() {
        validate();
        if (scope == null) {
            return Flux.empty();
        }
        final FluxSearchRequestor requestor = new FluxSearchRequestor();
        Job job = new Job("Search for " + pattern) {
            @Override
            protected IStatus run(IProgressMonitor monitor) {
                long start = System.currentTimeMillis();
                debug("Starting search for '" + pattern + "'");
                try {
                    searchEngine().search(pattern, participants, scope, requestor, monitor);
                    requestor.done();
                } catch (Exception e) {
                    debug("Canceled search for: " + pattern);
                    debug("          exception: " + e.getMessage());
                    long duration = System.currentTimeMillis() - start;
                    debug("          duration: " + duration + " ms");
                    requestor.cancel();
                }
                return Status.OK_STATUS;
            }
        };
        job.setSystem(useSystemJob);
        job.setPriority(jobPriority);
        job.schedule();
        return requestor.asFlux();
    }

    private void validate() {
        Assert.isNotNull(engine, "engine");
        //We allow scope to be set to null. This means there's no valid scope (or empty scope)
        // and the search should just return no results.
        //      Assert.isNotNull(scope, "scope");
        Assert.isNotNull(pattern, "pattern");
        Assert.isNotNull(participants, "participants");
        Assert.isLegal(bufferSize > 0);
    }

    public IJavaSearchScope workspaceScope(boolean includeBinaries) {
        if (includeBinaries) {
            return SearchEngine.createWorkspaceScope();
        } else {
            List<IJavaProject> projects = new ArrayList<>();
            for (IProject p : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
                try {
                    if (p.isAccessible() && p.hasNature(JavaCore.NATURE_ID)) {
                        IJavaProject jp = JavaCore.create(p);
                        projects.add(jp);
                    }
                } catch (Exception e) {
                    logger.log(e);
                }
            }
            int includeMask = IJavaSearchScope.SOURCES;
            return SearchEngine.createJavaSearchScope(projects.toArray(new IJavaElement[projects.size()]),
                    includeMask);
        }
    }

}