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