1 /*******************************************************************************
2  * Copyright (c) 2008, 2019 Mateusz Matela and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     Mateusz Matela <mateusz.matela@gmail.com> - [code manipulation] [dcr] toString() builder wizard - https://bugs.eclipse.org/bugs/show_bug.cgi?id=26070
13  *     Red Hat Inc. - moved to jdt.core.manipulation
14  *******************************************************************************/
15 package org.eclipse.jdt.internal.corext.codemanipulation.tostringgeneration;
16 
17 import org.eclipse.core.runtime.CoreException;
18 
19 import org.eclipse.jdt.core.NamingConventions;
20 import org.eclipse.jdt.core.dom.ASTNode;
21 import org.eclipse.jdt.core.dom.Block;
22 import org.eclipse.jdt.core.dom.ClassInstanceCreation;
23 import org.eclipse.jdt.core.dom.Expression;
24 import org.eclipse.jdt.core.dom.IfStatement;
25 import org.eclipse.jdt.core.dom.Name;
26 import org.eclipse.jdt.core.dom.ReturnStatement;
27 import org.eclipse.jdt.core.dom.Statement;
28 import org.eclipse.jdt.core.dom.StringLiteral;
29 import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
30 import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
31 import org.eclipse.jdt.core.dom.InfixExpression.Operator;
32 
33 
34 /**
35  * <p>
36  * Implementation of <code>AbstractToStringGenerator</code> that creates <code>toString()</code>
37  * method using <code>StringBuilder</code> (or <code>StringBuffer</code> for old versions of JDK)
38  * </p>
39  * <p>
40  * Generated methods look like this:
41  *
42  * <pre>
43  * public String toString() {
44  * 	StringBuilder builder= new StringBuilder();
45  * 	builder.append(&quot;FooClass( field1=&quot;);
46  * 	builder.append(field1);
47  * 	builder.append(&quot;, field2=&quot;);
48  * 	builder.append(field2);
49  * 	builder.append(&quot; )&quot;);
50  * 	return builder.toString();
51  * }
52  * </pre>
53  *
54  * </p>
55  *
56  * @since 3.5
57  */
58 public class StringBuilderGenerator extends AbstractToStringGenerator {
59 	protected StringBuffer fBuffer;
60 
61 	protected String fBuilderVariableName;
62 
63 	protected final String APPEND_METHOD_NAME= "append"; //$NON-NLS-1$
64 
flushBuffer(Block target)65 	protected void flushBuffer(Block target) {
66 		if (fBuffer.length() > 0) {
67 			StringLiteral literal= fAst.newStringLiteral();
68 			literal.setLiteralValue(fBuffer.toString());
69 			if (target == null)
70 				target= toStringMethod.getBody();
71 			target.statements().add(fAst.newExpressionStatement(createMethodInvocation(fBuilderVariableName, APPEND_METHOD_NAME, literal)));
72 			fBuffer.setLength(0);
73 		}
74 	}
75 
76 	@Override
initialize()77 	protected void initialize() {
78 		super.initialize();
79 		fBuilderVariableName= createNameSuggestion(getContext().is50orHigher() ? "builder" : "buffer", NamingConventions.VK_LOCAL); //$NON-NLS-1$ //$NON-NLS-2$
80 		fBuffer= new StringBuffer();
81 		VariableDeclarationFragment fragment= fAst.newVariableDeclarationFragment();
82 		fragment.setName(fAst.newSimpleName(fBuilderVariableName));
83 		ClassInstanceCreation classInstance= fAst.newClassInstanceCreation();
84 		Name typeName= addImport(getContext().is50orHigher() ? "java.lang.StringBuilder" : "java.lang.StringBuffer"); //$NON-NLS-1$ //$NON-NLS-2$
85 		classInstance.setType(fAst.newSimpleType(typeName));
86 		fragment.setInitializer(classInstance);
87 		VariableDeclarationStatement vStatement= fAst.newVariableDeclarationStatement(fragment);
88 		vStatement.setType(fAst.newSimpleType((Name)ASTNode.copySubtree(fAst, typeName)));
89 		toStringMethod.getBody().statements().add(vStatement);
90 	}
91 
92 	@Override
complete()93 	protected void complete() throws CoreException {
94 		flushBuffer(null);
95 		super.complete();
96 		ReturnStatement rStatement= fAst.newReturnStatement();
97 		rStatement.setExpression(createMethodInvocation(fBuilderVariableName, "toString", null)); //$NON-NLS-1$
98 		toStringMethod.getBody().statements().add(rStatement);
99 	}
100 
addElement(Object element, Block block)101 	protected void addElement(Object element, Block block) {
102 		if (element instanceof String)
103 			fBuffer.append((String)element);
104 		if (element instanceof Expression) {
105 			flushBuffer(block);
106 			block.statements().add(fAst.newExpressionStatement(createMethodInvocation(fBuilderVariableName, APPEND_METHOD_NAME, (Expression)element)));
107 		}
108 	}
109 
110 	@Override
addMemberCheckNull(Object member, boolean addSeparator)111 	protected void addMemberCheckNull(Object member, boolean addSeparator) {
112 		IfStatement ifStatement= fAst.newIfStatement();
113 		ifStatement.setExpression(createInfixExpression(createMemberAccessExpression(member, true, true), Operator.NOT_EQUALS, fAst.newNullLiteral()));
114 		Block thenBlock= fAst.newBlock();
115 		flushBuffer(null);
116 		for (String s : getContext().getTemplateParser().getBody()) {
117 			addElement(processElement(s, member), thenBlock);
118 		}
119 		if (addSeparator)
120 			addElement(getContext().getTemplateParser().getSeparator(), thenBlock);
121 		flushBuffer(thenBlock);
122 
123 		if (thenBlock.statements().size() == 1 && !getContext().isForceBlocks()) {
124 			ifStatement.setThenStatement((Statement)ASTNode.copySubtree(fAst, (ASTNode)thenBlock.statements().get(0)));
125 		} else {
126 			ifStatement.setThenStatement(thenBlock);
127 		}
128 		toStringMethod.getBody().statements().add(ifStatement);
129 	}
130 
131 	@Override
addElement(Object element)132 	protected void addElement(Object element) {
133 		addElement(element, toStringMethod.getBody());
134 	}
135 }
136