package de.plushnikov.intellij.plugin.psi;
import com.intellij.lang.ASTNode;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiCodeBlock;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementFactory;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiIdentifier;
import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiReferenceList;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiTypeParameter;
import com.intellij.psi.impl.CheckUtil;
import com.intellij.psi.impl.light.LightMethodBuilder;
import com.intellij.psi.impl.light.LightModifierList;
import com.intellij.psi.impl.light.LightTypeParameterListBuilder;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.StringBuilderSpinAllocator;
import de.plushnikov.intellij.plugin.icon.LombokIcons;
import de.plushnikov.intellij.plugin.util.ReflectionUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Plushnikov Michail
*/
public class LombokLightMethodBuilder extends LightMethodBuilder {
private PsiMethod myMethod;
private ASTNode myASTNode;
private PsiCodeBlock myBodyCodeBlock;
public LombokLightMethodBuilder(@NotNull PsiManager manager, @NotNull String name) {
super(manager, JavaLanguage.INSTANCE, name,
new LombokLightParameterListBuilder(manager, JavaLanguage.INSTANCE),
new LombokLightModifierList(manager, JavaLanguage.INSTANCE),
new LombokLightReferenceListBuilder(manager, JavaLanguage.INSTANCE, PsiReferenceList.Role.THROWS_LIST),
new LightTypeParameterListBuilder(manager, JavaLanguage.INSTANCE));
setBaseIcon(LombokIcons.METHOD_ICON);
}
public LombokLightMethodBuilder withNavigationElement(PsiElement navigationElement) {
setNavigationElement(navigationElement);
return this;
}
public LombokLightMethodBuilder withModifier(@PsiModifier.ModifierConstant @NotNull @NonNls String modifier) {
addModifier(modifier);
return this;
}
public LombokLightMethodBuilder withModifier(@PsiModifier.ModifierConstant @NotNull @NonNls String... modifiers) {
for (String modifier : modifiers) {
addModifier(modifier);
}
return this;
}
public LombokLightMethodBuilder withMethodReturnType(PsiType returnType) {
setMethodReturnType(returnType);
return this;
}
public LombokLightMethodBuilder withParameter(@NotNull String name, @NotNull PsiType type) {
return withParameter(new LombokLightParameter(name, type, this, JavaLanguage.INSTANCE));
}
public LombokLightMethodBuilder withParameter(@NotNull PsiParameter psiParameter) {
addParameter(psiParameter);
return this;
}
public LombokLightMethodBuilder withException(@NotNull PsiClassType type) {
addException(type);
return this;
}
public LombokLightMethodBuilder withException(@NotNull String fqName) {
addException(fqName);
return this;
}
public LombokLightMethodBuilder withContainingClass(@NotNull PsiClass containingClass) {
setContainingClass(containingClass);
return this;
}
public LombokLightMethodBuilder withTypeParameter(@NotNull PsiTypeParameter typeParameter) {
addTypeParameter(typeParameter);
return this;
}
public LombokLightMethodBuilder withConstructor(boolean isConstructor) {
setConstructor(isConstructor);
return this;
}
public LombokLightMethodBuilder withBody(@NotNull PsiCodeBlock codeBlock) {
myBodyCodeBlock = codeBlock;
return this;
}
// add Parameter as is, without wrapping with LightTypeParameter
public LightMethodBuilder addTypeParameter(PsiTypeParameter parameter) {
((LightTypeParameterListBuilder) getTypeParameterList()).addParameter(parameter);
return this;
}
@Override
public PsiCodeBlock getBody() {
return myBodyCodeBlock;
}
@Override
public PsiIdentifier getNameIdentifier() {
return new LombokLightIdentifier(myManager, getName());
}
@Override
public PsiElement getParent() {
PsiElement result = super.getParent();
result = null != result ? result : getContainingClass();
return result;
}
@Nullable
@Override
public PsiFile getContainingFile() {
PsiClass containingClass = getContainingClass();
return containingClass != null ? containingClass.getContainingFile() : null;
}
@Override
public String getText() {
ASTNode node = getNode();
if (null != node) {
return node.getText();
}
return "";
}
@Override
public ASTNode getNode() {
if (null == myASTNode) {
myASTNode = getOrCreateMyPsiMethod().getNode();
}
return myASTNode;
}
@Override
public TextRange getTextRange() {
TextRange r = super.getTextRange();
return r == null ? TextRange.EMPTY_RANGE : r;
}
private String getAllModifierProperties(LightModifierList modifierList) {
final StringBuilder builder = StringBuilderSpinAllocator.alloc();
try {
for (String modifier : modifierList.getModifiers()) {
if (!PsiModifier.PACKAGE_LOCAL.equals(modifier)) {
builder.append(modifier).append(' ');
}
}
return builder.toString();
} finally {
StringBuilderSpinAllocator.dispose(builder);
}
}
private PsiMethod rebuildMethodFromString() {
final StringBuilder methodTextDeclaration = StringBuilderSpinAllocator.alloc();
try {
methodTextDeclaration.append(getAllModifierProperties((LightModifierList) getModifierList()));
PsiType returnType = getReturnType();
if (null != returnType) {
methodTextDeclaration.append(returnType.getCanonicalText()).append(' ');
}
methodTextDeclaration.append(getName());
methodTextDeclaration.append('(');
if (getParameterList().getParametersCount() > 0) {
for (PsiParameter parameter : getParameterList().getParameters()) {
methodTextDeclaration.append(parameter.getType().getCanonicalText()).append(' ').append(parameter.getName()).append(',');
}
methodTextDeclaration.deleteCharAt(methodTextDeclaration.length() - 1);
}
methodTextDeclaration.append(')');
methodTextDeclaration.append('{').append(" ").append('}');
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(getManager().getProject());
final PsiMethod methodFromText = elementFactory.createMethodFromText(methodTextDeclaration.toString(), getContainingClass());
if (null != getBody()) {
methodFromText.getBody().replace(getBody());
}
return methodFromText;
} finally {
StringBuilderSpinAllocator.dispose(methodTextDeclaration);
}
}
public PsiElement copy() {
return getOrCreateMyPsiMethod().copy();
}
private PsiElement getOrCreateMyPsiMethod() {
if (null == myMethod) {
myMethod = rebuildMethodFromString();
}
return myMethod;
}
@NotNull
@Override
public PsiElement[] getChildren() {
return getOrCreateMyPsiMethod().getChildren();
}
public String toString() {
return "LombokLightMethodBuilder: " + getName();
}
@Override
public PsiElement replace(@NotNull PsiElement newElement) throws IncorrectOperationException {
// just add new element to the containing class
final PsiClass containingClass = getContainingClass();
if (null != containingClass) {
CheckUtil.checkWritable(containingClass);
return containingClass.add(newElement);
}
return null;
}
@Override
public PsiElement setName(@NotNull String name) throws IncorrectOperationException {
ReflectionUtil.setFinalFieldPerReflection(LightMethodBuilder.class, this, String.class, name);
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
LombokLightMethodBuilder that = (LombokLightMethodBuilder) o;
if (!getName().equals(that.getName())) {
return false;
}
if (isConstructor() != that.isConstructor()) {
return false;
}
final PsiClass containingClass = getContainingClass();
final PsiClass thatContainingClass = that.getContainingClass();
if (containingClass != null ? !containingClass.equals(thatContainingClass) : thatContainingClass != null) {
return false;
}
if (!getModifierList().equals(that.getModifierList())) {
return false;
}
if (!getParameterList().equals(that.getParameterList())) {
return false;
}
final PsiType returnType = getReturnType();
final PsiType thatReturnType = that.getReturnType();
if (returnType != null ? !returnType.equals(thatReturnType) : thatReturnType != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
// should be constant because of RenameJavaMethodProcessor#renameElement and fixNameCollisionsWithInnerClassMethod(...)
return 1;
}
@Override
public void delete() throws IncorrectOperationException {
// simple do nothing
}
@Override
public void checkDelete() throws IncorrectOperationException {
// simple do nothing
}
}