blob: 5f1da27f429fdac443bcd5d1037e501861620a9f [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.refactoring.extractclass;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PropertyUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.psi.MethodInheritanceUtils;
import com.intellij.util.Function;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NonNls;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
class ExtractedClassBuilder {
private static final Logger LOGGER = Logger.getInstance("com.siyeh.rpp.extractclass.ExtractedClassBuilder");
private String className = null;
private String packageName = null;
private final List<PsiField> fields = new ArrayList<PsiField>(5);
private final List<PsiMethod> methods = new ArrayList<PsiMethod>(5);
private final List<PsiClassInitializer> initializers = new ArrayList<PsiClassInitializer>(5);
private final List<PsiClass> innerClasses = new ArrayList<PsiClass>(5);
private final List<PsiClass> innerClassesToMakePublic = new ArrayList<PsiClass>(5);
private final List<PsiTypeParameter> typeParams = new ArrayList<PsiTypeParameter>();
private final List<PsiClass> interfaces = new ArrayList<PsiClass>();
private boolean requiresBackPointer = false;
private String originalClassName = null;
private String backPointerName = null;
private Project myProject;
private JavaCodeStyleManager myJavaCodeStyleManager;
private Set<PsiField> myFieldsNeedingSetters;
private Set<PsiField> myFieldsNeedingGetter;
private List<PsiField> enumConstantFields;
private PsiType myEnumParameterType;
public void setClassName(String className) {
this.className = className;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}
public void setOriginalClassName(String originalClassName) {
this.originalClassName = originalClassName;
}
public void addField(PsiField field) {
fields.add(field);
}
public void addMethod(PsiMethod method) {
methods.add(method);
}
public void addInitializer(PsiClassInitializer initializer) {
initializers.add(initializer);
}
public void addInnerClass(PsiClass innerClass, boolean makePublic) {
innerClasses.add(innerClass);
if (makePublic) {
innerClassesToMakePublic.add(innerClass);
}
}
public void setTypeArguments(List<PsiTypeParameter> typeParams) {
this.typeParams.clear();
this.typeParams.addAll(typeParams);
}
public void setInterfaces(List<PsiClass> interfaces) {
this.interfaces.clear();
this.interfaces.addAll(interfaces);
}
public String buildBeanClass() {
if (requiresBackPointer) {
calculateBackpointerName();
}
@NonNls final StringBuffer out = new StringBuffer(1024);
if (packageName.length() > 0) out.append("package " + packageName + ';');
out.append("public ");
fields.removeAll(enumConstantFields);
out.append(hasEnumConstants() ? "enum " : "class ");
out.append(className);
if (!typeParams.isEmpty()) {
out.append('<');
boolean first = true;
for (PsiTypeParameter typeParam : typeParams) {
if (!first) {
out.append(',');
}
out.append(typeParam.getText());
first = false;
}
out.append('>');
}
if (!interfaces.isEmpty()) {
out.append(" implements ");
boolean first = true;
for (PsiClass implemented : interfaces) {
if (!first) {
out.append(',');
}
out.append(implemented.getQualifiedName());
first = false;
}
}
out.append('{');
if (requiresBackPointer) {
out.append("private final " + originalClassName);
if (!typeParams.isEmpty()) {
out.append('<');
boolean first = true;
for (PsiTypeParameter typeParam : typeParams) {
if (!first) {
out.append(',');
}
out.append(typeParam.getName());
first = false;
}
out.append('>');
}
out.append(' ' + backPointerName + ";");
}
outputFieldsAndInitializers(out);
if (hasEnumConstants()) {
final String fieldName = getValueFieldName();
out.append("\n").append("private ").append(myEnumParameterType.getCanonicalText()).append(" ").append(fieldName).append(";\n");
out.append("public ").append(myEnumParameterType.getCanonicalText()).append(" ")
.append(getterName()).append("(){\nreturn ").append(fieldName).append(";\n}\n");
}
if (hasEnumConstants() || needConstructor() || requiresBackPointer) {
outputConstructor(out);
}
outputMethods(out);
outputInnerClasses(out);
out.append("}");
return out.toString();
}
private String getterName() {//todo unique getterName: see also com.intellij.refactoring.extractclass.usageInfo.ReplaceStaticVariableAccess
return PropertyUtil.suggestGetterName("value", myEnumParameterType);
}
private boolean hasEnumConstants() {
return !enumConstantFields.isEmpty();
}
private String getValueFieldName() {
final String myValue = myJavaCodeStyleManager.variableNameToPropertyName("value", VariableKind.FIELD);
return myJavaCodeStyleManager.suggestUniqueVariableName(myValue, enumConstantFields.get(0), true);
}
private void calculateBackpointerName() {
final String baseName;
if (originalClassName.indexOf((int)'.') == 0) {
baseName = StringUtil.decapitalize(originalClassName);
}
else {
final String simpleClassName = originalClassName.substring(originalClassName.lastIndexOf('.') + 1);
baseName = StringUtil.decapitalize(simpleClassName);
}
String name = myJavaCodeStyleManager.propertyNameToVariableName(baseName, VariableKind.FIELD);
if (!existsFieldWithName(name)) {
backPointerName = name;
return;
}
int counter = 1;
while (true) {
name = name + counter;
if (!existsFieldWithName(name)) {
backPointerName = name;
return;
}
counter++;
}
}
private boolean existsFieldWithName(String name) {
for (PsiField field : fields) {
final String fieldName = field.getName();
if (name.equals(fieldName)) {
return true;
}
}
return false;
}
private boolean needConstructor() {
for ( PsiField field : fields) {
if (!field.hasModifierProperty(PsiModifier.STATIC)) {
return true;
}
}
for (PsiMethod method : methods) {
if (!method.hasModifierProperty(PsiModifier.STATIC)) {
return true;
}
}
return false;
}
private void outputMethods(StringBuffer out) {
for (PsiMethod method : methods) {
method.accept(new Mutator(out));
}
}
private void outputInnerClasses(StringBuffer out) {
for (PsiClass innerClass : innerClasses) {
outputMutatedInnerClass(out, innerClass, innerClassesToMakePublic.contains(innerClass));
}
}
private void outputMutatedInnerClass(StringBuffer out, PsiClass innerClass, boolean makePublic) {
if (makePublic) {
try {
PsiUtil.setModifierProperty(innerClass, PsiModifier.PUBLIC, false);
}
catch (IncorrectOperationException e) {
LOGGER.error(e);
}
}
innerClass.accept(new Mutator(out));
}
private void outputFieldsAndInitializers(final StringBuffer out) {
if (hasEnumConstants()) {
out.append(StringUtil.join(enumConstantFields, new Function<PsiField, String>() {
public String fun(PsiField field) {
final StringBuffer fieldStr = new StringBuffer(field.getName() + "(");
final PsiExpression initializer = field.getInitializer();
if (initializer != null) {
initializer.accept(new Mutator(fieldStr));
}
fieldStr.append(")");
return fieldStr.toString();
}
}, ", "));
out.append(";");
}
final List<PsiClassInitializer> remainingInitializers = new ArrayList<PsiClassInitializer>(initializers);
for (final PsiField field : fields) {
final Iterator<PsiClassInitializer> initializersIterator = remainingInitializers.iterator();
final int fieldOffset = field.getTextRange().getStartOffset();
while (initializersIterator.hasNext()) {
final PsiClassInitializer initializer = initializersIterator.next();
if (initializer.getTextRange().getStartOffset() < fieldOffset) {
initializer.accept(new Mutator(out));
initializersIterator.remove();
}
}
field.accept(new Mutator(out));
if (myFieldsNeedingGetter != null && myFieldsNeedingGetter.contains(field)) {
out.append(PropertyUtil.generateGetterPrototype(field).getText());
out.append("\n");
}
if (myFieldsNeedingSetters != null && myFieldsNeedingSetters.contains(field)) {
out.append(PropertyUtil.generateSetterPrototype(field).getText());
out.append("\n");
}
}
for (PsiClassInitializer initializer : remainingInitializers) {
initializer.accept(new Mutator(out));
}
}
private void outputConstructor(@NonNls StringBuffer out) {
out.append("\t").append(hasEnumConstants() ? "" : "public ").append(className).append('(');
if (requiresBackPointer) {
final String parameterName = myJavaCodeStyleManager.propertyNameToVariableName(backPointerName, VariableKind.PARAMETER);
out.append(originalClassName);
if (!typeParams.isEmpty()) {
out.append('<');
boolean first = true;
for (PsiTypeParameter typeParam : typeParams) {
if (!first) {
out.append(',');
}
out.append(typeParam.getName());
first = false;
}
out.append('>');
}
out.append(' ' + parameterName);
} else if (hasEnumConstants()) {
out.append(myEnumParameterType.getCanonicalText()).append(" ").append("value");
}
out.append(")");
out.append("\t{");
if (requiresBackPointer) {
final String parameterName = myJavaCodeStyleManager.propertyNameToVariableName(backPointerName, VariableKind.PARAMETER);
if (backPointerName.equals(parameterName)) {
out.append("\t\tthis." + backPointerName + " = " + parameterName + ";");
}
else {
out.append("\t\t" + backPointerName + " = " + parameterName + ";");
}
} else if (hasEnumConstants()) {
final String fieldName = getValueFieldName();
out.append(fieldName.equals("value") ? "this." : "").append(fieldName).append(" = value;");
}
out.append("\t}");
}
public void setRequiresBackPointer(boolean requiresBackPointer) {
this.requiresBackPointer = requiresBackPointer;
}
public void setProject(final Project project) {
myProject = project;
myJavaCodeStyleManager = JavaCodeStyleManager.getInstance(project);
}
public void setFieldsNeedingGetters(Set<PsiField> fieldsNeedingGetter) {
myFieldsNeedingGetter = fieldsNeedingGetter;
}
public void setFieldsNeedingSetters(Set<PsiField> fieldsNeedingSetters) {
myFieldsNeedingSetters = fieldsNeedingSetters;
}
private boolean fieldIsExtracted(PsiField field) {
final ArrayList<PsiField> extractedFields = new ArrayList<PsiField>(fields);
extractedFields.addAll(enumConstantFields);
if (extractedFields.contains(field)) return true;
final PsiClass containingClass = field.getContainingClass();
return innerClasses.contains(containingClass);
}
public void setExtractAsEnum(List<PsiField> extractAsEnum) {
this.enumConstantFields = extractAsEnum;
if (hasEnumConstants()) {
myEnumParameterType = enumConstantFields.get(0).getType();
}
}
private class Mutator extends JavaElementVisitor {
@NonNls
private final StringBuffer out;
private Mutator(StringBuffer out) {
super();
this.out = out;
}
public void visitElement(PsiElement element) {
super.visitElement(element);
final PsiElement[] children = element.getChildren();
if (children.length == 0) {
final String text = element.getText();
out.append(text);
}
else {
for (PsiElement child : children) {
child.accept(this);
}
}
}
public void visitReferenceExpression(PsiReferenceExpression expression) {
final JavaResolveResult resolveResult = expression.advancedResolve(true);
final boolean staticImported = resolveResult.getCurrentFileResolveScope() instanceof PsiImportStaticStatement;
final PsiElement qualifier = expression.getQualifier();
if (qualifier == null || qualifier instanceof PsiThisExpression) {
final PsiElement referent = resolveResult.getElement();
if (referent instanceof PsiField) {
final PsiField field = (PsiField)referent;
if (fieldIsExtracted(field)) {
final String name = field.getName();
if (enumConstantFields.contains(field)) {
out.append(name).append(".").append(getterName()).append("()");
} else {
if (qualifier != null && name.equals(expression.getReferenceName())) {
out.append("this.");
}
out.append(name);
}
}
else {
if (field.hasModifierProperty(PsiModifier.STATIC)) {
if (field instanceof PsiEnumConstant) {
out.append(field.getName());
} else if (staticImported) {
final PsiImportStaticStatement importStaticStatement = (PsiImportStaticStatement)resolveResult.getCurrentFileResolveScope();
final PsiClass targetClass = importStaticStatement.resolveTargetClass();
out.append(targetClass != null ? targetClass.getQualifiedName() : "").append(".").append(field.getName());
} else {
out.append(originalClassName + '.' + field.getName());
}
}
else {
out.append(backPointerName + '.' + PropertyUtil.suggestGetterName(field) + "()");
}
}
}
else {
visitElement(expression);
}
}
else {
visitElement(expression);
}
}
public void visitAssignmentExpression(PsiAssignmentExpression expression) {
PsiExpression lhs = expression.getLExpression();
final PsiExpression rhs = expression.getRExpression();
if (isBackpointerReference(lhs) && rhs != null) {
while (lhs instanceof PsiParenthesizedExpression) {
lhs = ((PsiParenthesizedExpression)lhs).getExpression();
}
final PsiReferenceExpression reference = (PsiReferenceExpression)lhs;
assert reference != null;
final PsiField field = (PsiField)reference.resolve();
final PsiJavaToken sign = expression.getOperationSign();
final IElementType tokenType = sign.getTokenType();
assert field != null;
if (!field.hasModifierProperty(PsiModifier.STATIC)) {
delegate(rhs, field, sign, tokenType, backPointerName);
}
else {
visitElement(expression);
}
}
else {
visitElement(expression);
}
}
private void delegate(final PsiExpression rhs, final PsiField field, final PsiJavaToken sign, final IElementType tokenType,
final String fieldName) {
if (tokenType.equals(JavaTokenType.EQ)) {
final String setterName = PropertyUtil.suggestSetterName(field);
out.append(fieldName + '.' + setterName + '(');
rhs.accept(this);
out.append(')');
}
else {
final String operator = sign.getText().substring(0, sign.getTextLength() - 1);
final String setterName = PropertyUtil.suggestSetterName(field);
out.append(fieldName + '.' + setterName + '(');
final String getterName = PropertyUtil.suggestGetterName(field);
out.append(fieldName + '.' + getterName + "()");
out.append(operator);
rhs.accept(this);
out.append(')');
}
}
public void visitPostfixExpression(PsiPostfixExpression expression) {
outputUnaryExpression(expression, expression.getOperand(), expression.getOperationSign());
}
public void visitPrefixExpression(PsiPrefixExpression expression) {
outputUnaryExpression(expression, expression.getOperand(), expression.getOperationSign());
}
private void outputUnaryExpression(final PsiExpression expression, PsiExpression operand, final PsiJavaToken sign) {
final IElementType tokenType = sign.getTokenType();
if (isBackpointerReference(operand) && (tokenType.equals(JavaTokenType.PLUSPLUS) || tokenType.equals(JavaTokenType.MINUSMINUS))) {
while (operand instanceof PsiParenthesizedExpression) {
operand = ((PsiParenthesizedExpression)operand).getExpression();
}
final PsiReferenceExpression reference = (PsiReferenceExpression)operand;
final String operator;
if (tokenType.equals(JavaTokenType.PLUSPLUS)) {
operator = "+";
}
else {
operator = "-";
}
final PsiField field = (PsiField)reference.resolve();
assert field != null;
if (!field.hasModifierProperty(PsiModifier.STATIC)) {
out.append(backPointerName +
'.' + PropertyUtil.suggestSetterName(field) +
'(' +
backPointerName +
'.' + PropertyUtil.suggestGetterName(field) +
"()" +
operator +
"1)");
}
else {
visitElement(expression);
}
}
else {
visitElement(expression);
}
}
private boolean isBackpointerReference(PsiExpression expression) {
return BackpointerUtil.isBackpointerReference(expression, new Condition<PsiField>() {
public boolean value(final PsiField psiField) {
return !fieldIsExtracted(psiField);
}
});
}
public void visitThisExpression(PsiThisExpression expression) {
out.append(backPointerName);
}
public void visitMethodCallExpression(PsiMethodCallExpression call) {
final PsiReferenceExpression expression = call.getMethodExpression();
final JavaResolveResult resolveResult = expression.advancedResolve(false);
final PsiElement qualifier = expression.getQualifier();
if (qualifier == null || qualifier instanceof PsiThisExpression) {
final PsiMethod method = call.resolveMethod();
if (method != null && !isCompletelyMoved(method)) {
final String methodName = method.getName();
if (method.hasModifierProperty(PsiModifier.STATIC)) {
final PsiElement resolveScope = resolveResult.getCurrentFileResolveScope();
if (resolveScope instanceof PsiImportStaticStatement) {
final PsiClass targetClass = ((PsiImportStaticStatement)resolveScope).resolveTargetClass();
out.append(targetClass != null ? targetClass.getQualifiedName() : "").append('.').append(methodName);
} else {
out.append(originalClassName + '.' + methodName);
}
}
else {
out.append(backPointerName + '.' + methodName);
}
final PsiExpressionList argumentList = call.getArgumentList();
argumentList.accept(this);
}
else {
visitElement(call);
}
}
else {
visitElement(call);
}
}
public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
final String referenceText = reference.getCanonicalText();
out.append(referenceText);
}
private boolean isCompletelyMoved(PsiMethod method) {
return methods.contains(method) && !
MethodInheritanceUtils.hasSiblingMethods(method);
}
}
}