blob: 0906d1f72a293d21d664fd4da2df5260bfa0d836 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2019 Mateusz Matela and others.
*
* 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:
* Mateusz Matela <mateusz.matela@gmail.com> - [code manipulation] [dcr] toString() builder wizard - https://bugs.eclipse.org/bugs/show_bug.cgi?id=26070
* Mateusz Matela <mateusz.matela@gmail.com> - [toString] Generator uses wrong suffixes and prefixes - https://bugs.eclipse.org/bugs/show_bug.cgi?id=275370
* Red Hat Inc. - moved to jdt.core.manipulation
*******************************************************************************/
package org.eclipse.jdt.internal.corext.codemanipulation.tostringgeneration;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.jdt.core.NamingConventions;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ArrayAccess;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.CastExpression;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.ConditionalExpression;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.ForStatement;
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.IfStatement;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.InfixExpression.Operator;
import org.eclipse.jdt.core.dom.InstanceofExpression;
import org.eclipse.jdt.core.dom.Javadoc;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.ParenthesizedExpression;
import org.eclipse.jdt.core.dom.PostfixExpression;
import org.eclipse.jdt.core.dom.PrimitiveType;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.SuperMethodInvocation;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.VariableDeclarationExpression;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.core.manipulation.CodeGeneration;
import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility2Core;
import org.eclipse.jdt.internal.core.manipulation.StubUtility;
import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory;
/**
* <p>
* This generator creates an implementation of <code>{@link java.lang.Object#toString()}</code>
* which lists all selected fields and methods. What exactly is listed and how members are separated
* is determined by a format template.
* <p>
*
* <p>
* To print out items of arrays and/or limit number items printed for arrays, collections and so on,
* various methods are used according to actual jdk compatibility:
* <table border="10">
* <tr>
* <td></td>
* <th><code>java.util.List</code></th>
* <th><code>java.util.Collection</code></th>
* <th><code>java.util.Map</code></th>
* <th>Array of primitive types</th>
* <th>Array of non-primitive types</th>
* </tr>
* <tr>
* <th>jdk 1.4</th>
* <td>-</td>
* <td>-</td>
* <td>-</td>
* <td>helper method <code>arrayToString(Object array, int length)</code></td>
* <td><code>Arrays.asList(member)</code></td>
* </tr>
* <tr>
* <th>jdk 1.4/1.5, limit elements</th>
* <td><code>member.subList()</code></td>
* <td>helper method <code>toSting(Collection)</code></td>
* <td>helper method <code>toString(Collection)</code> with <code>member.entrySet()</code></td>
* <td>helper method <code>arrayToString(Object array, int length)</code></td>
* <td><code>Arrays.asList(member).subList()</code></td>
* </tr>
* <tr>
* <th>jdk 1.5</th>
* <td>-</td>
* <td>-</td>
* <td>-</td>
* <td><code>Arrays.toString()<code></td>
* <td><code>Arrays.asList(member)</code></td>
* </tr>
* <tr>
* <th>jdk 1.6</th>
* <td>-</td>
* <td>-</td>
* <td>-</td>
* <td><code>Arrays.toString()</code></td>
* <td><code>Arrays.toString()</code></td>
* </tr>
* <tr>
* <th>jdk 1.6, limit elements</th>
* <td><code>member.subList()</code></td>
* <td>helper method <code>toSting(Collection)</code></td>
* <td>helper method <code>toString(Collection)</code> with <code>member.entrySet()</code></td>
* <td><code>Arrays.toString(Arrays.copyOf(member, ...))</code></td>
* <td><code>Arrays.asList(member).subList()</code></td>
* </tr>
* </table>
* Additionally, if helper method is generated it is also used for other members members (even if
* other solution could be used), as it makes the code cleaner.
* </p>
*
* @since 3.5
*/
public abstract class AbstractToStringGenerator {
protected static final String METHODNAME_TO_STRING= "toString"; //$NON-NLS-1$
protected static final String TYPENAME_STRING= "String"; //$NON-NLS-1$
final protected String HELPER_TOSTRING_METHOD_NAME= "toString"; //$NON-NLS-1$
final private String HELPER_ARRAYTOSTRING_METHOD_NAME= "arrayToString"; //$NON-NLS-1$
final private String MAX_LEN_VARIABLE_NAME= "maxLen"; //$NON-NLS-1$
protected String fMaxLenVariableName= MAX_LEN_VARIABLE_NAME;
/**
* The name of the property that every <code>MethodDeclaration</code> generated should have.
* This property determines whether the method should be overwritten if already exists. The data
* of this property is a Boolean.
*/
protected final static String OVERWRITE_METHOD_PROPERTY= "override_method"; //$NON-NLS-1$
public ToStringTemplateParser getTemplateParser() {
return new ToStringTemplateParser();
}
protected ToStringGenerationContext fContext;
/** The ast to be used. Convenience accessor field */
protected AST fAst;
protected MethodDeclaration toStringMethod;
protected boolean needMaxLenVariable;
protected boolean needCollectionToStringMethod;
protected List<ITypeBinding> typesThatNeedArrayToStringMethod;
public RefactoringStatus checkConditions() {
return new RefactoringStatus();
}
/**
* This method is an implementation of Director in Builder pattern. It goes through all elements
* of the format template and calls methods responsible for processing them.
*
* @return declaration of the generated <code>toString()</code> method
* @throws CoreException if creation failed
*/
public MethodDeclaration generateToStringMethod() throws CoreException {
initialize();
String[] stringArray= fContext.getTemplateParser().getBeginning();
for (int i= 0; i < stringArray.length; i++) {
addElement(processElement(stringArray[i], null));
}
stringArray= fContext.getTemplateParser().getBody();
Object[] members= fContext.getSelectedMembers();
for (int i= 0; i < members.length; i++) {
if (!fContext.isSkipNulls() || getMemberType(members[i]).isPrimitive())
addMember(members[i], i != members.length - 1);
else
addMemberCheckNull(members[i], i != members.length - 1);
}
stringArray= fContext.getTemplateParser().getEnding();
for (int i= 0; i < stringArray.length; i++) {
addElement(processElement(stringArray[i], null));
}
complete();
return toStringMethod;
}
public List<MethodDeclaration> generateHelperMethods() {
List<MethodDeclaration> result= new ArrayList<>();
if (needCollectionToStringMethod)
result.add(createHelperToStringMethod(false));
if (!typesThatNeedArrayToStringMethod.isEmpty())
result.add(createHelperToStringMethod(true));
return result;
}
/**
* adds a comment (if necessary) and an <code>@Override</code> annotation to the generated
* method
*
* @throws CoreException if creation failed
*/
protected void createMethodComment() throws CoreException {
ITypeBinding object= fAst.resolveWellKnownType("java.lang.Object"); //$NON-NLS-1$
IMethodBinding[] objms= object.getDeclaredMethods();
IMethodBinding objectMethod= null;
for (int i= 0; i < objms.length; i++) {
if (objms[i].getName().equals(METHODNAME_TO_STRING) && objms[i].getParameterTypes().length == 0)
objectMethod= objms[i];
}
if (fContext.isCreateComments()) {
String docString= CodeGeneration.getMethodComment(fContext.getCompilationUnit(), fContext.getTypeBinding().getQualifiedName(), toStringMethod, objectMethod, StubUtility
.getLineDelimiterUsed(fContext.getCompilationUnit()));
if (docString != null) {
Javadoc javadoc= (Javadoc)fContext.getASTRewrite().createStringPlaceholder(docString, ASTNode.JAVADOC);
toStringMethod.setJavadoc(javadoc);
}
}
StubUtility2Core.addOverrideAnnotation(fContext.getCodeGenerationSettings(), fContext.getTypeBinding().getJavaElement().getJavaProject(), fContext.getASTRewrite(), fContext.getImportRewrite(), toStringMethod, objectMethod.getDeclaringClass().isInterface(), null);
}
/**
* Creates a method that takes a <code>Collection</code> or an Array and returns a
* <code>String</code> containing it's first <code>fSettings.limitValue</code> elements
*
* @param array if true, generated method will convert array to string, otherwise -
* <code>Collection</code>
* @return <code>arrayToString(Object[] array)</code> or </code>collectionToString(Collection
* collection)</code> method
*/
protected MethodDeclaration createHelperToStringMethod(boolean array) {
final String iteratorName= createNameSuggestion("iterator", NamingConventions.VK_LOCAL); //$NON-NLS-1$
final String appendMethodName= "append"; //$NON-NLS-1$
final String indexName= createNameSuggestion("i", NamingConventions.VK_LOCAL); //$NON-NLS-1$
final String lengthParamName= createNameSuggestion("len", NamingConventions.VK_PARAMETER); //$NON-NLS-1$
final String maxLenParamName= createNameSuggestion(MAX_LEN_VARIABLE_NAME, NamingConventions.VK_PARAMETER);
String paramName;
String stringBuilderName;
String stringBuilderTypeName;
if (fContext.is50orHigher()) {
stringBuilderTypeName= "java.lang.StringBuilder"; //$NON-NLS-1$
stringBuilderName= createNameSuggestion("builder", NamingConventions.VK_LOCAL); //$NON-NLS-1$
} else {
stringBuilderTypeName= "java.lang.StringBuffer"; //$NON-NLS-1$
stringBuilderName= createNameSuggestion("buffer", NamingConventions.VK_LOCAL); //$NON-NLS-1$
}
//private arrayToString() {
MethodDeclaration arrayToStringMethod= fAst.newMethodDeclaration();
arrayToStringMethod.setName(fAst.newSimpleName(array ? HELPER_ARRAYTOSTRING_METHOD_NAME : HELPER_TOSTRING_METHOD_NAME));
arrayToStringMethod.modifiers().add(fAst.newModifier(Modifier.ModifierKeyword.PRIVATE_KEYWORD));
arrayToStringMethod.setReturnType2(fAst.newSimpleType(fAst.newName(TYPENAME_STRING)));
if (array) {
//(Objec array, int length, ...)
paramName= createNameSuggestion("array", NamingConventions.VK_PARAMETER); //$NON-NLS-1$
SingleVariableDeclaration param= fAst.newSingleVariableDeclaration();
param.setType(fAst.newSimpleType(addImport("java.lang.Object"))); //$NON-NLS-1$
param.setName(fAst.newSimpleName(paramName));
arrayToStringMethod.parameters().add(param);
param= fAst.newSingleVariableDeclaration();
param.setType(fAst.newPrimitiveType(PrimitiveType.INT));
param.setName(fAst.newSimpleName(lengthParamName));
arrayToStringMethod.parameters().add(param);
} else {
//(Collection<?> collection, ...)
paramName= createNameSuggestion("collection", NamingConventions.VK_PARAMETER); //$NON-NLS-1$
SingleVariableDeclaration param= fAst.newSingleVariableDeclaration();
Type collectionType= fAst.newSimpleType(addImport("java.util.Collection")); //$NON-NLS-1$
if (fContext.is50orHigher()) {
ParameterizedType genericType= fAst.newParameterizedType(collectionType);
genericType.typeArguments().add(fAst.newWildcardType());
param.setType(genericType);
} else {
param.setType(collectionType);
}
param.setName(fAst.newSimpleName(paramName));
arrayToStringMethod.parameters().add(param);
}
if (fContext.isLimitItems()) {
SingleVariableDeclaration param= fAst.newSingleVariableDeclaration();
param.setType(fAst.newPrimitiveType(PrimitiveType.INT));
param.setName(fAst.newSimpleName(maxLenParamName));
arrayToStringMethod.parameters().add(param);
}
Block body= fAst.newBlock();
arrayToStringMethod.setBody(body);
//StringBuilder stringBuilder= new StringBuilder();
VariableDeclarationFragment fragment= fAst.newVariableDeclarationFragment();
fragment.setName(fAst.newSimpleName(stringBuilderName));
ClassInstanceCreation classInstance= fAst.newClassInstanceCreation();
classInstance.setType(fAst.newSimpleType(addImport(stringBuilderTypeName)));
fragment.setInitializer(classInstance);
VariableDeclarationStatement vStatement= fAst.newVariableDeclarationStatement(fragment);
vStatement.setType(fAst.newSimpleType(addImport(stringBuilderTypeName)));
body.statements().add(vStatement);
if (array && fContext.isLimitItems()) {
//length = Math.min(length, maxLen);
MethodInvocation minInvocation= createMethodInvocation(addImport("java.lang.Math"), "min", fAst.newSimpleName(lengthParamName)); //$NON-NLS-1$ //$NON-NLS-2$
minInvocation.arguments().add(fAst.newSimpleName(maxLenParamName));
Assignment lengthAssignment= fAst.newAssignment();
lengthAssignment.setLeftHandSide(fAst.newSimpleName(lengthParamName));
lengthAssignment.setRightHandSide(minInvocation);
body.statements().add(fAst.newExpressionStatement(lengthAssignment));
}
//stringBuilder.add("[");
StringLiteral literal= fAst.newStringLiteral();
literal.setLiteralValue("["); //$NON-NLS-1$
body.statements().add(fAst.newExpressionStatement(createMethodInvocation(stringBuilderName, appendMethodName, literal)));
//for(...
ForStatement forStatement= fAst.newForStatement();
Block forBlock= fAst.newBlock();
forStatement.setBody(forBlock);
//int i = 0;
VariableDeclarationFragment indexDeclFragment= fAst.newVariableDeclarationFragment();
indexDeclFragment.setName(fAst.newSimpleName(indexName));
indexDeclFragment.setInitializer(fAst.newNumberLiteral("0")); //$NON-NLS-1$
VariableDeclarationExpression indexDeclExpression= fAst.newVariableDeclarationExpression(indexDeclFragment);
indexDeclExpression.setType(fAst.newPrimitiveType(PrimitiveType.INT));
// i++
PostfixExpression postfixExpr= fAst.newPostfixExpression();
postfixExpr.setOperand(fAst.newSimpleName(indexName));
postfixExpr.setOperator(org.eclipse.jdt.core.dom.PostfixExpression.Operator.INCREMENT);
forStatement.updaters().add(postfixExpr);
//if (i > 0) builder.append(", ");
IfStatement ifStatement= fAst.newIfStatement();
ifStatement.setExpression(createInfixExpression(fAst.newSimpleName(indexName), Operator.GREATER, fAst.newNumberLiteral(String.valueOf(0))));
literal= fAst.newStringLiteral();
literal.setLiteralValue(", "); //$NON-NLS-1$
ifStatement.setThenStatement(createOneStatementBlock(createMethodInvocation(stringBuilderName, appendMethodName, literal)));
forBlock.statements().add(ifStatement);
if (array) {
forStatement.initializers().add(indexDeclExpression);
// i < length;
forStatement.setExpression(createInfixExpression(fAst.newSimpleName(indexName), Operator.LESS, fAst.newSimpleName(lengthParamName)));
for (Iterator<ITypeBinding> iterator= typesThatNeedArrayToStringMethod.iterator(); iterator.hasNext();) {
ITypeBinding typeBinding= iterator.next();
//if (array instanceof int[]) {
String typeName= typeBinding.getName();
PrimitiveType.Code code= null;
if (typeName.equals("byte")) code= PrimitiveType.BYTE; //$NON-NLS-1$
if (typeName.equals("short")) code= PrimitiveType.SHORT; //$NON-NLS-1$
if (typeName.equals("char")) code= PrimitiveType.CHAR; //$NON-NLS-1$
if (typeName.equals("int")) code= PrimitiveType.INT; //$NON-NLS-1$
if (typeName.equals("long")) code= PrimitiveType.LONG; //$NON-NLS-1$
if (typeName.equals("float")) code= PrimitiveType.FLOAT; //$NON-NLS-1$
if (typeName.equals("double")) code= PrimitiveType.DOUBLE; //$NON-NLS-1$
if (typeName.equals("boolean")) code= PrimitiveType.BOOLEAN; //$NON-NLS-1$
if (code == null && !typeName.equals("Object"))continue; //$NON-NLS-1$
InstanceofExpression instanceOf= fAst.newInstanceofExpression();
instanceOf.setLeftOperand(fAst.newSimpleName(paramName));
instanceOf.setRightOperand(fAst.newArrayType(code != null ? (Type)fAst.newPrimitiveType(code) : fAst.newSimpleType(addImport("java.lang.Object")))); //$NON-NLS-1$
ifStatement= fAst.newIfStatement();
ifStatement.setExpression(instanceOf);
//builder.append(((int[]) array)[i]);
CastExpression arrayCast= fAst.newCastExpression();
arrayCast.setExpression(fAst.newSimpleName(paramName));
arrayCast.setType(fAst.newArrayType(code != null ? (Type)fAst.newPrimitiveType(code) : fAst.newSimpleType(addImport("java.lang.Object")))); //$NON-NLS-1$
ParenthesizedExpression parenthesizedCast= fAst.newParenthesizedExpression();
parenthesizedCast.setExpression(arrayCast);
ArrayAccess arrayAccess= fAst.newArrayAccess();
arrayAccess.setArray(parenthesizedCast);
arrayAccess.setIndex(fAst.newSimpleName(indexName));
ifStatement.setThenStatement(createOneStatementBlock(createMethodInvocation(stringBuilderName, appendMethodName, arrayAccess)));
forBlock.statements().add(ifStatement);
}
} else {
body.statements().add(fAst.newExpressionStatement(indexDeclExpression));
//... Iterator iterator= collection.iterator() ...
fragment= fAst.newVariableDeclarationFragment();
fragment.setName(fAst.newSimpleName(iteratorName));
fragment.setInitializer(createMethodInvocation(paramName, "iterator", null)); //$NON-NLS-1$
VariableDeclarationExpression vExpression= fAst.newVariableDeclarationExpression(fragment);
SimpleType iteratorType= fAst.newSimpleType(addImport("java.util.Iterator")); //$NON-NLS-1$
if (fContext.is50orHigher()) {
ParameterizedType pType= fAst.newParameterizedType(iteratorType);
pType.typeArguments().add(fAst.newWildcardType());
vExpression.setType(pType);
} else {
vExpression.setType(iteratorType);
}
forStatement.initializers().add(vExpression);
//iterator.hasNext() && i < maxLen;
Expression indexExpression= createInfixExpression(fAst.newSimpleName(indexName), Operator.LESS, fAst.newSimpleName(maxLenParamName));
forStatement.setExpression(createInfixExpression(createMethodInvocation(iteratorName, "hasNext", null), Operator.CONDITIONAL_AND, indexExpression)); //$NON-NLS-1$
//if (i > 0)
//stringBuilder.append(iterator.next());
MethodInvocation nextInvocation= createMethodInvocation(iteratorName, "next", null); //$NON-NLS-1$
forBlock.statements().add(fAst.newExpressionStatement(createMethodInvocation(stringBuilderName, appendMethodName, nextInvocation)));
}
body.statements().add(forStatement);
//stringBuilder.add("]");
literal= fAst.newStringLiteral();
literal.setLiteralValue("]"); //$NON-NLS-1$
body.statements().add(fAst.newExpressionStatement(createMethodInvocation(stringBuilderName, appendMethodName, literal)));
//return stringBuilder.toString();
ReturnStatement returnStatement= fAst.newReturnStatement();
returnStatement.setExpression(createMethodInvocation(stringBuilderName, "toString", null)); //$NON-NLS-1$
body.statements().add(returnStatement);
arrayToStringMethod.setProperty(OVERWRITE_METHOD_PROPERTY, Boolean.valueOf(array));
return arrayToStringMethod;
}
/**
* This method initializes all variables used in the process of generating <code>toString</code>
* method.
*/
protected void initialize() {
needMaxLenVariable= false;
needCollectionToStringMethod= false;
typesThatNeedArrayToStringMethod= new ArrayList<>();
checkNeedForHelperMethods();
toStringMethod= fAst.newMethodDeclaration();
toStringMethod.modifiers().addAll(ASTNodeFactory.newModifiers(fAst, Modifier.PUBLIC));
toStringMethod.setName(fAst.newSimpleName(METHODNAME_TO_STRING));
toStringMethod.setConstructor(false);
toStringMethod.setReturnType2(fAst.newSimpleType(fAst.newName(TYPENAME_STRING)));
Block body= fAst.newBlock();
toStringMethod.setBody(body);
fMaxLenVariableName= createNameSuggestion(MAX_LEN_VARIABLE_NAME, NamingConventions.VK_LOCAL);
}
/**
* This method is called at the end of the process of generating <code>toString</code> method.
* It should make sure the processed properly and clean the environment.
*
* @throws CoreException if creation failed
*/
protected void complete() throws CoreException {
if (needMaxLenVariable) {
toStringMethod.getBody().statements().add(0, createMaxLenDeclaration());
}
createMethodComment();
toStringMethod.setProperty(OVERWRITE_METHOD_PROPERTY, Boolean.valueOf(true));
}
/**
* Iterates over selected members to determine whether helper methods will be needed.
*/
protected void checkNeedForHelperMethods() {
if ((!fContext.isLimitItems() && !fContext.isCustomArray()) || (fContext.isLimitItems() && fContext.getLimitItemsValue() == 0))
return;
boolean isNonPrimitive= false;
for (int i= 0; i < fContext.getSelectedMembers().length; i++) {
ITypeBinding memberType= getMemberType(fContext.getSelectedMembers()[i]);
boolean[] implementsInterfaces= implementsInterfaces(memberType.getErasure(), new String[] { "java.util.Collection", "java.util.List", "java.util.Map" }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
boolean isCollection= implementsInterfaces[0];
boolean isList= implementsInterfaces[1];
boolean isMap= implementsInterfaces[2];
if (fContext.isLimitItems() && (isCollection || isMap) && !isList) {
needCollectionToStringMethod= true;
}
if (fContext.isCustomArray() && memberType.isArray()) {
ITypeBinding componentType= memberType.getComponentType();
if (componentType.isPrimitive() && (!fContext.is50orHigher() || (!fContext.is60orHigher() && fContext.isLimitItems()))) {
if (!typesThatNeedArrayToStringMethod.contains(componentType))
typesThatNeedArrayToStringMethod.add(componentType);
} else if (!componentType.isPrimitive())
isNonPrimitive= true;
}
}
if (!typesThatNeedArrayToStringMethod.isEmpty() && isNonPrimitive)
typesThatNeedArrayToStringMethod.add(fAst.resolveWellKnownType("java.lang.Object")); //$NON-NLS-1$
}
/**
*
* @param templateElement the template element, see constants in {@link ToStringTemplateParser}
* @param member the member
* @return <code>String</code> or <code>Expression</code> switching
*/
protected Object processElement(String templateElement, Object member) {
Object result= templateElement;
if (templateElement == ToStringTemplateParser.OBJECT_NAME_VARIABLE) {
result= fContext.getTypeBinding().getName();
}
if (templateElement == ToStringTemplateParser.OBJECT_GET_NAME_VARIABLE) {
//this.getClass().getName()
MethodInvocation getClassInvocation= fAst.newMethodInvocation();
if (fContext.isKeywordThis())
getClassInvocation.setExpression(fAst.newThisExpression());
getClassInvocation.setName(fAst.newSimpleName("getClass")); //$NON-NLS-1$
MethodInvocation getNameInvocation= fAst.newMethodInvocation();
getNameInvocation.setExpression(getClassInvocation);
getNameInvocation.setName(fAst.newSimpleName("getName")); //$NON-NLS-1$
result= getNameInvocation;
}
if (templateElement == ToStringTemplateParser.OBJECT_SUPER_TOSTRING_VARIABLE) {
//super.toString()
SuperMethodInvocation superToStringInvocation= fAst.newSuperMethodInvocation();
superToStringInvocation.setName(fAst.newSimpleName(METHODNAME_TO_STRING));
result= superToStringInvocation;
}
if (templateElement == ToStringTemplateParser.OBJECT_HASHCODE_VARIABLE) {
//this.hashCode()
MethodInvocation hashCodeInvocation= fAst.newMethodInvocation();
if (fContext.isKeywordThis())
hashCodeInvocation.setExpression(fAst.newThisExpression());
hashCodeInvocation.setName(fAst.newSimpleName("hashCode")); //$NON-NLS-1$
result= hashCodeInvocation;
}
if (templateElement == ToStringTemplateParser.OBJECT_SYSTEM_HASHCODE_VARIABLE) {
//system.identityHashCode(this)
result= createMethodInvocation(addImport("java.lang.System"), "identityHashCode", fAst.newThisExpression()); //$NON-NLS-1$ //$NON-NLS-2$
}
if (templateElement == ToStringTemplateParser.MEMBER_NAME_VARIABLE || templateElement == ToStringTemplateParser.MEMBER_NAME_PARENTHESIS_VARIABLE) {
result= getMemberName(member, templateElement);
}
if (templateElement == ToStringTemplateParser.MEMBER_VALUE_VARIABLE) {
result= createMemberAccessExpression(member, false, fContext.isSkipNulls());
}
if (result instanceof StringLiteral)
return ((StringLiteral)result).getLiteralValue();
else
return result;
}
/**
* Adds an element to the generated <code>toString</code> method. This method is called for
* every element of the format template.
*
* @param element String or expression to be added (<code>IVariableBinding</code> or
* <code>IMethodBinding</code>
*/
protected abstract void addElement(Object element);
/**
* Adds a member to the <code>toString</code> method. This method is called for every member if
* "Skip null values" options is unchecked.
*
* @param member a member to be added
* @param addSeparator true, if separator should be added after the member (i.e. this is not the
* last member)
*/
protected void addMember(Object member, boolean addSeparator) {
String[] stringArray= fContext.getTemplateParser().getBody();
for (int i= 0; i < stringArray.length; i++) {
addElement(processElement(stringArray[i], member));
}
if (addSeparator)
addElement(fContext.getTemplateParser().getSeparator());
}
/**
* Adds a code checking if member's value is not null and adding it to the generated string.
* This method is called for every non-primitive type member if "Skip null values" options is
* checked, or for every <code>Collection</code> and <code>Map</code> member if there's a limit
* for number of elements.
*
* @param member a member to be added
* @param addSeparator true, if separator should be added after the member (i.e. this is not the
* last member)
*/
protected void addMemberCheckNull(Object member, boolean addSeparator) {
addMember(member, addSeparator);
}
/**
* Creates an invocation of a method that takes zero or one argument
*
* @param expression the receiver expression
* @param methodName the method name
* @param argument the argument, can be <code>null</code> if the method does not take any arguments
* @return MethodInvocation in following form: <code>expression.methodName(argument)</code>
*/
protected MethodInvocation createMethodInvocation(Expression expression, String methodName, Expression argument) {
MethodInvocation invocation= fAst.newMethodInvocation();
invocation.setExpression(expression);
invocation.setName(fAst.newSimpleName(methodName));
if (argument != null)
invocation.arguments().add(argument);
return invocation;
}
/**
* Creates an invocation of a method that takes zero or one argument
*
* @param receiver the receiver name
* @param methodName the method name
* @param argument the argument, can be <code>null</code> if the method does not take any arguments
* @return MethodInvocation in following form: <code>expression.methodName(argument)</code>
*/
protected MethodInvocation createMethodInvocation(String receiver, String methodName, Expression argument) {
return createMethodInvocation(fAst.newName(receiver), methodName, argument);
}
/**
* Creates a statement that can be used as for/while/if-then-else block
*
* @param expression an expression
* @return a single-line statement, or a block, depending on settings
*/
protected Statement createOneStatementBlock(Expression expression) {
if (fContext.isForceBlocks()) {
Block forBlock= fAst.newBlock();
forBlock.statements().add(fAst.newExpressionStatement(expression));
return forBlock;
} else {
return fAst.newExpressionStatement(expression);
}
}
protected InfixExpression createInfixExpression(Expression leftOperand, Operator operator, Expression rightOperand) {
InfixExpression expression= fAst.newInfixExpression();
expression.setLeftOperand(leftOperand);
expression.setOperator(operator);
expression.setRightOperand(rightOperand);
return expression;
}
/**
* @return a statement in form of <code>final int maxLen = 10;</code>
*/
protected VariableDeclarationStatement createMaxLenDeclaration() {
VariableDeclarationFragment fragment= fAst.newVariableDeclarationFragment();
fragment.setName(fAst.newSimpleName(fMaxLenVariableName));
fragment.setInitializer(fAst.newNumberLiteral(String.valueOf(fContext.getLimitItemsValue())));
VariableDeclarationStatement declExpression= fAst.newVariableDeclarationStatement(fragment);
declExpression.setType(fAst.newPrimitiveType(PrimitiveType.INT));
declExpression.modifiers().add(fAst.newModifier(ModifierKeyword.FINAL_KEYWORD));
return declExpression;
}
/**
* @param member <code>IVariableBinding</code> or <code>IMethodBinding</code> representing a
* member
* @param ignoreArraysCollections if false and <i>limit number of items</i> is set, this method
* will use custom methods to print out Arrays, Collections and Sets
* @param ignoreNulls if false, method will add checking for nulls when using custom methods
* @return an expression that accesses given member
*/
protected Expression createMemberAccessExpression(Object member, boolean ignoreArraysCollections, boolean ignoreNulls) {
if (!ignoreArraysCollections) {
ITypeBinding memberType= getMemberType(member);
boolean isArray= memberType.isArray();
boolean[] implementsInterfaces= implementsInterfaces(memberType.getErasure(), new String[] { "java.util.Collection", "java.util.List", "java.util.Map" }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
boolean isCollection= implementsInterfaces[0];
boolean isList= implementsInterfaces[1];
boolean isMap= implementsInterfaces[2];
if (isCollection || isMap || (isArray && fContext.isCustomArray())) {
Expression accessExpression= null;
if (fContext.isLimitItems()) {
if (fContext.getLimitItemsValue() == 0) {
accessExpression= fAst.newStringLiteral();
((StringLiteral)accessExpression).setLiteralValue("[]"); //$NON-NLS-1$
} else {
if (isList && !needCollectionToStringMethod) {
//member.subList(0, Math.min(maxLen, member.size())
MethodInvocation memberSizeInvocation= fAst.newMethodInvocation();
memberSizeInvocation.setExpression(createMemberAccessExpression(member, true, true));
memberSizeInvocation.setName(fAst.newSimpleName("size")); //$NON-NLS-1$
accessExpression= createSubListInvocation(createMemberAccessExpression(member, true, true), memberSizeInvocation);
needMaxLenVariable= true;
} else if (isCollection || isMap) {
//toString(member, maxLen)
Expression memberAccess= createMemberAccessExpression(member, true, true);
if (isMap) {
//member.entrySet();
MethodInvocation entrySetInvocation= fAst.newMethodInvocation();
entrySetInvocation.setExpression(memberAccess);
entrySetInvocation.setName(fAst.newSimpleName("entrySet")); //$NON-NLS-1$
memberAccess= entrySetInvocation;
}
MethodInvocation toStringInvocation= fAst.newMethodInvocation();
if (fContext.isKeywordThis())
toStringInvocation.setExpression(fAst.newThisExpression());
toStringInvocation.setName(fAst.newSimpleName(HELPER_TOSTRING_METHOD_NAME));
toStringInvocation.arguments().add(memberAccess);
toStringInvocation.arguments().add(fAst.newSimpleName(fMaxLenVariableName));
needMaxLenVariable= true;
accessExpression= toStringInvocation;
} else if (isArray) {
FieldAccess lengthAccess= fAst.newFieldAccess();
lengthAccess.setExpression(createMemberAccessExpression(member, true, true));
lengthAccess.setName(fAst.newSimpleName("length")); //$NON-NLS-1$
ITypeBinding arrayComponentType= memberType.getComponentType();
if (!arrayComponentType.isPrimitive() && typesThatNeedArrayToStringMethod.isEmpty()) {
//Arrays.asList(member).subList(0, Math.min(maxLen, member.length))
MethodInvocation asListInvocation= createMethodInvocation(addImport("java.util.Arrays"), "asList", createMemberAccessExpression(member, true, true)); //$NON-NLS-1$ //$NON-NLS-2$
accessExpression= createSubListInvocation(asListInvocation, lengthAccess);
} else {
if (fContext.is60orHigher()) {
// Arrays.toString( Arrays.copyOf ( member, Math.min (maxLen, member.length) )
Name arraysImport= addImport("java.util.Arrays"); //$NON-NLS-1$
MethodInvocation minInvocation= createMethodInvocation(addImport("java.lang.Math"), "min", lengthAccess); //$NON-NLS-1$ //$NON-NLS-2$
minInvocation.arguments().add(fAst.newSimpleName(fMaxLenVariableName));
needMaxLenVariable= true;
MethodInvocation copyOfInvocation= createMethodInvocation(arraysImport, "copyOf", createMemberAccessExpression(member, true, true)); //$NON-NLS-1$
copyOfInvocation.arguments().add(minInvocation);
Name arraysImportCopy= (Name)ASTNode.copySubtree(fAst, arraysImport);
accessExpression= createMethodInvocation(arraysImportCopy, "toString", copyOfInvocation); //$NON-NLS-1$
} else {
// arrayToString(member, member.length, maxLen)
MethodInvocation arrayToStringInvocation= fAst.newMethodInvocation();
if (fContext.isKeywordThis())
arrayToStringInvocation.setExpression(fAst.newThisExpression());
arrayToStringInvocation.setName(fAst.newSimpleName(HELPER_ARRAYTOSTRING_METHOD_NAME));
arrayToStringInvocation.arguments().add(createMemberAccessExpression(member, true, true));
arrayToStringInvocation.arguments().add(lengthAccess);
arrayToStringInvocation.arguments().add(fAst.newSimpleName(fMaxLenVariableName));
needMaxLenVariable= true;
accessExpression= arrayToStringInvocation;
}
}
}
}
} else {
if (isArray && fContext.isCustomArray()) {
if (fContext.is50orHigher()) {
// Arrays.toString(member)
return createMethodInvocation(addImport("java.util.Arrays"), "toString", createMemberAccessExpression(member, true, true)); //$NON-NLS-1$ //$NON-NLS-2$
} else {
ITypeBinding arrayComponentType= memberType.getComponentType();
if (!arrayComponentType.isPrimitive() && typesThatNeedArrayToStringMethod.isEmpty()) {
// Arrays.asList(member)
accessExpression= createMethodInvocation(addImport("java.util.Arrays"), "asList", createMemberAccessExpression(member, true, true)); //$NON-NLS-1$ //$NON-NLS-2$
} else {
// arrayToString(member, member.length, maxLen)
FieldAccess lengthAccess= fAst.newFieldAccess();
lengthAccess.setExpression(createMemberAccessExpression(member, true, true));
lengthAccess.setName(fAst.newSimpleName("length")); //$NON-NLS-1$
MethodInvocation arrayToStringInvocation= fAst.newMethodInvocation();
if (fContext.isKeywordThis())
arrayToStringInvocation.setExpression(fAst.newThisExpression());
arrayToStringInvocation.setName(fAst.newSimpleName(HELPER_ARRAYTOSTRING_METHOD_NAME));
arrayToStringInvocation.arguments().add(createMemberAccessExpression(member, true, true));
arrayToStringInvocation.arguments().add(lengthAccess);
accessExpression= arrayToStringInvocation;
}
}
}
}
if (accessExpression != null) {
if (!ignoreNulls) {
ConditionalExpression conditional= fAst.newConditionalExpression();
conditional.setExpression(createInfixExpression(createMemberAccessExpression(member, true, true), Operator.NOT_EQUALS, fAst.newNullLiteral()));
conditional.setThenExpression(accessExpression);
conditional.setElseExpression(fAst.newNullLiteral());
return conditional;
}
return accessExpression;
}
}
}
if (member instanceof IVariableBinding) {
if (fContext.isKeywordThis()) {
FieldAccess fa= fAst.newFieldAccess();
fa.setExpression(fAst.newThisExpression());
fa.setName(fAst.newSimpleName(((IVariableBinding)member).getName()));
return fa;
}
return fAst.newSimpleName(((IVariableBinding)member).getName());
}
if (member instanceof IMethodBinding) {
if (((IMethodBinding)member).getName().equals(METHODNAME_TO_STRING)) {
SuperMethodInvocation invocation= fAst.newSuperMethodInvocation();
invocation.setName(fAst.newSimpleName(((IMethodBinding)member).getName()));
return invocation;
}
MethodInvocation invocation= fAst.newMethodInvocation();
if (fContext.isKeywordThis()) {
invocation.setExpression(fAst.newThisExpression());
}
invocation.setName(fAst.newSimpleName(((IMethodBinding)member).getName()));
return invocation;
}
return null;
}
protected Expression createSubListInvocation(Expression memberAccess, Expression sizeAccess) {
MethodInvocation subListInvocation= fAst.newMethodInvocation();
subListInvocation.setExpression(memberAccess);
subListInvocation.setName(fAst.newSimpleName("subList")); //$NON-NLS-1$
subListInvocation.arguments().add(fAst.newNumberLiteral(String.valueOf(0)));
MethodInvocation minInvocation= createMethodInvocation(addImport("java.lang.Math"), "min", sizeAccess); //$NON-NLS-1$ //$NON-NLS-2$
minInvocation.arguments().add(fAst.newSimpleName(fMaxLenVariableName));
subListInvocation.arguments().add(minInvocation);
needMaxLenVariable= true;
return subListInvocation;
}
/**
* Adds an import to the class. This method should be used for every class reference added to
* the generated code.
*
* @param typeName a fully qualified name of a type
* @return simple name of a class if the import was added and fully qualified name if there was
* a conflict
*/
protected Name addImport(String typeName) {
String importedName= fContext.getImportRewrite().addImport(typeName);
return fAst.newName(importedName);
}
private Set<String> excluded;
protected String createNameSuggestion(String baseName, int variableKind) {
if (excluded == null) {
excluded= new HashSet<>();
IVariableBinding[] fields= fContext.getTypeBinding().getDeclaredFields();
for (int i= 0; i < fields.length; i++) {
excluded.add(fields[i].getName());
}
ITypeBinding superType= fContext.getTypeBinding().getSuperclass();
while (superType != null) {
fields= superType.getDeclaredFields();
for (int i= 0; i < fields.length; i++) {
if (!Modifier.isPrivate(fields[i].getModifiers())) {
excluded.add(fields[i].getName());
}
}
superType= superType.getSuperclass();
}
ITypeBinding[] types= fContext.getTypeBinding().getDeclaredTypes();
for (int i= 0; i < types.length; i++) {
excluded.add(types[i].getName());
}
superType= fContext.getTypeBinding().getSuperclass();
while (superType != null) {
types= superType.getDeclaredTypes();
for (int i= 0; i < types.length; i++) {
if (!Modifier.isPrivate(types[i].getModifiers())) {
excluded.add(types[i].getName());
}
}
superType= superType.getSuperclass();
}
}
return StubUtility.getVariableNameSuggestions(variableKind, fContext.getCompilationUnit().getJavaProject(), baseName, 0, excluded, true)[0];
}
/**
* Checks whether given type implements given interface
*
* @param memberType binding of the type to check
* @param interfaceNames fully qualified names of the interfaces to seek for
* @return array of booleans, every element is set to true if interface at the same position in
* <code>interfaceNames</code> is implemented by <code>memberType</code>
*/
protected boolean[] implementsInterfaces(ITypeBinding memberType, String[] interfaceNames) {
boolean[] result= new boolean[interfaceNames.length];
for (int i= 0; i < interfaceNames.length; i++) {
if (memberType.getQualifiedName().equals(interfaceNames[i]))
result[i]= true;
}
ITypeBinding[] interfaces= memberType.getInterfaces();
for (int i= 0; i < interfaces.length; i++) {
boolean[] deeper= implementsInterfaces(interfaces[i].getErasure(), interfaceNames);
for (int j= 0; j < interfaceNames.length; j++) {
result[j]= result[j] || deeper[j];
}
}
return result;
}
/**
*
* @param member <code>IVariableBinding</code> or <code>IMethodBinding</code> representing a
* member
* @param templateElement the template element
* @return the name of the member, with parenthesis at the end if the member is a method
*/
protected String getMemberName(Object member, String templateElement) {
if (member instanceof IVariableBinding) {
return ((IVariableBinding)member).getName();
}
if (member instanceof IMethodBinding) {
String result= ((IMethodBinding)member).getName();
if (templateElement == ToStringTemplateParser.MEMBER_NAME_PARENTHESIS_VARIABLE)
result+= "()"; //$NON-NLS-1$
return result;
}
return null;
}
/**
*
* @param member member to check
* @return type of field or method's return type
*/
protected ITypeBinding getMemberType(Object member) {
if (member instanceof IVariableBinding) {
return ((IVariableBinding)member).getType();
}
if (member instanceof IMethodBinding) {
return ((IMethodBinding)member).getReturnType();
}
return null;
}
public void setContext(ToStringGenerationContext context) {
fContext= context;
fAst= fContext.getAST();
excluded= null;
}
public ToStringGenerationContext getContext() {
return fContext;
}
}