blob: b3e4aa5dcbf5c6182e2db4a95431667c3eeaaaad [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2019 Nicolaj Hoess.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Nicolaj Hoess - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.corext.template.java;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.CompletionContext;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.NamingConventions;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.BooleanLiteral;
import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.PrimitiveType;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.SuperFieldAccess;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.WildcardType;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.internal.core.manipulation.StubUtility;
import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.IASTSharedValues;
import org.eclipse.jdt.internal.corext.template.java.JavaContext;
import org.eclipse.jdt.internal.core.manipulation.dom.ASTResolving;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.templates.Template;
import org.eclipse.jface.text.templates.TemplateBuffer;
import org.eclipse.jface.text.templates.TemplateContextType;
import org.eclipse.jface.text.templates.TemplateException;
import org.eclipse.jface.text.templates.TemplateVariable;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.TextEdit;
/**
* This class is an extension to the existing {@link JavaContext} and includes/provides additional
* information on the current node which the code completion was invoked on.
*/
public class JavaPostfixContext extends JavaContext {
private static final Object CONTEXT_TYPE_ID= "postfix"; //$NON-NLS-1$
private static final String OBJECT_SIGNATURE= "java.lang.Object"; //$NON-NLS-1$
private static final String ID_SEPARATOR= "��"; //$NON-NLS-1$
private static final Pattern INNER_EXPRESSION_PATTERN= Pattern.compile("\\$\\{([a-zA-Z]*):" + InnerExpressionResolver.INNER_EXPRESSION_VAR + "\\(([^\\$|\\{|\\}]*)\\)\\}"); //$NON-NLS-1$ //$NON-NLS-2$
private static final Pattern CLASS_NAME_PATTERN= Pattern.compile("[a-zA-Z0-9$_\\.]+"); //$NON-NLS-1$
private ASTNode selectedNode;
private Map<ASTNode, Region> nodeRegions;
private Map<TemplateVariable, int[]> variableOutOfRangeOffsets;
private boolean domInitialized;
private BodyDeclaration bodyDeclaration;
private ASTNode parentDeclaration;
private CompletionContext completionCtx;
public JavaPostfixContext(TemplateContextType type,
IDocument document, final int completionOffset, int completionLength,
ICompilationUnit compilationUnit) {
this(type, document, completionOffset, completionLength, compilationUnit, null, null, null);
}
public JavaPostfixContext(TemplateContextType type,
IDocument document, Position completionPosition,
ICompilationUnit compilationUnit) {
this(type, document, completionPosition.getOffset(), completionPosition.getLength(), compilationUnit, null, null, null);
}
public JavaPostfixContext(TemplateContextType type,
IDocument document, int offset, int length,
ICompilationUnit compilationUnit,
ASTNode currentNode, ASTNode parentNode, CompletionContext context) {
super(type, document, offset, length, compilationUnit);
completionCtx= context;
nodeRegions= new HashMap<>();
variableOutOfRangeOffsets= new HashMap<>();
nodeRegions.put(currentNode, calculateNodeRegion(currentNode));
nodeRegions.put(parentNode, calculateNodeRegion(parentNode));
selectedNode= findBestASTNodeSelection(currentNode);
}
/**
* Determines and returns the <i>best</i> {@link ASTNode} to apply a postfix template in the
* current context.<br/>
* Examples for <i>best</i> {@link ASTNode}s:
* <ul>
* <li><code>new Integer(0).$</code> will return the {@link ASTNode} for
* <code>new Integer(0)</code></li>
* </ul>
*
* @param currentNode The {@link ASTNode} of the completion.
* @return An {@link ASTNode} of the key set of {@link #nodeRegions}.
*/
private ASTNode findBestASTNodeSelection(ASTNode currentNode) {
// This implementation takes the longest ASTNode by means of string length
// which doesn't exceed the completion position.
// If future extensions of the postfix implementation inject more than two ASTNodes
// the selection should be more intelligent.
// Some examples which should be considered for future implementations:
// `1 + 1 + 1.$` should select `1 + 1 + 1` (note that the expression is not parenthesized).
// `foo("asdf" + true.$` should select `true` since `"asdf" + true` makes no sense for any template in this context.
// `"test" + true.$` should select `true` for "sif" template and `"test" + true` for "var" template.
ASTNode result= currentNode;
int currMax= getNodeBegin(currentNode) + getNodeLength(currentNode);
Set<ASTNode> nodes= nodeRegions.keySet();
for (ASTNode n : nodes) {
int end= getNodeBegin(n) + getNodeLength(n);
if (end > currMax && end <= getCompletionOffset()) {
currMax= end;
result= n;
}
}
return result;
}
/**
* Adds a new field to the {@link AST} using the given type and variable name. The method
* returns a {@link TextEdit} which can then be applied using the
* {@link #applyTextEdit(TextEdit)} method.
*
* @param type fully qualified type name of the new field
* @param varName suggested field name
* @param publicField <code>true</code> if the new field should be public
* @param staticField <code>true</code> if the new field should be static
* @param finalField <code>true</code> if the new field should be final
* @param value the initialization value of the new field. If this parameter is
* <code>null</code> or empty the field is not initialized.
* @return a {@link TextEdit} which represents the changes which would be made, or
* <code>null</code> if the field can not be created.
*/
public TextEdit addField(String type, String varName, boolean publicField, boolean staticField, boolean finalField, String value) {
if (isReadOnly())
return null;
if (!domInitialized)
initDomAST();
boolean isStatic= isBodyStatic();
int modifiers= (!publicField) ? Modifier.PRIVATE : Modifier.PUBLIC;
if (isStatic || staticField) {
modifiers|= Modifier.STATIC;
}
if (finalField) {
modifiers|= Modifier.FINAL;
}
ASTRewrite rewrite= ASTRewrite.create(parentDeclaration.getAST());
addFieldDeclaration(rewrite, parentDeclaration, modifiers, varName, type, value);
TextEdit te= rewrite.rewriteAST(getDocument(), null);
return te;
}
private VariableDeclarationFragment addFieldDeclaration(ASTRewrite rewrite, ASTNode newTypeDecl, int modifiers, String varName, String qualifiedName, String value) {
ChildListPropertyDescriptor property= ASTNodes.getBodyDeclarationsProperty(newTypeDecl);
List<BodyDeclaration> decls= ASTNodes.getBodyDeclarations(newTypeDecl);
AST ast= newTypeDecl.getAST();
VariableDeclarationFragment newDeclFrag= ast.newVariableDeclarationFragment();
newDeclFrag.setName(ast.newSimpleName(varName));
Type type= createType(Signature.createTypeSignature(qualifiedName, true), ast);
if (value != null && value.trim().length() > 0) {
Expression e= createExpression(value);
Expression ne= (Expression) ASTNode.copySubtree(ast, e);
newDeclFrag.setInitializer(ne);
} else {
if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)) {
newDeclFrag.setInitializer(ASTNodeFactory.newDefaultExpression(ast, type, 0));
}
}
FieldDeclaration newDecl= ast.newFieldDeclaration(newDeclFrag);
newDecl.setType(type);
newDecl.modifiers().addAll(ASTNodeFactory.newModifiers(ast, modifiers));
int insertIndex= findFieldInsertIndex(decls, getCompletionOffset(), modifiers);
rewrite.getListRewrite(newTypeDecl, property).insertAt(newDecl, insertIndex, null);
return newDeclFrag;
}
/**
* Adds all imports for a fully qualified generic type name, i.e. this method calls
* {@link #addImport(String)} for all "parts" of the given qualified name.
*
* @param className fully qualified name of a generic type name
* @return returns unqualified name of the generic type
*/
public String addImportGenericClass(String className) {
Matcher m= CLASS_NAME_PATTERN.matcher(className);
List<String> classNames= new ArrayList<>();
Map<String, String> classNameMapping= new HashMap<>();
while (m.find()) {
classNames.add(className.substring(m.start(), m.end()));
}
/*
* In case the import class looks like this:
* a.b.c.Foo<b.c.Foo>
* we have to consider that - if we do not care about ordering, the following could happen:
* 1. trying to import b.c.Foo - import is resolved to Foo
* 2. replacing b.c.Foo with Foo - a.Foo<Foo> --> not correct (should be a.b.c.Foo<Foo>)
* 3. ...
*
* The solution to this is as follows:
* 1. sorting the fully qualified class names by length
* 2. replacing all occurring class names with unique identifiers ($$id$$)
* 3. importing all class names and map the fully qualified identifier with the resolved identifier of the class
* 4. replace the unique identifiers with the mapped values
*/
Collections.sort(classNames, (arg0, arg1) -> arg1.length() - arg0.length());
for (int i= 0; i < classNames.size(); i++) {
className= className.replace(classNames.get(i), ID_SEPARATOR + i + ID_SEPARATOR);
classNameMapping.put(classNames.get(i), addImport(classNames.get(i)));
}
for (int i= 0; i < classNames.size(); i++) {
className= className.replace(ID_SEPARATOR + i + ID_SEPARATOR, classNameMapping.get(classNames.get(i)));
}
return className;
}
/**
* Applies a {@link TextEdit} to the {@link IDocument} of this context and updates the
* completion offset variable.
*
* @param te {@link TextEdit} to apply
* @return <code>true</code> if the method was successful, <code>false</code> otherwise
*/
public boolean applyTextEdit(TextEdit te) {
try {
te.apply(getDocument());
setCompletionOffset(getCompletionOffset() + ((te.getOffset() < getCompletionOffset()) ? te.getLength() : 0));
return true;
} catch (MalformedTreeException | BadLocationException e) {
// fall through returning false
}
return false;
}
private Region calculateNodeRegion(ASTNode node) {
if (node == null) {
return new Region(0, 0);
}
int start= getNodeBegin(node);
int end= getCompletionOffset() - getPrefixKey().length() - start - 1; // TODO Not entirely correct but good enough for our needs (calculation should be similar to getNodeBegin(..))
return new Region(start, end);
}
/*
* @see TemplateContext#canEvaluate(Template templates)
*/
@Override
public boolean canEvaluate(Template template) {
if (!template.getContextTypeId().equals(JavaPostfixContext.CONTEXT_TYPE_ID))
return false;
if (isForceEvaluation())
return true;
if (selectedNode == null) // We can evaluate to true only if we have a valid inner expression
return false;
if (template.getName().toLowerCase().startsWith(getPrefixKey().toLowerCase()) == false) {
return false;
}
// We check if the template makes "sense" by checking the requirements/conditions for the template
// For this purpose we have to resolve the inner_expression variable of the template
// This approach is much faster then delegating this to the existing TemplateTranslator class
// (Maybe this hard-coded dependency to the inner expression variable is a little bit weird)
Matcher matcher= INNER_EXPRESSION_PATTERN.matcher(template.getPattern());
boolean result= true;
while (matcher.find()) {
String[] types= matcher.group(2).split(","); //$NON-NLS-1$
for (String s : types) {
if (!Arrays.asList(InnerExpressionResolver.FLAGS).contains(s)) {
result= false;
if (this.isNodeResolvingTo(selectedNode, s.trim()) == true) {
return true;
}
}
}
}
return result;
}
private boolean containsNestedCapture(String signature) {
return signature.length() > 1 && signature.indexOf(Signature.C_CAPTURE, 1) != -1;
}
private Expression createExpression(String expr) {
ASTParser parser= ASTParser.newParser(IASTSharedValues.SHARED_AST_LEVEL);
parser.setKind(ASTParser.K_EXPRESSION);
parser.setResolveBindings(true);
parser.setSource(expr.toCharArray());
ASTNode astNode= parser.createAST(new NullProgressMonitor());
return (Expression) astNode;
}
private Type createType(String typeSig, AST ast) {
int sigKind= Signature.getTypeSignatureKind(typeSig);
switch (sigKind) {
case Signature.BASE_TYPE_SIGNATURE:
return ast.newPrimitiveType(PrimitiveType.toCode(Signature.toString(typeSig)));
case Signature.ARRAY_TYPE_SIGNATURE:
Type elementType= createType(Signature.getElementType(typeSig), ast);
return ast.newArrayType(elementType, Signature.getArrayCount(typeSig));
case Signature.CLASS_TYPE_SIGNATURE:
String erasureSig= Signature.getTypeErasure(typeSig);
String erasureName= Signature.toString(erasureSig);
if (erasureSig.charAt(0) == Signature.C_RESOLVED) {
erasureName= addImport(erasureName);
}
Type baseType= ast.newSimpleType(ast.newName(erasureName));
String[] typeArguments= Signature.getTypeArguments(typeSig);
if (typeArguments.length > 0) {
ParameterizedType type= ast.newParameterizedType(baseType);
List<Type> argNodes= type.typeArguments();
for (int i= 0; i < typeArguments.length; i++) {
String curr= typeArguments[i];
if (containsNestedCapture(curr)) {
argNodes.add(ast.newWildcardType());
} else {
argNodes.add(createType(curr, ast));
}
}
return type;
}
return baseType;
case Signature.TYPE_VARIABLE_SIGNATURE:
return ast.newSimpleType(ast.newSimpleName(Signature.toString(typeSig)));
case Signature.WILDCARD_TYPE_SIGNATURE:
WildcardType wildcardType= ast.newWildcardType();
char ch= typeSig.charAt(0);
if (ch != Signature.C_STAR) {
Type bound= createType(typeSig.substring(1), ast);
wildcardType.setBound(bound, ch == Signature.C_EXTENDS);
}
return wildcardType;
case Signature.CAPTURE_TYPE_SIGNATURE:
return createType(typeSig.substring(1), ast);
}
return ast.newSimpleType(ast.newName(OBJECT_SIGNATURE));
}
@Override
public TemplateBuffer evaluate(Template template)
throws BadLocationException, TemplateException {
TemplateBuffer result= super.evaluate(template);
// After the template buffer has been created we are able to add out of range offsets
// This is not possible beforehand as it will result in an exception!
for (TemplateVariable tv : result.getVariables()) {
int[] outOfRangeOffsets= variableOutOfRangeOffsets.get(tv);
if (outOfRangeOffsets != null && outOfRangeOffsets.length > 0) {
int[] offsets= tv.getOffsets();
int[] newOffsets= new int[outOfRangeOffsets.length + offsets.length];
System.arraycopy(offsets, 0, newOffsets, 0, offsets.length);
for (int i= 0; i < outOfRangeOffsets.length; i++) {
newOffsets[i + offsets.length]= outOfRangeOffsets[i];
}
tv.setOffsets(newOffsets);
}
}
return result;
}
private int findFieldInsertIndex(List<BodyDeclaration> decls, int currPos, int modifiers) {
for (int i= decls.size() - 1; i >= 0; i--) {
ASTNode curr= decls.get(i);
if (curr instanceof FieldDeclaration && currPos > curr.getStartPosition() + curr.getLength()
&& ((FieldDeclaration) curr).getModifiers() == modifiers) {
return i + 1;
}
}
return 0;
}
/**
* Returns the {@link Region} which represents the source region of the affected statement.
*
* @return the source region of the affected statement
*/
public Region getAffectedSourceRegion() {
return new Region(getCompletionOffset() - getPrefixKey().length() - nodeRegions.get(selectedNode).getLength() - 1, nodeRegions.get(selectedNode).getLength());
}
public String getAffectedStatement() {
Region r= getAffectedSourceRegion();
try {
return getDocument().get(r.getOffset(), r.getLength());
} catch (BadLocationException e) {
// fall through returning empty string
}
return ""; //$NON-NLS-1$
}
@Override
public int getEnd() {
return getCompletionOffset();
}
/**
* Returns the fully qualified name the node of the current code completion invocation resolves
* to.
*
* @return a fully qualified type signature or the name of the base type.
*/
public String getInnerExpressionTypeSignature() {
return resolveNodeToTypeString(selectedNode);
}
/**
* Calculates the beginning position of a given {@link ASTNode}
*
* @param node the {@link ASTNode}
* @return source position of the node or -1 if the given node is <code>null</code>
*/
protected int getNodeBegin(ASTNode node) {
if (node == null) {
return -1;
}
if (node.getParent() instanceof MethodInvocation) {
return ((MethodInvocation) node.getParent()).getStartPosition();
} else if (node.getParent() instanceof FieldAccess || node.getParent() instanceof SuperFieldAccess) {
return node.getParent().getStartPosition();
} else if (node instanceof Name) {
ASTNode n= node;
while (n.getParent() instanceof QualifiedName) {
n= n.getParent();
}
return ((Name) n).getStartPosition();
}
return node.getStartPosition();
}
/**
* Calculates the length of a given {@link ASTNode}
*
* @param node the {@link ASTNode}
* @return length of the node or -1 if the given node is <code>null</code>
*/
protected int getNodeLength(ASTNode node) {
if (node == null) {
return -1;
}
return node.getLength();
}
/**
* Returns the current prefix of the key which was typed in. <br/>
* Examples: <code>
* <br/>
* new Object(). => getPrefixKey() returns ""<br/>
* new Object().a => getPrefixKey() returns "a"<br/>
* new object().asdf => getPrefixKey() returns "asdf"<br/>
* </code>
*
* @return an empty string or a string which represents the prefix of the key which was typed in
*/
private String getPrefixKey() {
if (completionCtx != null) {
IDocument document= getDocument();
int start= completionCtx.getTokenStart();
int end= completionCtx.getTokenEnd();
try {
return document.get(start, end - start + 1);
} catch (BadLocationException e) {
// fall through returning empty string
}
}
return ""; //$NON-NLS-1$
}
@Override
public int getStart() {
int result= super.getStart();
result-= getAffectedSourceRegion().getLength() + 1;
return result;
}
private void initDomAST() {
if (isReadOnly())
return;
ASTParser parser= ASTParser.newParser(IASTSharedValues.SHARED_AST_LEVEL);
parser.setSource(getCompilationUnit());
parser.setResolveBindings(true);
ASTNode domAst= parser.createAST(new NullProgressMonitor());
NodeFinder nf= new NodeFinder(domAst, getCompletionOffset(), 1);
ASTNode cv= nf.getCoveringNode();
bodyDeclaration= ASTResolving.findParentBodyDeclaration(cv);
parentDeclaration= ASTResolving.findParentType(cv);
domInitialized= true;
}
/**
* Returns <code>true</code> if the type or one of its supertypes of a given {@link ASTNode}
* resolves to a given type signature. <br/>
* Examples: <br/>
* <code>
* <br/>
* isNodeResolvingTo(node of type java.lang.String, "java.lang.Object") returns true<br/>
* isNodeResolvingTo(node of type java.lang.String, "java.lang.Iterable") returns false<br/>
* </code>
*
* TODO Implement this method without using the recursive helper method if there are any
* performance/stackoverflow issues
*
* @param node an ASTNode
* @param signature a fully qualified type
* @return true if the type of the given ASTNode itself or one of its superclass/superinterfaces
* resolves to the given signature. false otherwise.
*/
private boolean isNodeResolvingTo(ASTNode node, String signature) {
if (signature == null || signature.trim().length() == 0) {
return true;
}
ITypeBinding tb= resolveNodeToBinding(node);
if (tb != null && tb.isPrimitive()) {
return (new String(tb.getQualifiedName()).equals(signature));
} else {
return resolvesReferenceBindingTo(tb, signature);
}
}
private boolean isBodyStatic() {
boolean isAnonymous= parentDeclaration.getNodeType() == ASTNode.ANONYMOUS_CLASS_DECLARATION;
boolean isStatic= Modifier.isStatic(bodyDeclaration.getModifiers()) && !isAnonymous;
return isStatic;
}
public void registerOutOfRangeOffset(TemplateVariable var, int absoluteOffset) {
if (variableOutOfRangeOffsets.get(var) == null) {
variableOutOfRangeOffsets.put(var, new int[] { absoluteOffset });
} else {
int[] temp= variableOutOfRangeOffsets.get(var);
int[] newArr= new int[temp.length + 1];
System.arraycopy(temp, 0, newArr, 0, temp.length);
newArr[temp.length]= absoluteOffset;
variableOutOfRangeOffsets.put(var, newArr);
}
}
private ITypeBinding resolveNodeToBinding(ASTNode node) {
if (node instanceof StringLiteral) {
return ((StringLiteral) node).resolveTypeBinding();
}
ITypeBinding[] res= new ITypeBinding[1];
node.accept(new ASTVisitor() {
@Override
public boolean visit(MethodInvocation n) {
res[0]= n.resolveTypeBinding();
return false;
}
@Override
public boolean visit(SimpleName n) {
IBinding b= n.resolveBinding();
if (b instanceof IVariableBinding) {
IVariableBinding vb= (IVariableBinding) b;
res[0]= vb.getType();
} else if (b instanceof IMethodBinding) {
IMethodBinding mb= (IMethodBinding) b;
res[0]= mb.getReturnType();
}
return false;
}
@Override
public boolean visit(QualifiedName n) {
IBinding b= n.resolveBinding();
if (b instanceof IVariableBinding) {
IVariableBinding vb= (IVariableBinding) b;
res[0]= vb.getType();
}
return false;
}
@Override
public boolean visit(FieldAccess n) {
ITypeBinding tmp= n.getName().resolveTypeBinding();
if (tmp != null) {
res[0]= tmp;
return false;
}
res[0]= n.getExpression().resolveTypeBinding();
return false;
}
@Override
public boolean visit(Assignment n) {
ITypeBinding tmp= n.getLeftHandSide().resolveTypeBinding();
if (tmp != null) {
res[0]= tmp;
return false;
}
return true;
}
@Override
public boolean visit(BooleanLiteral n) {
res[0]= n.resolveTypeBinding();
return false;
}
@Override
public boolean visit(InfixExpression n) {
res[0]= n.resolveTypeBinding();
return false;
}
@Override
public boolean visit(ClassInstanceCreation n) {
res[0]= n.resolveTypeBinding();
return false;
}
});
return res[0] != null ? res[0] : null;
}
private String resolveNodeToTypeString(ASTNode node) {
ITypeBinding b= resolveNodeToBinding(node);
if (b == null) {
return OBJECT_SIGNATURE;
}
String result= b.getQualifiedName();
if (result.isEmpty() && b.isCapture()) {
for (ITypeBinding tb : b.getTypeBounds()) {
result= tb.getQualifiedName();
if (!result.isEmpty()) {
return result;
}
}
}
return result;
}
/**
* This is a recursive method which performs a depth first search in the inheritance graph of
* the given {@link ITypeBinding}.
*
* @param sb a TypeBinding
* @param signature a fully qualified type
* @return <code>true</code> if the given TypeBinding itself or one of its
* superclass/superinterfaces resolves to the given signature, <code>false</code>
* otherwise.
*/
private boolean resolvesReferenceBindingTo(ITypeBinding sb, String signature) {
if (sb == null) {
return false;
}
if (new String(sb.getQualifiedName()).startsWith(signature) || (sb.isArray() && "array".equals(signature))) { //$NON-NLS-1$
return true;
}
if (Object.class.getName().equals(signature)) {
return true;
}
List<ITypeBinding> bindings= new ArrayList<>();
Collections.addAll(bindings, sb.getInterfaces());
bindings.add(sb.getSuperclass());
boolean result= false;
Iterator<ITypeBinding> it= bindings.iterator();
while (it.hasNext() && result == false) {
result= resolvesReferenceBindingTo(it.next(), signature);
}
return result;
}
public String[] suggestFieldName(String type, String[] excludes, boolean staticField, boolean finalField) throws IllegalArgumentException {
int dim= 0;
while (type.endsWith("[]")) { //$NON-NLS-1$
dim++;
type= type.substring(0, type.length() - 2);
}
IJavaProject project= getJavaProject();
int namingConventions= 0;
if (staticField && finalField) {
namingConventions= NamingConventions.VK_STATIC_FINAL_FIELD;
} else if (staticField && !finalField) {
namingConventions= NamingConventions.VK_STATIC_FIELD;
} else {
namingConventions= NamingConventions.VK_INSTANCE_FIELD;
}
if (project != null)
return StubUtility.getVariableNameSuggestions(namingConventions, project, type, dim, Arrays.asList(excludes), true);
return new String[] { Signature.getSimpleName(type).toLowerCase() };
}
public String[] suggestFieldName(String type, boolean finalField, boolean forceStatic) {
if (!domInitialized) {
initDomAST();
}
if (domInitialized) {
return suggestFieldName(type, ASTResolving.getUsedVariableNames(bodyDeclaration), (forceStatic) ? forceStatic : isBodyStatic(), finalField);
}
// If the dom is not initialized yet (template preview) we return a dummy name
return new String[] { "newField" }; //$NON-NLS-1$
}
}