/*******************************************************************************
* Copyright (c) 2000, 2011 IBM Corporation 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
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.ext.java.jdt.internal.corext.codemanipulation;
import org.eclipse.che.ide.ext.java.jdt.core.Flags;
import org.eclipse.che.ide.ext.java.jdt.core.NamingConventions;
import org.eclipse.che.ide.ext.java.jdt.core.Signature;
import org.eclipse.che.ide.ext.java.jdt.core.dom.AST;
import org.eclipse.che.ide.ext.java.jdt.core.dom.ASTNode;
import org.eclipse.che.ide.ext.java.jdt.core.dom.Assignment;
import org.eclipse.che.ide.ext.java.jdt.core.dom.Assignment.Operator;
import org.eclipse.che.ide.ext.java.jdt.core.dom.CastExpression;
import org.eclipse.che.ide.ext.java.jdt.core.dom.Expression;
import org.eclipse.che.ide.ext.java.jdt.core.dom.IMethodBinding;
import org.eclipse.che.ide.ext.java.jdt.core.dom.ITypeBinding;
import org.eclipse.che.ide.ext.java.jdt.core.dom.IVariableBinding;
import org.eclipse.che.ide.ext.java.jdt.core.dom.InfixExpression;
import org.eclipse.che.ide.ext.java.jdt.core.dom.MethodDeclaration;
import org.eclipse.che.ide.ext.java.jdt.core.dom.NumberLiteral;
import org.eclipse.che.ide.ext.java.jdt.core.dom.ParenthesizedExpression;
import org.eclipse.che.ide.ext.java.jdt.core.dom.PostfixExpression;
import org.eclipse.che.ide.ext.java.jdt.core.dom.PrefixExpression;
import org.eclipse.che.ide.ext.java.jdt.core.dom.PrimitiveType;
import org.eclipse.che.ide.ext.java.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.che.ide.ext.java.jdt.core.dom.TypeDeclaration;
import org.eclipse.che.ide.ext.java.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.che.ide.ext.java.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.che.ide.ext.java.jdt.internal.corext.dom.NecessaryParenthesesChecker;
import org.eclipse.che.ide.ext.java.jdt.internal.corext.util.JdtFlags;
import org.eclipse.che.ide.runtime.CoreException;
import java.util.List;
public class GetterSetterUtil {
private static final String[] EMPTY = new String[0];
//no instances
private GetterSetterUtil() {
}
public static String getGetterName(IVariableBinding field, String[] excludedNames) {
boolean useIs = StubUtility.useIsForBooleanGetters();
return getGetterName(field, excludedNames, useIs);
}
//
// private static String getGetterName(IField field, String[] excludedNames, boolean useIsForBoolGetters)
// {
// if (excludedNames == null)
// {
// excludedNames = EMPTY;
// }
// return getGetterName(field.getJavaProject(), field.getElementName(), field.getFlags(), useIsForBoolGetters
// && JavaModelUtil.isBoolean(field), excludedNames);
// }
public static String getGetterName(IVariableBinding variableType, String[] excludedNames, boolean isBoolean) {
boolean useIs = isBoolean(variableType) && isBoolean;
return getGetterName(variableType.getName(), variableType.getModifiers(), useIs, excludedNames);
}
public static String getGetterName(String fieldName, int flags, boolean isBoolean, String[] excludedNames) {
return NamingConventions.suggestGetterName(fieldName, flags, isBoolean, excludedNames);
}
public static String getSetterName(IVariableBinding variableType, String[] excludedNames, boolean isBoolean) {
return getSetterName(variableType.getName(), variableType.getModifiers(), isBoolean, excludedNames);
}
public static String getSetterName(String fieldName, int flags, boolean isBoolean, String[] excludedNames) {
boolean useIs = StubUtility.useIsForBooleanGetters();
return NamingConventions.suggestSetterName(fieldName, flags, useIs && isBoolean, excludedNames);
}
public static String getSetterName(IVariableBinding field, String[] excludedNames) {
if (excludedNames == null) {
excludedNames = EMPTY;
}
return getSetterName(field.getName(), field.getModifiers(), isBoolean(field), excludedNames);
}
/**
* Checks if the field is boolean.
*
* @param field
* the field
* @return returns <code>true</code> if the field returns a boolean
* @throws JavaModelException
* thrown when the field can not be accessed
*/
public static boolean isBoolean(IVariableBinding field) {
return field.getType().getBinaryName().equals(Signature.SIG_BOOLEAN);
}
public static IMethodBinding getGetter(IVariableBinding field) {
String getterName = getGetterName(field, EMPTY, true);
IMethodBinding primaryCandidate = findMethod(getterName, new String[0], false, field.getDeclaringClass());
if (!isBoolean(field) || (primaryCandidate != null))
return primaryCandidate;
//bug 30906 describes why we need to look for other alternatives here (try with get... for booleans)
String secondCandidateName = getGetterName(field, EMPTY, false);
return findMethod(secondCandidateName, new String[0], false, field.getDeclaringClass());
}
public static IMethodBinding getSetter(IVariableBinding field) {
String[] args = new String[]{field.getType().getKey().replaceAll("/", ".")};
return findMethod(getSetterName(field, EMPTY), args, false, field.getDeclaringClass());
}
/**
* Finds a method in a type.
* This searches for a method with the same name and signature. Parameter types are only
* compared by the simple name, no resolving for the fully qualified type name is done.
* Constructors are only compared by parameters, not the name.
*
* @param name
* The name of the method to find
* @param paramTypes
* The type signatures of the parameters e.g. <code>{"QString;","I"}</code>
* @param isConstructor
* If the method is a constructor
* @param type
* the type
* @return The first found method or <code>null</code>, if nothing foun
* @throws JavaModelException
* thrown when the type can not be accessed
*/
public static IMethodBinding findMethod(String name, String[] paramTypes, boolean isConstructor, ITypeBinding type) {
IMethodBinding[] methods = type.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
if (isSameMethodSignature(name, paramTypes, isConstructor, methods[i])) {
return methods[i];
}
}
return null;
}
/**
* Finds a method in a type.
* This searches for a method with the same name and signature. Parameter types are only
* compared by the simple name, no resolving for the fully qualified type name is done.
* Constructors are only compared by parameters, not the name.
*
* @param name
* The name of the method to find
* @param paramTypes
* The type signatures of the parameters e.g. <code>{"QString;","I"}</code>
* @param isConstructor
* If the method is a constructor
* @param type
* the type
* @return The first found method or <code>null</code>, if nothing foun
* @throws JavaModelException
* thrown when the type can not be accessed
*/
public static MethodDeclaration findMethod(String name, String[] paramTypes, boolean isConstructor, TypeDeclaration type) {
MethodDeclaration[] methods = type.getMethods();
for (int i = 0; i < methods.length; i++) {
if (isSameMethodSignature(name, paramTypes, isConstructor, methods[i])) {
return methods[i];
}
}
return null;
}
/**
* Tests if a method equals to the given signature.
* Parameter types are only compared by the simple name, no resolving for
* the fully qualified type name is done. Constructors are only compared by
* parameters, not the name.
*
* @param name
* Name of the method
* @param paramTypes
* The type signatures of the parameters e.g. <code>{"QString;","I"}</code>
* @param isConstructor
* Specifies if the method is a constructor
* @param curr
* the method
* @return Returns <code>true</code> if the method has the given name and parameter types and constructor state.
* @throws JavaModelException
* thrown when the method can not be accessed
*/
public static boolean isSameMethodSignature(String name, String[] paramTypes, boolean isConstructor,
IMethodBinding curr) {
if (isConstructor || name.equals(curr.getName())) {
if (isConstructor == curr.isConstructor()) {
ITypeBinding[] currParamTypes = curr.getParameterTypes();
if (paramTypes.length == currParamTypes.length) {
for (int i = 0; i < paramTypes.length; i++) {
String t1 = Signature.getSimpleName(Signature.toString(paramTypes[i]));
String t2 = Signature.getSimpleName(currParamTypes[i].getQualifiedName());
if (!t1.equals(t2)) {
return false;
}
}
return true;
}
}
}
return false;
}
/**
* Tests if a method equals to the given signature.
* Parameter types are only compared by the simple name, no resolving for
* the fully qualified type name is done. Constructors are only compared by
* parameters, not the name.
*
* @param name
* Name of the method
* @param paramTypes
* The type signatures of the parameters e.g. <code>{"QString;","I"}</code>
* @param isConstructor
* Specifies if the method is a constructor
* @param curr
* the method
* @return Returns <code>true</code> if the method has the given name and parameter types and constructor state.
* @throws JavaModelException
* thrown when the method can not be accessed
*/
public static boolean isSameMethodSignature(String name, String[] paramTypes, boolean isConstructor,
MethodDeclaration curr) {
if (isConstructor || name.equals(curr.getName())) {
if (isConstructor == curr.isConstructor()) {
List<SingleVariableDeclaration> currParamTypes = curr.parameters();
if (paramTypes.length == currParamTypes.size()) {
for (int i = 0; i < paramTypes.length; i++) {
String t1 = Signature.getSimpleName(Signature.toString(paramTypes[i]));
String t2 = Signature.getSimpleName(currParamTypes.get(i).getName().getFullyQualifiedName());
if (!t1.equals(t2)) {
return false;
}
}
return true;
}
}
}
return false;
}
/**
* Create a stub for a getter of the given field using getter/setter templates. The resulting code
* has to be formatted and indented.
*
* @param field
* The field to create a getter for
* @param setterName
* The chosen name for the setter
* @param addComments
* If <code>true</code>, comments will be added.
* @param flags
* The flags signaling visibility, if static, synchronized or final
* @return Returns the generated stub.
* @throws CoreException
* when stub creation failed
*/
public static String getSetterStub(IVariableBinding field, String setterName, boolean addComments, int flags)
throws CoreException {
String fieldName = field.getName();
ITypeBinding parentType = field.getDeclaringClass();
String returnSig = field.getType().getKey().replaceAll("/", ".");
String typeName = Signature.getSimpleName(Signature.toString(returnSig));
String accessorName = StubUtility.getBaseName(field);
String argname = StubUtility.suggestArgumentName(accessorName, EMPTY);
boolean isStatic = Flags.isStatic(flags);
boolean isSync = Flags.isSynchronized(flags);
boolean isFinal = Flags.isFinal(flags);
String lineDelim = "\n"; // Use default line delimiter, as generated stub has to be formatted anyway //$NON-NLS-1$
StringBuffer buf = new StringBuffer();
if (addComments) {
String comment =
StubUtility.getSetterComment(parentType.getQualifiedName(), setterName, field.getName(), typeName, argname,
accessorName, lineDelim);
if (comment != null) {
buf.append(comment);
buf.append(lineDelim);
}
}
buf.append(JdtFlags.getVisibilityString(flags));
buf.append(' ');
if (isStatic)
buf.append("static "); //$NON-NLS-1$
if (isSync)
buf.append("synchronized "); //$NON-NLS-1$
if (isFinal)
buf.append("final "); //$NON-NLS-1$
buf.append("void "); //$NON-NLS-1$
buf.append(setterName);
buf.append('(');
buf.append(typeName);
buf.append(' ');
buf.append(argname);
buf.append(") {"); //$NON-NLS-1$
buf.append(lineDelim);
boolean useThis = StubUtility.useThisForFieldAccess();
if (argname.equals(fieldName) || (useThis && !isStatic)) {
if (isStatic)
fieldName = parentType.getName() + '.' + fieldName;
else
fieldName = "this." + fieldName; //$NON-NLS-1$
}
String body =
StubUtility.getSetterMethodBodyContent(parentType.getQualifiedName(), setterName, fieldName, argname,
lineDelim);
if (body != null) {
buf.append(body);
}
buf.append("}"); //$NON-NLS-1$
buf.append(lineDelim);
return buf.toString();
}
/**
* Create a stub for a getter of the given field using getter/setter templates. The resulting code
* has to be formatted and indented.
*
* @param field
* The field to create a getter for
* @param getterName
* The chosen name for the getter
* @param addComments
* If <code>true</code>, comments will be added.
* @param flags
* The flags signaling visibility, if static, synchronized or final
* @return Returns the generated stub.
* @throws CoreException
* when stub creation failed
*/
public static String getGetterStub(IVariableBinding field, String getterName, boolean addComments, int flags)
throws CoreException {
String fieldName = field.getName();
ITypeBinding parentType = field.getDeclaringClass();
boolean isStatic = Flags.isStatic(flags);
boolean isSync = Flags.isSynchronized(flags);
boolean isFinal = Flags.isFinal(flags);
String typeName = Signature.getSimpleName(Signature.toString(field.getType().getKey().replaceAll("/", ".")));
String accessorName = StubUtility.getBaseName(field);
String lineDelim = "\n"; // Use default line delimiter, as generated stub has to be formatted anyway //$NON-NLS-1$
StringBuffer buf = new StringBuffer();
if (addComments) {
String comment =
StubUtility.getGetterComment(parentType.getQualifiedName(), getterName, field.getName(), typeName,
accessorName, lineDelim);
if (comment != null) {
buf.append(comment);
buf.append(lineDelim);
}
}
buf.append(JdtFlags.getVisibilityString(flags));
buf.append(' ');
if (isStatic)
buf.append("static "); //$NON-NLS-1$
if (isSync)
buf.append("synchronized "); //$NON-NLS-1$
if (isFinal)
buf.append("final "); //$NON-NLS-1$
buf.append(typeName);
buf.append(' ');
buf.append(getterName);
buf.append("() {"); //$NON-NLS-1$
buf.append(lineDelim);
boolean useThis = StubUtility.useThisForFieldAccess();
if (useThis && !isStatic) {
fieldName = "this." + fieldName; //$NON-NLS-1$
}
String body =
StubUtility.getGetterMethodBodyContent(parentType.getQualifiedName(), getterName, fieldName, lineDelim);
if (body != null) {
buf.append(body);
}
buf.append("}"); //$NON-NLS-1$
buf.append(lineDelim);
return buf.toString();
}
/**
* Converts an assignment, postfix expression or prefix expression into an assignable equivalent expression using the getter.
*
* @param node
* the assignment/prefix/postfix node
* @param astRewrite
* the astRewrite to use
* @param getterExpression
* the expression to insert for read accesses or <code>null</code> if such an expression does not exist
* @param variableType
* the type of the variable that the result will be assigned to
* @param is50OrHigher
* <code>true</code> if a 5.0 or higher environment can be used
* @return an expression that can be assigned to the type variableType with node being replaced by a equivalent expression using the
* getter
*/
public static Expression getAssignedValue(ASTNode node, ASTRewrite astRewrite, Expression getterExpression,
ITypeBinding variableType, boolean is50OrHigher) {
InfixExpression.Operator op = null;
AST ast = astRewrite.getAST();
if (isNotInBlock(node))
return null;
if (node.getNodeType() == ASTNode.ASSIGNMENT) {
Assignment assignment = ((Assignment)node);
Expression rightHandSide = assignment.getRightHandSide();
Expression copiedRightOp = (Expression)astRewrite.createCopyTarget(rightHandSide);
if (assignment.getOperator() == Operator.ASSIGN) {
ITypeBinding rightHandSideType = rightHandSide.resolveTypeBinding();
copiedRightOp =
createNarrowCastIfNessecary(copiedRightOp, rightHandSideType, ast, variableType, is50OrHigher);
return copiedRightOp;
}
if (getterExpression != null) {
InfixExpression infix = ast.newInfixExpression();
infix.setLeftOperand(getterExpression);
infix.setOperator(ASTNodes.convertToInfixOperator(assignment.getOperator()));
ITypeBinding infixType = infix.resolveTypeBinding();
if (NecessaryParenthesesChecker.needsParentheses(copiedRightOp, infix,
InfixExpression.RIGHT_OPERAND_PROPERTY)) {
//TODO: this introduces extra parentheses as the new "infix" node doesn't have bindings
ParenthesizedExpression p = ast.newParenthesizedExpression();
p.setExpression(copiedRightOp);
copiedRightOp = p;
}
infix.setRightOperand(copiedRightOp);
return createNarrowCastIfNessecary(infix, infixType, ast, variableType, is50OrHigher);
}
} else if (node.getNodeType() == ASTNode.POSTFIX_EXPRESSION) {
PostfixExpression po = (PostfixExpression)node;
if (po.getOperator() == PostfixExpression.Operator.INCREMENT)
op = InfixExpression.Operator.PLUS;
if (po.getOperator() == PostfixExpression.Operator.DECREMENT)
op = InfixExpression.Operator.MINUS;
} else if (node.getNodeType() == ASTNode.PREFIX_EXPRESSION) {
PrefixExpression pe = (PrefixExpression)node;
if (pe.getOperator() == PrefixExpression.Operator.INCREMENT)
op = InfixExpression.Operator.PLUS;
if (pe.getOperator() == PrefixExpression.Operator.DECREMENT)
op = InfixExpression.Operator.MINUS;
}
if (op != null && getterExpression != null) {
return createInfixInvocationFromPostPrefixExpression(op, getterExpression, ast, variableType, is50OrHigher);
}
return null;
}
/*
* Check if the node is in a block. We don't want to update declarations
*/
private static boolean isNotInBlock(ASTNode parent) {
ASTNode statement = parent.getParent();
boolean isStatement = statement.getNodeType() != ASTNode.EXPRESSION_STATEMENT;
ASTNode block = statement.getParent();
boolean isBlock = block.getNodeType() == ASTNode.BLOCK || block.getNodeType() == ASTNode.SWITCH_STATEMENT;
boolean isControlStatemenBody = ASTNodes.isControlStatementBody(statement.getLocationInParent());
return isStatement || !(isBlock || isControlStatemenBody);
}
private static Expression createInfixInvocationFromPostPrefixExpression(InfixExpression.Operator operator,
Expression getterExpression, AST ast, ITypeBinding variableType,
boolean is50OrHigher) {
InfixExpression infix = ast.newInfixExpression();
infix.setLeftOperand(getterExpression);
infix.setOperator(operator);
NumberLiteral number = ast.newNumberLiteral();
number.setToken("1"); //$NON-NLS-1$
infix.setRightOperand(number);
ITypeBinding infixType = infix.resolveTypeBinding();
return createNarrowCastIfNessecary(infix, infixType, ast, variableType, is50OrHigher);
}
/**
* Checks if the assignment needs a downcast and inserts it if necessary
*
* @param expression
* the right hand-side
* @param expressionType
* the type of the right hand-side. Can be null
* @param ast
* the AST
* @param variableType
* the Type of the variable the expression will be assigned to
* @param is50OrHigher
* if <code>true</code> java 5.0 code will be assumed
* @return the casted expression if necessary
*/
private static Expression createNarrowCastIfNessecary(Expression expression, ITypeBinding expressionType, AST ast,
ITypeBinding variableType, boolean is50OrHigher) {
PrimitiveType castTo = null;
if (variableType.isEqualTo(expressionType))
return expression; //no cast for same type
if (is50OrHigher) {
if (ast.resolveWellKnownType("java.lang.Character").isEqualTo(variableType)) //$NON-NLS-1$
castTo = ast.newPrimitiveType(PrimitiveType.CHAR);
if (ast.resolveWellKnownType("java.lang.Byte").isEqualTo(variableType)) //$NON-NLS-1$
castTo = ast.newPrimitiveType(PrimitiveType.BYTE);
if (ast.resolveWellKnownType("java.lang.Short").isEqualTo(variableType)) //$NON-NLS-1$
castTo = ast.newPrimitiveType(PrimitiveType.SHORT);
}
if (ast.resolveWellKnownType("char").isEqualTo(variableType)) //$NON-NLS-1$
castTo = ast.newPrimitiveType(PrimitiveType.CHAR);
if (ast.resolveWellKnownType("byte").isEqualTo(variableType)) //$NON-NLS-1$
castTo = ast.newPrimitiveType(PrimitiveType.BYTE);
if (ast.resolveWellKnownType("short").isEqualTo(variableType)) //$NON-NLS-1$
castTo = ast.newPrimitiveType(PrimitiveType.SHORT);
if (castTo != null) {
CastExpression cast = ast.newCastExpression();
if (NecessaryParenthesesChecker.needsParentheses(expression, cast, CastExpression.EXPRESSION_PROPERTY)) {
ParenthesizedExpression parenthesized = ast.newParenthesizedExpression();
parenthesized.setExpression(expression);
cast.setExpression(parenthesized);
} else
cast.setExpression(expression);
cast.setType(castTo);
return cast;
}
return expression;
}
}