| /* |
| * 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.Collections; |
| 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 org.netbeans.api.annotations.common.NullAllowed; |
| import org.netbeans.api.java.source.*; |
| import org.openide.util.NbBundle; |
| |
| /** |
| * |
| * @author Dusan Balek |
| */ |
| public final class JavaTooltipTask extends BaseTask { |
| |
| public static JavaTooltipTask create(final int caretOffset, @NullAllowed final Callable<Boolean> cancel) { |
| return new JavaTooltipTask(caretOffset, cancel); |
| } |
| |
| private static final String INIT = "<init>"; //NOI18N |
| private static final String THIS_KEYWORD = "this"; //NOI18N |
| private static final String SUPER_KEYWORD = "super"; //NOI18N |
| |
| private int anchorOffset; |
| private List<List<String>> toolTipData; |
| private int toolTipIndex; |
| private int toolTipOffset; |
| |
| private JavaTooltipTask(final int caretOffset, final Callable<Boolean> cancel) { |
| super(caretOffset, cancel); |
| } |
| |
| public List<List<String>> getTooltipData() { |
| return toolTipData; |
| } |
| |
| public int getTooltipIndex() { |
| return toolTipIndex; |
| } |
| |
| public int getAnchorOffset() { |
| return anchorOffset; |
| } |
| |
| public int getTooltipOffset() { |
| return toolTipOffset; |
| } |
| |
| @Override |
| protected void resolve(CompilationController controller) throws IOException { |
| Env env = getCompletionEnvironment(controller, true); |
| 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<Tree> argTypes = getArgumentsUpToPos(env, mi.getArguments(), (int) sourcePositions.getEndPosition(root, mi.getMethodSelect()), startPos, false); |
| if (argTypes != null) { |
| controller.toPhase(JavaSource.Phase.RESOLVED); |
| TypeMirror[] types = new TypeMirror[argTypes.size()]; |
| int j = 0; |
| for (Tree t : argTypes) { |
| types[j++] = controller.getTrees().getTypeMirror(new TreePath(path, t)); |
| } |
| Tree mid = mi.getMethodSelect(); |
| path = new TreePath(path, mid); |
| switch (mid.getKind()) { |
| case MEMBER_SELECT: { |
| ExpressionTree exp = ((MemberSelectTree) mid).getExpression(); |
| path = new TreePath(path, exp); |
| final Trees trees = controller.getTrees(); |
| 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))); |
| } |
| }; |
| toolTipData = getMatchingParams(controller, type, controller.getElementUtilities().getMembers(type, acceptor), ((MemberSelectTree) mid).getIdentifier().toString(), types, controller.getTypes()); |
| break; |
| } |
| case IDENTIFIER: { |
| final Scope scope = env.getScope(); |
| final TreeUtilities tu = controller.getTreeUtilities(); |
| final Trees trees = controller.getTrees(); |
| 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(); |
| toolTipData = getMatchingParams(controller, superclass, controller.getElementUtilities().getMembers(superclass, acceptor), INIT, types, controller.getTypes()); |
| } else if (THIS_KEYWORD.equals(name) && enclClass != null) { |
| TypeMirror thisclass = enclClass.asType(); |
| toolTipData = getMatchingParams(controller, thisclass, controller.getElementUtilities().getMembers(thisclass, acceptor), INIT, types, controller.getTypes()); |
| } else { |
| toolTipData = getMatchingParams(controller, enclClass != null ? enclClass.asType() : null, controller.getElementUtilities().getLocalMembersAndVars(scope, acceptor), name, types, controller.getTypes()); |
| } |
| 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<Tree> argTypes = getArgumentsUpToPos(env, nc.getArguments(), pos, startPos, false); |
| if (argTypes != null) { |
| controller.toPhase(JavaSource.Phase.RESOLVED); |
| TypeMirror[] types = new TypeMirror[argTypes.size()]; |
| int j = 0; |
| for (Tree t : argTypes) { |
| types[j++] = controller.getTrees().getTypeMirror(new TreePath(path, t)); |
| } |
| path = new TreePath(path, nc.getIdentifier()); |
| final Trees trees = controller.getTrees(); |
| 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)); |
| } |
| }; |
| toolTipData = getMatchingParams(controller, type, controller.getElementUtilities().getMembers(type, acceptor), INIT, types, controller.getTypes()); |
| 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; |
| } |
| } |
| lastTree = tree; |
| path = path.getParentPath(); |
| } |
| } |
| |
| private List<List<String>> getMatchingParams(CompilationInfo info, TypeMirror type, Iterable<? extends Element> elements, String name, TypeMirror[] argTypes, Types types) { |
| List<List<String>> ret = new ArrayList<>(); |
| TypeUtilities tu = info.getTypeUtilities(); |
| for (Element e : elements) { |
| if ((e.getKind() == CONSTRUCTOR || e.getKind() == METHOD) && name.contentEquals(e.getSimpleName())) { |
| List<? extends VariableElement> params = ((ExecutableElement) e).getParameters(); |
| int parSize = params.size(); |
| boolean varArgs = ((ExecutableElement) e).isVarArgs(); |
| if (!varArgs && (parSize < argTypes.length)) { |
| continue; |
| } |
| if (parSize == 0) { |
| ret.add(Collections.<String>singletonList(NbBundle.getMessage(JavaCompletionTask.class, "JCP-no-parameters"))); |
| } else { |
| ExecutableType eType = (ExecutableType) asMemberOf(e, type, types); |
| Iterator<? extends TypeMirror> 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<String> paramStrings = new ArrayList<>(parSize); |
| Iterator<? extends TypeMirror> tIt = eType.getParameterTypes().iterator(); |
| for (Iterator<? extends VariableElement> it = params.iterator(); it.hasNext();) { |
| VariableElement ve = it.next(); |
| StringBuilder sb = new StringBuilder(); |
| sb.append(tu.getTypeName(tIt.next())); |
| if (varArgs && !tIt.hasNext()) { |
| sb.delete(sb.length() - 2, sb.length()).append("..."); //NOI18N |
| } |
| CharSequence veName = ve.getSimpleName(); |
| if (veName != null && veName.length() > 0) { |
| sb.append(" "); // NOI18N |
| sb.append(veName); |
| } |
| if (it.hasNext()) { |
| sb.append(", "); // NOI18N |
| } |
| paramStrings.add(sb.toString()); |
| } |
| ret.add(paramStrings); |
| break; |
| } |
| if (argTypes[i] == null || argTypes[i].getKind() != TypeKind.ERROR && !types.isAssignable(argTypes[i], param)) { |
| break; |
| } |
| } |
| } |
| } |
| } |
| return ret.isEmpty() ? null : ret; |
| } |
| } |