All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.microsoft.java.debug.plugin.internal.CompletionProposalRequestor Maven / Gradle / Ivy

There is a newer version: 0.53.1
Show newest version
/*******************************************************************************
 * Copyright (c) 2016-2018 Red Hat Inc. and others.
 * 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
 *
 * Copied form org.eclipse.jdt.ls.core.internal.contentassist.CompletionsProvider and modified it for class file completion.
 *
 * Contributors:
 *     Red Hat Inc. - initial API and implementation
 *******************************************************************************/

package com.microsoft.java.debug.plugin.internal;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.CompletionContext;
import org.eclipse.jdt.core.CompletionProposal;
import org.eclipse.jdt.core.CompletionRequestor;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
import org.eclipse.jdt.ls.core.internal.contentassist.CompletionProposalDescriptionProvider;
import org.eclipse.jdt.ls.core.internal.contentassist.GetterSetterCompletionProposal;
import org.eclipse.jdt.ls.core.internal.contentassist.SortTextHelper;
import org.eclipse.jdt.ls.core.internal.handlers.CompletionResolveHandler;
import org.eclipse.jdt.ls.core.internal.handlers.CompletionResponse;
import org.eclipse.jdt.ls.core.internal.handlers.CompletionResponses;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4j.CompletionItemLabelDetails;

import com.google.common.collect.ImmutableSet;
import com.microsoft.java.debug.core.Configuration;


@SuppressWarnings("restriction")
public final class CompletionProposalRequestor extends CompletionRequestor {

    private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);

    private List proposals = new ArrayList<>();
    private final ITypeRoot typeRoot;
    private CompletionProposalDescriptionProvider descriptionProvider;
    private CompletionResponse response;
    private boolean isTestCodeExcluded;
    private CompletionContext context;

    // Update SUPPORTED_KINDS when mapKind changes
    // @formatter:off
    public static final Set SUPPORTED_KINDS = ImmutableSet.of(CompletionItemKind.Constructor,
            CompletionItemKind.Class, CompletionItemKind.Module, CompletionItemKind.Field, CompletionItemKind.Keyword,
            CompletionItemKind.Reference, CompletionItemKind.Variable, CompletionItemKind.Function,
            CompletionItemKind.Text);
    // @formatter:on

    private static boolean isFilterFailed = false;

    /**
     * Constructor.
     * @param typeRoot ITypeRoot
     * @param offset within the source file
     */
    public CompletionProposalRequestor(ITypeRoot typeRoot, int offset) {
        this.typeRoot = typeRoot;
        response = new CompletionResponse();
        response.setOffset(offset);
        if (typeRoot instanceof IClassFile)  {
            isTestCodeExcluded = true;
        } else {
            isTestCodeExcluded = !isTestSource(typeRoot.getJavaProject(), typeRoot);
        }
        setRequireExtendedContext(true);
    }

    private boolean isTestSource(IJavaProject project, ITypeRoot cu) {
        if (project == null) {
            return true;
        }
        try {
            IClasspathEntry[] resolvedClasspath = project.getResolvedClasspath(true);
            final IPath resourcePath = cu.getResource().getFullPath();
            for (IClasspathEntry e : resolvedClasspath) {
                if (e.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
                    if (e.isTest()) {
                        if (e.getPath().isPrefixOf(resourcePath)) {
                            return true;
                        }
                    }
                }
            }
        } catch (JavaModelException e) {
            logger.log(Level.WARNING, String.format("Failed to judge the cu scope: %s", e.toString()), e);
        }
        return false;
    }

    @Override
    public void accept(CompletionProposal proposal) {
        if (isFiltered(proposal)) {
            return;
        }

        if (!isIgnored(proposal.getKind())) {
            if (proposal.getKind() == CompletionProposal.POTENTIAL_METHOD_DECLARATION) {
                acceptPotentialMethodDeclaration(proposal);
            } else {
                if (proposal.getKind() == CompletionProposal.PACKAGE_REF && typeRoot.getParent() != null
                        && String.valueOf(proposal.getCompletion()).equals(typeRoot.getParent().getElementName())) {
                    // Hacky way to boost relevance of current package, for package completions,
                    // until
                    // https://bugs.eclipse.org/518140 is fixed
                    proposal.setRelevance(proposal.getRelevance() + 1);
                }
                proposals.add(proposal);
            }
        }
    }

    /**
     * Return the completion list requested.
     * @return list of the completion item.
     */
    public List getCompletionItems() {
        response.setProposals(proposals);
        CompletionResponses.store(response);
        List completionItems = new ArrayList<>(proposals.size());
        for (int i = 0; i < proposals.size(); i++) {
            completionItems.add(toCompletionItem(proposals.get(i), i));
        }
        return completionItems;
    }

    /**
     * To completion item.
     * @param proposal proposal
     * @param index index
     * @return CompletionItem
     */
    public CompletionItem toCompletionItem(CompletionProposal proposal, int index) {
        final CompletionItem $ = new CompletionItem();
        $.setKind(mapKind(proposal.getKind(), proposal.getFlags()));
        Map data = new HashMap<>();
        data.put(CompletionResolveHandler.DATA_FIELD_REQUEST_ID, String.valueOf(response.getId()));
        data.put(CompletionResolveHandler.DATA_FIELD_PROPOSAL_ID, String.valueOf(index));
        $.setData(data);
        this.descriptionProvider.updateDescription(proposal, $);
        // Use fully qualified name as needed.
        $.setInsertText(String.valueOf(proposal.getCompletion()));
        adjustCompleteItem($);
        $.setSortText(SortTextHelper.computeSortText(proposal));
        return $;
    }

    private void adjustCompleteItem(CompletionItem item) {
        CompletionItemKind itemKind = item.getKind();
        if (itemKind == CompletionItemKind.Class || itemKind == CompletionItemKind.Interface
            || itemKind == CompletionItemKind.Enum) {
            // Display the package name in the label property.
            CompletionItemLabelDetails labelDetails = item.getLabelDetails();
            if (labelDetails != null && StringUtils.isNotBlank(labelDetails.getDescription())) {
                item.setLabel(item.getLabel() + " - " + labelDetails.getDescription());
            }
        } else if (itemKind == CompletionItemKind.Function) {
            // Merge the label details into the label property
            // because the completion provider in DEBUG CONSOLE
            // doesn't support the label details.
            CompletionItemLabelDetails labelDetails = item.getLabelDetails();
            if (labelDetails != null && StringUtils.isNotBlank(labelDetails.getDetail())) {
                item.setLabel(item.getLabel() + labelDetails.getDetail());
            }
            if (labelDetails != null && StringUtils.isNotBlank(labelDetails.getDescription())) {
                item.setLabel(item.getLabel() + " : " + labelDetails.getDescription());
            }

            String text = item.getInsertText();
            if (StringUtils.isNotBlank(text) && !text.endsWith(")")) {
                item.setInsertText(text + "()");
            }
        }
    }

    @Override
    public void acceptContext(CompletionContext context) {
        super.acceptContext(context);
        this.context = context;
        response.setContext(context);
        this.descriptionProvider = new CompletionProposalDescriptionProvider(context);
    }

    private CompletionItemKind mapKind(final int kind, final int flags) {
        // When a new CompletionItemKind is added, don't forget to update
        // SUPPORTED_KINDS
        switch (kind) {
            case CompletionProposal.ANONYMOUS_CLASS_CONSTRUCTOR_INVOCATION:
            case CompletionProposal.CONSTRUCTOR_INVOCATION:
                return CompletionItemKind.Constructor;
            case CompletionProposal.ANONYMOUS_CLASS_DECLARATION:
            case CompletionProposal.TYPE_REF:
                if (Flags.isInterface(flags)) {
                    return CompletionItemKind.Interface;
                } else if (Flags.isEnum(flags)) {
                    return CompletionItemKind.Enum;
                }
                return CompletionItemKind.Class;
            case CompletionProposal.FIELD_IMPORT:
            case CompletionProposal.METHOD_IMPORT:
            case CompletionProposal.METHOD_NAME_REFERENCE:
            case CompletionProposal.PACKAGE_REF:
            case CompletionProposal.TYPE_IMPORT:
                return CompletionItemKind.Module;
            case CompletionProposal.FIELD_REF:
            case CompletionProposal.FIELD_REF_WITH_CASTED_RECEIVER:
                if (Flags.isStatic(flags) && Flags.isFinal(flags)) {
                    return CompletionItemKind.Constant;
                }
                return CompletionItemKind.Field;
            case CompletionProposal.KEYWORD:
                return CompletionItemKind.Keyword;
            case CompletionProposal.LABEL_REF:
                return CompletionItemKind.Reference;
            case CompletionProposal.LOCAL_VARIABLE_REF:
            case CompletionProposal.VARIABLE_DECLARATION:
                return CompletionItemKind.Variable;
            case CompletionProposal.METHOD_DECLARATION:
            case CompletionProposal.METHOD_REF:
            case CompletionProposal.METHOD_REF_WITH_CASTED_RECEIVER:
            case CompletionProposal.POTENTIAL_METHOD_DECLARATION:
                return CompletionItemKind.Function;
            // text
            case CompletionProposal.ANNOTATION_ATTRIBUTE_REF:
            case CompletionProposal.JAVADOC_BLOCK_TAG:
            case CompletionProposal.JAVADOC_FIELD_REF:
            case CompletionProposal.JAVADOC_INLINE_TAG:
            case CompletionProposal.JAVADOC_METHOD_REF:
            case CompletionProposal.JAVADOC_PARAM_REF:
            case CompletionProposal.JAVADOC_TYPE_REF:
            case CompletionProposal.JAVADOC_VALUE_REF:
            default:
                return CompletionItemKind.Text;
        }
    }

    @Override
    public void setIgnored(int completionProposalKind, boolean ignore) {
        super.setIgnored(completionProposalKind, ignore);
        if (completionProposalKind == CompletionProposal.METHOD_DECLARATION && !ignore) {
            setRequireExtendedContext(true);
        }
    }

    private void acceptPotentialMethodDeclaration(CompletionProposal proposal) {
        try {
            IJavaElement enclosingElement = null;
            if (response.getContext().isExtended()) {
                enclosingElement = response.getContext().getEnclosingElement();
            } else if (typeRoot != null) {
                // kept for backward compatibility: CU is not reconciled at this moment,
                // information is missing (bug 70005)
                enclosingElement = typeRoot.getElementAt(proposal.getCompletionLocation() + 1);
            }
            if (enclosingElement == null) {
                return;
            }
            IType type = (IType) enclosingElement.getAncestor(IJavaElement.TYPE);
            if (type != null) {
                String prefix = String.valueOf(proposal.getName());
                int completionStart = proposal.getReplaceStart();
                int completionEnd = proposal.getReplaceEnd();
                int relevance = proposal.getRelevance() + 6;

                GetterSetterCompletionProposal.evaluateProposals(type, prefix, completionStart,
                        completionEnd - completionStart, relevance, proposals);
            }
        } catch (CoreException e) {
            JavaLanguageServerPlugin.logException("Accept potential method declaration failed for completion ", e);
        }
    }

    @Override
    public boolean isTestCodeExcluded() {
        return isTestCodeExcluded;
    }

    public CompletionContext getContext() {
        return context;
    }

    /**
     * copied from
     * org.eclipse.jdt.ui.text.java.CompletionProposalCollector.isFiltered(CompletionProposal)
     */
    private boolean isFiltered(CompletionProposal proposal) {
        if (isIgnored(proposal.getKind())) {
            return true;
        }

        try {
            // Only filter types and constructors from completion.
            // Methods from already imported types and packages can still be proposed.
            // See https://github.com/eclipse/eclipse.jdt.ls/issues/1212
            switch (proposal.getKind()) {
                case CompletionProposal.CONSTRUCTOR_INVOCATION:
                case CompletionProposal.ANONYMOUS_CLASS_CONSTRUCTOR_INVOCATION:
                case CompletionProposal.JAVADOC_TYPE_REF:
                case CompletionProposal.TYPE_REF: {
                    char[] declaringType = getDeclaringType(proposal);
                    return declaringType != null && isFiltered(declaringType);
                }
                default: // do nothing
            }
        } catch (Exception e) {
            // do nothing
        }

        return false;
    }

    // Temp workaround for the completion error https://github.com/microsoft/java-debug/issues/534
    private static boolean isFiltered(char[] fullTypeName) {
        if (isFilterFailed) {
            return false;
        }

        try {
            return JavaLanguageServerPlugin.getInstance().getTypeFilter().filter(new String(fullTypeName));
        } catch (NoSuchMethodError ex) {
            isFilterFailed = true;
            JavaLanguageServerPlugin.logException("isFiltered for the completion failed.", ex);
        }

        return false;
    }

    /**
     * copied from
     * org.eclipse.jdt.ui.text.java.CompletionProposalCollector.getDeclaringType(CompletionProposal)
     */
    private final char[] getDeclaringType(CompletionProposal proposal) {
        switch (proposal.getKind()) {
            case CompletionProposal.METHOD_DECLARATION:
            case CompletionProposal.METHOD_NAME_REFERENCE:
            case CompletionProposal.JAVADOC_METHOD_REF:
            case CompletionProposal.METHOD_REF:
            case CompletionProposal.CONSTRUCTOR_INVOCATION:
            case CompletionProposal.ANONYMOUS_CLASS_CONSTRUCTOR_INVOCATION:
            case CompletionProposal.METHOD_REF_WITH_CASTED_RECEIVER:
            case CompletionProposal.ANNOTATION_ATTRIBUTE_REF:
            case CompletionProposal.POTENTIAL_METHOD_DECLARATION:
            case CompletionProposal.ANONYMOUS_CLASS_DECLARATION:
            case CompletionProposal.FIELD_REF:
            case CompletionProposal.FIELD_REF_WITH_CASTED_RECEIVER:
            case CompletionProposal.JAVADOC_FIELD_REF:
            case CompletionProposal.JAVADOC_VALUE_REF:
                char[] declaration = proposal.getDeclarationSignature();
                // special methods may not have a declaring type: methods defined on arrays etc.
                // Currently known: class literals don't have a declaring type - use Object
                if (declaration == null) {
                    return "java.lang.Object".toCharArray(); //$NON-NLS-1$
                }
                return Signature.toCharArray(declaration);
            case CompletionProposal.PACKAGE_REF:
            case CompletionProposal.MODULE_REF:
            case CompletionProposal.MODULE_DECLARATION:
                return proposal.getDeclarationSignature();
            case CompletionProposal.JAVADOC_TYPE_REF:
            case CompletionProposal.TYPE_REF:
                return Signature.toCharArray(proposal.getSignature());
            case CompletionProposal.LOCAL_VARIABLE_REF:
            case CompletionProposal.VARIABLE_DECLARATION:
            case CompletionProposal.KEYWORD:
            case CompletionProposal.LABEL_REF:
            case CompletionProposal.JAVADOC_BLOCK_TAG:
            case CompletionProposal.JAVADOC_INLINE_TAG:
            case CompletionProposal.JAVADOC_PARAM_REF:
                return null;
            default:
                Assert.isTrue(false);
                return null;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy