org.eclipse.recommenders.completion.rcp.processable.IntelligentCompletionProposalComputer.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.recommenders.completion.rcp.processable.IntelligentCompletionProposalComputer.java

Source

/**
 * Copyright (c) 2010, 2012 Darmstadt University of Technology.
 * 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:
 *    Marcel Bruch - initial API and implementation.
 */
package org.eclipse.recommenders.completion.rcp.processable;

import static org.eclipse.recommenders.completion.rcp.CompletionContextKey.ACTIVE_PROCESSORS;
import static org.eclipse.recommenders.completion.rcp.processable.ProcessableProposalFactory.create;
import static org.eclipse.recommenders.completion.rcp.processable.ProposalTag.*;
import static org.eclipse.recommenders.internal.completion.rcp.Constants.*;
import static org.eclipse.recommenders.internal.completion.rcp.l10n.LogMessages.ERROR_SESSION_PROCESSOR_FAILED;
import static org.eclipse.recommenders.utils.Checks.cast;
import static org.eclipse.recommenders.utils.Logs.log;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.inject.Inject;
import javax.inject.Provider;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.CompletionProposal;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.internal.ui.javaeditor.CompilationUnitEditor;
import org.eclipse.jdt.internal.ui.text.java.CompletionProposalCategory;
import org.eclipse.jdt.internal.ui.text.java.CompletionProposalComputerRegistry;
import org.eclipse.jdt.internal.ui.text.java.JavaAllCompletionProposalComputer;
import org.eclipse.jdt.internal.ui.text.java.JavaMethodCompletionProposal;
import org.eclipse.jdt.ui.PreferenceConstants;
import org.eclipse.jdt.ui.text.java.ContentAssistInvocationContext;
import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.ContentAssistEvent;
import org.eclipse.jface.text.contentassist.ICompletionListener;
import org.eclipse.jface.text.contentassist.ICompletionListenerExtension2;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.source.ContentAssistantFacade;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.recommenders.completion.rcp.CompletionContextKey;
import org.eclipse.recommenders.completion.rcp.DisableContentAssistCategoryJob;
import org.eclipse.recommenders.completion.rcp.ICompletionContextFunction;
import org.eclipse.recommenders.completion.rcp.IRecommendersCompletionContext;
import org.eclipse.recommenders.completion.rcp.RecommendersCompletionContext;
import org.eclipse.recommenders.internal.completion.rcp.CompletionRcpPreferences;
import org.eclipse.recommenders.internal.completion.rcp.EmptyCompletionProposal;
import org.eclipse.recommenders.internal.completion.rcp.EnabledCompletionProposal;
import org.eclipse.recommenders.rcp.IAstProvider;
import org.eclipse.recommenders.rcp.SharedImages;
import org.eclipse.recommenders.utils.Logs;
import org.eclipse.ui.IEditorPart;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

@SuppressWarnings({ "restriction", "rawtypes" })
public class IntelligentCompletionProposalComputer extends JavaAllCompletionProposalComputer
        implements ICompletionListener, ICompletionListenerExtension2 {

    /**
     * A whitelist ensuring that the CompilationUnitEditor we are dealing with actually contains Java code. This is
     * necessary as certain plugins (Groovy Eclipse, Scala IDE) extend the JDT's CompilationUnitEditor.
     *
     * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=470372">Bug 470372</a>
     * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=470406">Bug 470406</a>
     * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=474318">Bug 474318</a>
     */
    private static final List<String> JAVA_EDITOR_WHITELIST = ImmutableList
            .of(CompilationUnitEditor.class.getName(), "org.eclipse.wb.internal.core.editor.multi.DesignerEditor");

    private final CompletionRcpPreferences preferences;
    private final IAstProvider astProvider;
    private final SharedImages images;
    private final Map<CompletionContextKey, ICompletionContextFunction> functions;
    private final Provider<IEditorPart> editorProvider;
    private final IProcessableProposalFactory proposalFactory = new ProcessableProposalFactory();

    private final Set<SessionProcessor> processors = Sets.newLinkedHashSet();
    private final Set<SessionProcessor> activeProcessors = Sets.newLinkedHashSet();

    // Set in storeContext
    public JavaContentAssistInvocationContext jdtContext;
    public IRecommendersCompletionContext crContext;

    public ContentAssistantFacade contentAssist;

    @Inject
    public IntelligentCompletionProposalComputer(CompletionRcpPreferences preferences, IAstProvider astProvider,
            SharedImages images, Map<CompletionContextKey, ICompletionContextFunction> functions,
            Provider<IEditorPart> editorRetriever) {
        this.preferences = preferences;
        this.astProvider = astProvider;
        this.images = images;
        this.functions = functions;
        this.editorProvider = editorRetriever;
    }

    @Override
    public void sessionStarted() {
        processors.clear();
        for (SessionProcessorDescriptor d : preferences.getEnabledSessionProcessors()) {
            try {
                processors.add(d.getProcessor());
            } catch (Throwable e) {
                log(ERROR_SESSION_PROCESSOR_FAILED, e, d.getId());
            }
        }
        activeProcessors.clear();
        activeProcessors.addAll(processors);
        // code looks odd? This method unregisters this instance from the last(!) source viewer see
        // unregisterCompletionListener for details
        unregisterCompletionListener();
    }

    @Override
    public List<ICompletionProposal> computeCompletionProposals(ContentAssistInvocationContext context,
            IProgressMonitor monitor) {

        if (!(context instanceof JavaContentAssistInvocationContext)) {
            return Collections.emptyList();
        }

        storeContext(context);

        if (!isTriggeredInJavaProject()) {
            // We can't make recommendations. Fall back to JDT.
            if (!isContentAssistConfigurationOkay()) {
                // JDT is still active, so don't add any proposals.
                return Collections.emptyList();
            } else {
                // JDT is inactive. Return all proposals JDT would have made.
                List<ICompletionProposal> res = Lists.newLinkedList();
                for (Entry<IJavaCompletionProposal, CompletionProposal> pair : crContext.getProposals()
                        .entrySet()) {
                    IJavaCompletionProposal jdtProposal = create(pair.getValue(), pair.getKey(), jdtContext,
                            proposalFactory);
                    res.add(jdtProposal);
                }
                return res;
            }
        }

        if (!isContentAssistConfigurationOkay()) {
            enableRecommenders();
            int offset = context.getInvocationOffset();
            EnabledCompletionProposal info = new EnabledCompletionProposal(images, offset);
            boolean hasOtherProposals = !crContext.getProposals().isEmpty();
            if (hasOtherProposals) {
                // Return the configure proposal
                return ImmutableList.<ICompletionProposal>of(info);
            } else {
                return ImmutableList.<ICompletionProposal>of(info, new EmptyCompletionProposal(offset));
            }
        } else {
            List<ICompletionProposal> res = Lists.newLinkedList();
            registerCompletionListener();
            crContext.set(ACTIVE_PROCESSORS, ImmutableSet.copyOf(activeProcessors));
            fireInitializeContext(crContext);
            fireStartSession(crContext);
            for (Entry<IJavaCompletionProposal, CompletionProposal> pair : crContext.getProposals().entrySet()) {
                IJavaCompletionProposal jdtProposal = create(pair.getValue(), pair.getKey(), jdtContext,
                        proposalFactory);
                res.add(jdtProposal);
                if (jdtProposal instanceof JavaMethodCompletionProposal) {
                    int position = guessContextInformationPosition(jdtContext);
                    JavaMethodCompletionProposal jmcp = (JavaMethodCompletionProposal) jdtProposal;
                    jmcp.setContextInformationPosition(position);
                }
                if (jdtProposal instanceof IProcessableProposal) {
                    IProcessableProposal crProposal = (IProcessableProposal) jdtProposal;
                    crProposal.setTag(CONTEXT, crContext);
                    crProposal.setTag(IS_VISIBLE, true);
                    crProposal.setTag(JDT_UI_PROPOSAL, pair.getKey());
                    crProposal.setTag(JDT_CORE_PROPOSAL, pair.getValue());
                    crProposal.setTag(JDT_SCORE, jdtProposal.getRelevance());
                    fireProcessProposal(crProposal);
                }
            }
            fireEndComputation(res);
            fireAboutToShow(res);

            return res;
        }
    }

    private boolean isTriggeredInJavaProject() {
        if (jdtContext == null) {
            return false;
        }

        IEditorPart editor = editorProvider.get();
        if (editor == null) {
            return false;
        }

        if (!JAVA_EDITOR_WHITELIST.contains(editor.getClass().getName())) {
            return false;
        }

        IJavaProject project = jdtContext.getProject();
        if (project == null) {
            return false;
        }
        return project.exists();
    }

    private void enableRecommenders() {
        new DisableContentAssistCategoryJob(MYLYN_ALL_CATEGORY).schedule();
        new DisableContentAssistCategoryJob(JDT_ALL_CATEGORY).schedule();
        new DisableContentAssistCategoryJob(JDT_TYPE_CATEGORY).schedule();
        new DisableContentAssistCategoryJob(JDT_NON_TYPE_CATEGORY).schedule();
    }

    @Override
    public void sessionEnded() {
        fireAboutToClose();
    }

    private void storeContext(ContentAssistInvocationContext context) {
        jdtContext = cast(context);
        crContext = new RecommendersCompletionContext(jdtContext, astProvider, functions);
    }

    protected boolean isContentAssistConfigurationOkay() {
        Set<String> excludedCategories = Sets
                .newHashSet(PreferenceConstants.getExcludedCompletionProposalCategories());
        if (excludedCategories.contains(RECOMMENDERS_ALL_CATEGORY_ID)) {
            // If we are excluded on the default tab, then we cannot be on the default tab now, as we are executing.
            // Hence, we must be on a subsequent tab.
            return true;
        }
        if (isJdtJavaProposalsEnabled(excludedCategories) || isMylynJavaProposalsEnabled(excludedCategories)) {
            return false;
        }
        return true;
    }

    private boolean isMylynJavaProposalsEnabled(Set<String> excludedCategories) {
        return isMylynInstalled() && !excludedCategories.contains(MYLYN_ALL_CATEGORY);
    }

    private boolean isJdtJavaProposalsEnabled(Set<String> excludedCategories) {
        return !excludedCategories.contains(JDT_ALL_CATEGORY) || !excludedCategories.contains(JDT_TYPE_CATEGORY)
                || !excludedCategories.contains(JDT_NON_TYPE_CATEGORY);
    }

    private boolean isMylynInstalled() {
        CompletionProposalComputerRegistry reg = CompletionProposalComputerRegistry.getDefault();
        for (CompletionProposalCategory cat : reg.getProposalCategories()) {
            if (cat.getId().equals(MYLYN_ALL_CATEGORY)) {
                return true;
            }
        }
        return false;
    }

    private void registerCompletionListener() {
        ITextViewer v = jdtContext.getViewer();
        if (!(v instanceof SourceViewer)) {
            return;
        }
        SourceViewer sv = (SourceViewer) v;
        contentAssist = sv.getContentAssistantFacade();
        contentAssist.addCompletionListener(this);
    }

    /*
     * Unregisters this computer from the last known content assist facade. This method is called in some unexpected
     * places (i.e., not in sessionEnded and similar methods) because unregistering in these methods would be too early
     * to get notified about apply events.
     */
    private void unregisterCompletionListener() {
        if (contentAssist != null) {
            contentAssist.removeCompletionListener(this);
        }
    }

    protected void fireInitializeContext(IRecommendersCompletionContext crContext) {
        for (Iterator<SessionProcessor> it = activeProcessors.iterator(); it.hasNext();) {
            SessionProcessor p = it.next();
            try {
                p.initializeContext(crContext);
            } catch (Throwable e) {
                it.remove();
                Logs.log(ERROR_SESSION_PROCESSOR_FAILED, e, p.getClass());
            }
        }
    }

    protected void fireStartSession(IRecommendersCompletionContext crContext) {
        for (Iterator<SessionProcessor> it = activeProcessors.iterator(); it.hasNext();) {
            SessionProcessor p = it.next();
            try {
                boolean interested = p.startSession(crContext);
                if (!interested) {
                    it.remove();
                }
            } catch (Throwable e) {
                it.remove();
                Logs.log(ERROR_SESSION_PROCESSOR_FAILED, e, p.getClass());
            }
        }
    }

    protected void fireProcessProposal(IProcessableProposal proposal) {
        for (SessionProcessor p : activeProcessors) {
            try {
                proposal.getRelevance();
                p.process(proposal);
            } catch (Throwable e) {
                log(ERROR_SESSION_PROCESSOR_FAILED, e, p.getClass());
            }
        }
        proposal.getProposalProcessorManager().prefixChanged(crContext.getPrefix());
    }

    protected void fireEndComputation(List<ICompletionProposal> proposals) {
        for (SessionProcessor p : activeProcessors) {
            try {
                p.endSession(proposals);
            } catch (Throwable e) {
                log(ERROR_SESSION_PROCESSOR_FAILED, e, p.getClass());
            }
        }
    }

    protected void fireAboutToShow(List<ICompletionProposal> proposals) {
        for (SessionProcessor p : activeProcessors) {
            try {
                p.aboutToShow(proposals);
            } catch (Throwable e) {
                log(ERROR_SESSION_PROCESSOR_FAILED, e, p.getClass());
            }
        }
    }

    protected void fireAboutToClose() {
        for (SessionProcessor p : activeProcessors) {
            try {
                p.aboutToClose();
            } catch (Throwable e) {
                log(ERROR_SESSION_PROCESSOR_FAILED, e, p.getClass());
            }
        }
    }

    @Override
    public void assistSessionStarted(ContentAssistEvent event) {
        // ignore
    }

    @Override
    public void assistSessionEnded(ContentAssistEvent event) {
        // ignore

        // Calling unregister here seems like a good choice here but unfortunately isn't. "proposal applied" events are
        // fired after the sessionEnded event, and thus, we cannot use this method to unsubscribe from the current
        // editor. See unregisterCompletionListern for details.
    }

    @Override
    public void selectionChanged(ICompletionProposal proposal, boolean smartToggle) {
        for (SessionProcessor p : activeProcessors) {
            try {
                p.selected(proposal);
            } catch (Throwable e) {
                log(ERROR_SESSION_PROCESSOR_FAILED, e, p.getClass());
            }
        }
    }

    @Override
    public void applied(ICompletionProposal proposal) {
        for (SessionProcessor p : activeProcessors) {
            try {
                p.applied(proposal);
            } catch (Throwable e) {
                log(ERROR_SESSION_PROCESSOR_FAILED, e, p.getClass());
            }
        }
        unregisterCompletionListener();
    }
}