| /******************************************************************************* |
| * 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$ |
| } |
| } |