--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2008, 2011 Mateusz Matela 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:
+ * 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
+ *******************************************************************************/
+package org.eclipse.jdt.internal.corext.codemanipulation.tostringgeneration;
+
+import java.util.ArrayList;
+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.InstanceofExpression;
+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.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.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.dom.InfixExpression.Operator;
+import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword;
+
+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<MethodDeclaration>();
+ 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];
+ }
+ fContext.generated_5960924765249646172(this, objectMethod);
+ }
+
+ /**
+ * 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<ITypeBinding>();
+
+ 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];
+ isNonPrimitive= fContext.generated_4381819252754149304(this, isNonPrimitive, memberType, implementsInterfaces, isCollection);
+ }
+ 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;
+ result= fContext.generated_6573513055157508799(this, templateElement, result);
+ 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;
+ }
+ }
+ }
+ return fContext.generated_2098369527780276835(this, member);
+ }
+
+ 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);
+ }
+
+ Set<String> excluded;
+ protected String createNameSuggestion(String baseName, int variableKind) {
+ return fContext.generated_907652555048609483(this, baseName, variableKind);
+ }
+
+ /**
+ * 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;
+ }
+
+}