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

org.netbeans.modules.java.completion.JavaTooltipTask Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.netbeans.modules.java.completion;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;

import javax.lang.model.element.Element;
import static javax.lang.model.element.ElementKind.*;
import javax.lang.model.element.ExecutableElement;
import static javax.lang.model.element.Modifier.*;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.*;
import javax.lang.model.util.Types;

import com.sun.source.tree.*;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;
import java.util.Collections;
import javax.lang.model.element.AnnotationValue;

import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.java.source.*;

/**
 *
 * @author Dusan Balek
 */
public final class JavaTooltipTask extends BaseTask {

    public static JavaTooltipTask create(final int caretOffset, @NullAllowed final Callable cancel) {
        return new JavaTooltipTask(caretOffset, cancel);
    }

    private static final String INIT = ""; //NOI18N
    private static final String THIS_KEYWORD = "this"; //NOI18N
    private static final String SUPER_KEYWORD = "super"; //NOI18N

    private int anchorOffset;
    private List> toolTipData;
    private List toolTipSignatures;
    private int toolTipIndex;
    private int activeSignatureIndex;
    private int toolTipOffset;

    private JavaTooltipTask(final int caretOffset, final Callable cancel) {
        super(caretOffset, cancel);
    }

    public List> getTooltipData() {
        return toolTipData;
    }

    public List getTooltipSignatures() {
        return toolTipSignatures;
    }

    public int getTooltipIndex() {
        return toolTipIndex;
    }

    public int getActiveSignatureIndex() {
        return activeSignatureIndex;
    }

    public int getAnchorOffset() {
        return anchorOffset;
    }

    public int getTooltipOffset() {
        return toolTipOffset;
    }

    @Override
    protected void resolve(CompilationController controller) throws IOException {
        Env env = getCompletionEnvironment(controller, false);
        if (env == null) {
            return;
        }
        Tree lastTree = null;
        int offset = env.getOffset();
        TreePath path = env.getPath();
        while (path != null) {
            Tree tree = path.getLeaf();
            if (tree.getKind() == Tree.Kind.METHOD_INVOCATION) {
                MethodInvocationTree mi = (MethodInvocationTree) tree;
                CompilationUnitTree root = env.getRoot();
                SourcePositions sourcePositions = env.getSourcePositions();
                int startPos = lastTree != null ? (int) sourcePositions.getStartPosition(root, lastTree) : offset;
                List argTypes = getArgumentsUpToPos(env, mi.getArguments(), (int) sourcePositions.getEndPosition(root, mi.getMethodSelect()), startPos, false);
                if (argTypes != null) {
                    controller.toPhase(JavaSource.Phase.RESOLVED);
                    final Trees trees = controller.getTrees();
                    TypeMirror[] types = new TypeMirror[argTypes.size()];
                    int j = 0;
                    for (Tree t : argTypes) {
                        types[j++] = trees.getTypeMirror(new TreePath(path, t));
                    }
                    final Tree mid = mi.getMethodSelect();
                    final Element activeElement = trees.getElement(path);
                    path = new TreePath(path, mid);
                    switch (mid.getKind()) {
                        case MEMBER_SELECT: {
                            ExpressionTree exp = ((MemberSelectTree) mid).getExpression();
                            path = new TreePath(path, exp);
                            final TypeMirror type = trees.getTypeMirror(path);
                            final Element element = trees.getElement(path);
                            final boolean isStatic = element != null && (element.getKind().isClass() || element.getKind().isInterface() || element.getKind() == TYPE_PARAMETER);
                            final boolean isSuperCall = element != null && element.getKind().isField() && element.getSimpleName().contentEquals(SUPER_KEYWORD);
                            final Scope scope = env.getScope();
                            TypeElement enclClass = scope.getEnclosingClass();
                            final TypeMirror enclType = enclClass != null ? enclClass.asType() : null;
                            ElementUtilities.ElementAcceptor acceptor = new ElementUtilities.ElementAcceptor() {
                                @Override
                                public boolean accept(Element e, TypeMirror t) {
                                    return (!isStatic || e.getModifiers().contains(STATIC) || e.getKind() == CONSTRUCTOR) && (t.getKind() != TypeKind.DECLARED || trees.isAccessible(scope, e, (DeclaredType) (isSuperCall && enclType != null ? enclType : t)));
                                }
                            };
                            handleMatchingParams(controller, type, activeElement, controller.getElementUtilities().getMembers(type, acceptor), ((MemberSelectTree) mid).getIdentifier().toString(), types);
                            break;
                        }
                        case IDENTIFIER: {
                            final Scope scope = env.getScope();
                            final TreeUtilities tu = controller.getTreeUtilities();
                            final TypeElement enclClass = scope.getEnclosingClass();
                            final boolean isStatic = enclClass != null ? (tu.isStaticContext(scope) || (env.getPath().getLeaf().getKind() == Tree.Kind.BLOCK && ((BlockTree) env.getPath().getLeaf()).isStatic())) : false;
                            ElementUtilities.ElementAcceptor acceptor = new ElementUtilities.ElementAcceptor() {
                                @Override
                                public boolean accept(Element e, TypeMirror t) {
                                    switch (e.getKind()) {
                                        case CONSTRUCTOR:
                                            return !e.getModifiers().contains(PRIVATE);
                                        case METHOD:
                                            return (!isStatic || e.getModifiers().contains(STATIC)) && trees.isAccessible(scope, e, (DeclaredType) t);
                                        default:
                                            return false;
                                    }
                                }
                            };
                            String name = ((IdentifierTree) mid).getName().toString();
                            if (SUPER_KEYWORD.equals(name) && enclClass != null) {
                                TypeMirror superclass = enclClass.getSuperclass();
                                handleMatchingParams(controller, superclass, activeElement, controller.getElementUtilities().getMembers(superclass, acceptor), INIT, types);
                            } else if (THIS_KEYWORD.equals(name) && enclClass != null) {
                                TypeMirror thisclass = enclClass.asType();
                                handleMatchingParams(controller, thisclass, activeElement, controller.getElementUtilities().getMembers(thisclass, acceptor), INIT, types);
                            } else {
                                handleMatchingParams(controller, enclClass != null ? enclClass.asType() : null, activeElement, controller.getElementUtilities().getLocalMembersAndVars(scope, acceptor), name, types);
                            }
                            break;
                        }
                    }
                    toolTipIndex = types.length;
                    startPos = (int) sourcePositions.getEndPosition(env.getRoot(), mi.getMethodSelect());
                    String text = controller.getText().substring(startPos, offset);
                    int idx = text.indexOf('('); //NOI18N
                    anchorOffset = idx < 0 ? startPos : startPos + controller.getSnapshot().getOriginalOffset(idx);
                    idx = text.lastIndexOf(','); //NOI18N
                    toolTipOffset = idx < 0 ? startPos : startPos + controller.getSnapshot().getOriginalOffset(idx);
                    if (toolTipOffset < anchorOffset) {
                        toolTipOffset = anchorOffset;
                    }
                    return;
                }
            } else if (tree.getKind() == Tree.Kind.NEW_CLASS) {
                NewClassTree nc = (NewClassTree) tree;
                CompilationUnitTree root = env.getRoot();
                SourcePositions sourcePositions = env.getSourcePositions();
                int startPos = lastTree != null ? (int) sourcePositions.getStartPosition(root, lastTree) : offset;
                int pos = (int) sourcePositions.getEndPosition(root, nc.getIdentifier());
                List argTypes = getArgumentsUpToPos(env, nc.getArguments(), pos, startPos, false);
                if (argTypes != null) {
                    controller.toPhase(JavaSource.Phase.RESOLVED);
                    final Trees trees = controller.getTrees();
                    TypeMirror[] types = new TypeMirror[argTypes.size()];
                    int j = 0;
                    for (Tree t : argTypes) {
                        types[j++] = trees.getTypeMirror(new TreePath(path, t));
                    }
                    final Element activeElement = trees.getElement(path);
                    path = new TreePath(path, nc.getIdentifier());
                    TypeMirror type = trees.getTypeMirror(path);
                    if (type != null && type.getKind() == TypeKind.ERROR && path.getLeaf().getKind() == Tree.Kind.PARAMETERIZED_TYPE) {
                        path = new TreePath(path, ((ParameterizedTypeTree) path.getLeaf()).getType());
                        type = trees.getTypeMirror(path);
                    }
                    final Element el = trees.getElement(path);
                    final Scope scope = env.getScope();
                    final boolean isAnonymous = nc.getClassBody() != null || (el != null && (el.getKind().isInterface() || el.getModifiers().contains(ABSTRACT)));
                    ElementUtilities.ElementAcceptor acceptor = new ElementUtilities.ElementAcceptor() {
                        @Override
                        public boolean accept(Element e, TypeMirror t) {
                            return e.getKind() == CONSTRUCTOR && (trees.isAccessible(scope, e, (DeclaredType) t) || isAnonymous && e.getModifiers().contains(PROTECTED));
                        }
                    };
                    handleMatchingParams(controller, type, activeElement, controller.getElementUtilities().getMembers(type, acceptor), INIT, types);
                    toolTipIndex = types.length;
                    if (pos < 0) {
                        path = path.getParentPath();
                        pos = (int) sourcePositions.getStartPosition(root, path.getLeaf());
                    }
                    String text = controller.getText().substring(pos, offset);
                    int idx = text.indexOf('('); //NOI18N
                    anchorOffset = idx < 0 ? pos : pos + controller.getSnapshot().getOriginalOffset(idx);
                    idx = text.lastIndexOf(','); //NOI18N
                    toolTipOffset = idx < 0 ? pos : pos + controller.getSnapshot().getOriginalOffset(idx);
                    if (toolTipOffset < anchorOffset) {
                        toolTipOffset = anchorOffset;
                    }
                    return;
                }
            } else if (tree.getKind() == Tree.Kind.ANNOTATION) {
                AnnotationTree at = (AnnotationTree) tree;
                controller.toPhase(JavaSource.Phase.RESOLVED);
                final Trees trees = controller.getTrees();
                final Element element = trees.getElement(path);
                if (element != null && element.getKind() == ANNOTATION_TYPE) {
                    final Element activeElement = lastTree != null && lastTree.getKind() == Tree.Kind.ASSIGNMENT ? trees.getElement(new TreePath(path, ((AssignmentTree) lastTree).getVariable())) : null;
                    TypeUtilities tu = controller.getTypeUtilities();
                    List> data = new ArrayList<>();
                    List signatures = new ArrayList<>();
                    for (Element e : element.getEnclosedElements()) {
                        if (e.getKind() == METHOD && e.asType().getKind() == TypeKind.EXECUTABLE) {
                            StringBuilder sb = new StringBuilder();
                            sb.append(tu.getTypeName(((ExecutableType) e.asType()).getReturnType())).append(' '); //NOI18N
                            sb.append(e.getSimpleName()).append("()"); //NOI18N
                            AnnotationValue defaultValue = ((ExecutableElement) e).getDefaultValue();
                            if (defaultValue != null) {
                                sb.append(" default ").append(defaultValue.toString());
                            }
                            if (e == activeElement) {
                                activeSignatureIndex = signatures.size();
                            }
                            data.add(Collections.singletonList(sb.toString()));
                            signatures.add(sb.toString());
                        }
                    }
                    toolTipData = data.isEmpty() ? null : data;
                    toolTipSignatures = signatures.isEmpty() ? null : signatures;
                    toolTipIndex = -1;
                    CompilationUnitTree root = env.getRoot();
                    SourcePositions sourcePositions = env.getSourcePositions();
                    int pos = (int) sourcePositions.getEndPosition(root, at.getAnnotationType());
                    String text = controller.getText().substring(pos, offset);
                    int idx = text.indexOf('('); //NOI18N
                    anchorOffset = idx < 0 ? pos : pos + controller.getSnapshot().getOriginalOffset(idx);
                    idx = text.lastIndexOf(','); //NOI18N
                    toolTipOffset = idx < 0 ? pos : pos + controller.getSnapshot().getOriginalOffset(idx);
                    if (toolTipOffset < anchorOffset) {
                        toolTipOffset = anchorOffset;
                    }
                    return;
                }
            }
            lastTree = tree;
            path = path.getParentPath();
        }
    }

    private void handleMatchingParams(CompilationInfo info, TypeMirror type, Element activeElement, Iterable elements, String name, TypeMirror[] argTypes) {
        List> data = new ArrayList<>();
        List signatures = new ArrayList<>();
        Types types = info.getTypes();
        TypeUtilities tu = info.getTypeUtilities();
        activeSignatureIndex = 0;
        for (Element e : elements) {
            if ((e.getKind() == CONSTRUCTOR || e.getKind() == METHOD) && name.contentEquals(e.getSimpleName())) {
                List params = ((ExecutableElement) e).getParameters();
                int parSize = params.size();
                boolean varArgs = ((ExecutableElement) e).isVarArgs();
                if (!varArgs && (parSize < argTypes.length)) {
                    continue;
                }
                if (e == activeElement) {
                    activeSignatureIndex = signatures.size();
                }
                ExecutableType eType = (ExecutableType) asMemberOf(e, type, types);
                StringBuilder sig = new StringBuilder(INIT.equals(name) && type != null && type.getKind() == TypeKind.DECLARED ? ((DeclaredType) type).asElement().getSimpleName() : name).append('(');
                if (parSize == 0) {
                    data.add(new ArrayList<>());
                    sig.append(')');
                    if (e.getKind() == METHOD) {
                        sig.append(" : ").append(tu.getTypeName(eType.getReturnType()));
                    }
                    signatures.add(sig.toString());
                } else {
                    Iterator parIt = eType.getParameterTypes().iterator();
                    TypeMirror param = null;
                    for (int i = 0; i <= argTypes.length; i++) {
                        if (parIt.hasNext()) {
                            param = parIt.next();
                            if (!parIt.hasNext() && param.getKind() == TypeKind.ARRAY) {
                                param = ((ArrayType) param).getComponentType();
                            }
                        } else if (!varArgs) {
                            break;
                        }
                        if (i == argTypes.length) {
                            List paramStrings = new ArrayList<>(parSize);
                            Iterator tIt = eType.getParameterTypes().iterator();
                            for (Iterator it = params.iterator(); it.hasNext();) {
                                VariableElement ve = it.next();
                                StringBuilder sb = new StringBuilder();
                                CharSequence typeName = tu.getTypeName(tIt.next());
                                sb.append(typeName);
                                sig.append(typeName);
                                if (varArgs && !tIt.hasNext()) {
                                    sb.delete(sb.length() - 2, sb.length()).append("..."); //NOI18N
                                    sig.delete(sig.length() - 2, sig.length()).append("..."); //NOI18N
                                }
                                CharSequence veName = ve.getSimpleName();
                                if (veName != null && veName.length() > 0) {
                                    sb.append(" ").append(veName); // NOI18N
                                    sig.append(" ").append(veName); // NOI18N
                                }
                                if (it.hasNext()) {
                                    sig.append(", "); // NOI18N
                                }
                                paramStrings.add(sb.toString());
                            }
                            data.add(paramStrings);
                            sig.append(')');
                            if (e.getKind() == METHOD) {
                                sig.append(" : ").append(tu.getTypeName(eType.getReturnType()));
                            }
                            signatures.add(sig.toString());
                            break;
                        }
                        if (argTypes[i] == null || argTypes[i].getKind() != TypeKind.ERROR && !isAssignable(types, argTypes[i], param)) {
                            break;
                        }
                    }
                }
            }
        }
        toolTipData = data.isEmpty() ? null : data;
        toolTipSignatures = signatures.isEmpty() ? null : signatures;
    }

    private static boolean isAssignable(Types types, TypeMirror arg, TypeMirror parameter) {
        if(types.isAssignable(arg, parameter)) {
            return true;
        } else if (parameter instanceof TypeVariable) {
            TypeMirror requiredTypes = ((TypeVariable) parameter).getUpperBound();
            return types.isAssignable(arg, requiredTypes);
        }
        return false;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy