/*
* Copyright 2013-2017 consulo.io
*
* 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 consulo.csharp.ide.msil.representation.builder;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.intellij.openapi.util.Condition;
import com.intellij.psi.PsiElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.PairFunction;
import com.intellij.util.containers.ContainerUtil;
import consulo.annotations.RequiredReadAction;
import consulo.csharp.ide.refactoring.util.CSharpNameSuggesterUtil;
import consulo.csharp.lang.psi.*;
import consulo.csharp.lang.psi.impl.source.resolve.type.CSharpStaticTypeRef;
import consulo.dotnet.DotNetTypes;
import consulo.dotnet.psi.*;
import consulo.dotnet.resolve.DotNetTypeRef;
import consulo.dotnet.resolve.DotNetTypeRefUtil;
import consulo.internal.dotnet.msil.decompiler.textBuilder.block.LineStubBlock;
import consulo.internal.dotnet.msil.decompiler.textBuilder.block.StubBlock;
import consulo.internal.dotnet.msil.decompiler.textBuilder.util.StubBlockUtil;
import consulo.msil.lang.psi.MsilConstantValue;
import consulo.msil.lang.psi.MsilCustomAttribute;
/**
* @author VISTALL
* @since 02.06.14
*/
public class CSharpStubBuilderVisitor extends CSharpElementVisitor
{
@NotNull
@RequiredReadAction
public static List<StubBlock> buildBlocks(PsiElement qualifiedElement)
{
return buildBlocks(qualifiedElement, true);
}
@NotNull
@RequiredReadAction
public static List<StubBlock> buildBlocks(PsiElement qualifiedElement, boolean compiled)
{
CSharpStubBuilderVisitor visitor = new CSharpStubBuilderVisitor(compiled);
qualifiedElement.accept(visitor);
return visitor.getBlocks();
}
private boolean myCompiled;
private List<StubBlock> myBlocks = new ArrayList<StubBlock>(2);
public CSharpStubBuilderVisitor(boolean compiled)
{
myCompiled = compiled;
}
@Override
@RequiredReadAction
public void visitPropertyDeclaration(CSharpPropertyDeclaration declaration)
{
StringBuilder builder = new StringBuilder();
processAttributeListAsLine(declaration, myBlocks);
processModifierList(builder, declaration);
appendTypeRef(declaration, builder, declaration.toTypeRef(false));
builder.append(" ");
appendName(declaration, builder);
StubBlock e = new StubBlock(builder, null, StubBlock.BRACES);
for(DotNetXXXAccessor dotNetXXXAccessor : declaration.getAccessors())
{
e.getBlocks().addAll(buildBlocks(dotNetXXXAccessor, myCompiled));
}
myBlocks.add(e);
}
@Override
@RequiredReadAction
public void visitXXXAccessor(DotNetXXXAccessor accessor)
{
DotNetXXXAccessor.Kind accessorKind = accessor.getAccessorKind();
if(accessorKind == null)
{
return;
}
StringBuilder builder = new StringBuilder();
processModifierList(builder, accessor);
builder.append(accessorKind.name().toLowerCase(Locale.US));
boolean canHaveBody = !accessor.hasModifier(CSharpModifier.ABSTRACT);
if(canHaveBody)
{
builder.append("; //compiled code\n");
}
else
{
builder.append(";\n");
}
myBlocks.add(new LineStubBlock(builder));
}
@Override
@RequiredReadAction
public void visitEnumConstantDeclaration(CSharpEnumConstantDeclaration declaration)
{
processAttributeListAsLine(declaration, myBlocks);
StringBuilder builder = new StringBuilder();
builder.append(declaration.getName());
appendInitializer(builder, declaration);
builder.append(",\n");
myBlocks.add(new LineStubBlock(builder));
}
@Override
@RequiredReadAction
public void visitFieldDeclaration(CSharpFieldDeclaration declaration)
{
StringBuilder builder = new StringBuilder();
processAttributeListAsLine(declaration, myBlocks);
processModifierList(builder, declaration);
if(declaration.isConstant())
{
builder.append("const ");
}
appendTypeRef(declaration, builder, declaration.toTypeRef(false));
builder.append(" ");
appendValidName(builder, declaration.getName());
appendInitializer(builder, declaration);
builder.append(";\n");
myBlocks.add(new LineStubBlock(builder));
}
@Override
@RequiredReadAction
public void visitEventDeclaration(CSharpEventDeclaration declaration)
{
StringBuilder builder = new StringBuilder();
processAttributeListAsLine(declaration, myBlocks);
processModifierList(builder, declaration);
builder.append("event ");
appendTypeRef(declaration, builder, declaration.toTypeRef(false));
builder.append(" ");
appendName(declaration, builder);
StubBlock e = new StubBlock(builder, null, StubBlock.BRACES);
for(DotNetXXXAccessor dotNetXXXAccessor : declaration.getAccessors())
{
e.getBlocks().addAll(buildBlocks(dotNetXXXAccessor, myCompiled));
}
myBlocks.add(e);
}
@Override
@RequiredReadAction
public void visitIndexMethodDeclaration(CSharpIndexMethodDeclaration declaration)
{
StringBuilder builder = new StringBuilder();
processAttributeListAsLine(declaration, myBlocks);
processModifierList(builder, declaration);
appendTypeRef(declaration, builder, declaration.getReturnTypeRef());
builder.append(" ");
appendName(declaration, builder);
processParameterList(declaration, builder, '[', ']');
StubBlock e = new StubBlock(builder, null, StubBlock.BRACES);
for(DotNetXXXAccessor dotNetXXXAccessor : declaration.getAccessors())
{
e.getBlocks().addAll(buildBlocks(dotNetXXXAccessor, myCompiled));
}
myBlocks.add(e);
}
@Override
@RequiredReadAction
public void visitConversionMethodDeclaration(CSharpConversionMethodDeclaration declaration)
{
StringBuilder builder = new StringBuilder();
processAttributeListAsLine(declaration, myBlocks);
processModifierList(builder, declaration);
appendTypeRef(declaration, builder, declaration.getConversionTypeRef());
builder.append(" ");
builder.append("operator ");
appendTypeRef(declaration, builder, declaration.getReturnTypeRef());
processParameterList(declaration, builder, '(', ')');
builder.append("; //compiled code\n");
myBlocks.add(new LineStubBlock(builder));
}
@Override
@RequiredReadAction
public void visitConstructorDeclaration(CSharpConstructorDeclaration declaration)
{
StringBuilder builder = new StringBuilder();
if(declaration.isDeConstructor())
{
builder.append("~");
}
else
{
processAttributeListAsLine(declaration, myBlocks);
processModifierList(builder, declaration);
}
builder.append(declaration.getName());
processParameterList(declaration, builder, '(', ')');
builder.append("; //compiled code\n");
myBlocks.add(new LineStubBlock(builder));
}
@Override
@RequiredReadAction
public void visitMethodDeclaration(CSharpMethodDeclaration declaration)
{
StringBuilder builder = new StringBuilder();
processAttributeListAsLine(declaration, myBlocks);
processModifierList(builder, declaration);
if(declaration.isDelegate())
{
builder.append("delegate ");
}
appendTypeRef(declaration, builder, declaration.getReturnTypeRef());
builder.append(" ");
if(declaration.isOperator())
{
builder.append("operator ");
}
appendName(declaration, builder);
processGenericParameterList(builder, declaration);
processParameterList(declaration, builder, '(', ')');
processGenericConstraintList(builder, declaration);
if(myCompiled)
{
boolean canHaveBody = !declaration.hasModifier(CSharpModifier.ABSTRACT) && !declaration.isDelegate();
if(canHaveBody)
{
builder.append("; //compiled code\n");
}
else
{
builder.append(";\n");
}
}
myBlocks.add(new LineStubBlock(builder));
}
@Override
@RequiredReadAction
public void visitTypeDeclaration(final CSharpTypeDeclaration declaration)
{
StringBuilder builder = new StringBuilder();
processAttributeListAsLine(declaration, myBlocks);
processModifierList(builder, declaration);
if(declaration.isEnum())
{
builder.append("enum ");
}
else if(declaration.isStruct())
{
builder.append("struct ");
}
else if(declaration.isInterface())
{
builder.append("interface ");
}
else
{
builder.append("class ");
}
builder.append(declaration.getName());
processGenericParameterList(builder, declaration);
if(declaration.isEnum())
{
DotNetTypeRef typeRefForEnumConstants = declaration.getTypeRefForEnumConstants();
if(!DotNetTypeRefUtil.isVmQNameEqual(typeRefForEnumConstants, declaration, DotNetTypes.System.Int32))
{
builder.append(" : ");
appendTypeRef(declaration, builder, typeRefForEnumConstants);
}
}
else
{
DotNetTypeRef[] extendTypeRefs = declaration.getExtendTypeRefs();
List<DotNetTypeRef> temp = ContainerUtil.filter(extendTypeRefs, new Condition<DotNetTypeRef>()
{
@Override
@RequiredReadAction
public boolean value(DotNetTypeRef typeRef)
{
return !DotNetTypeRefUtil.isVmQNameEqual(typeRef, declaration, DotNetTypes.System.Object) && !DotNetTypeRefUtil.isVmQNameEqual(typeRef, declaration, DotNetTypes.System.ValueType);
}
});
if(!temp.isEmpty())
{
builder.append(" : ");
StubBlockUtil.join(builder, temp, new PairFunction<StringBuilder, DotNetTypeRef, Void>()
{
@Nullable
@Override
@RequiredReadAction
public Void fun(StringBuilder builder, DotNetTypeRef typeRef)
{
appendTypeRef(declaration, builder, typeRef);
return null;
}
}, ", ");
}
}
processGenericConstraintList(builder, declaration);
StubBlock e = new StubBlock(builder, null, StubBlock.BRACES);
myBlocks.add(e);
for(DotNetNamedElement dotNetNamedElement : declaration.getMembers())
{
e.getBlocks().addAll(buildBlocks(dotNetNamedElement, myCompiled));
}
}
@RequiredReadAction
public static void appendTypeRef(@NotNull final PsiElement scope, @NotNull StringBuilder builder, @NotNull DotNetTypeRef typeRef)
{
CSharpTypeRefPresentationUtil.appendTypeRef(scope, builder, typeRef, CSharpTypeRefPresentationUtil.QUALIFIED_NAME | CSharpTypeRefPresentationUtil.TYPE_KEYWORD);
}
@RequiredReadAction
private static <T extends DotNetVirtualImplementOwner & DotNetNamedElement> void appendName(T element, StringBuilder builder)
{
DotNetTypeRef typeRefForImplement = element.getTypeRefForImplement();
if(typeRefForImplement != DotNetTypeRef.ERROR_TYPE)
{
appendTypeRef(element, builder, typeRefForImplement);
builder.append(".");
if(element instanceof CSharpIndexMethodDeclaration)
{
builder.append("this");
}
else
{
appendValidName(builder, element.getName());
}
}
else
{
if(element instanceof CSharpIndexMethodDeclaration)
{
builder.append("this");
}
else
{
appendValidName(builder, element.getName());
}
}
}
private static void processGenericParameterList(StringBuilder builder, DotNetGenericParameterListOwner owner)
{
DotNetGenericParameter[] genericParameters = owner.getGenericParameters();
if(genericParameters.length == 0)
{
return;
}
builder.append("<");
StubBlockUtil.join(builder, genericParameters, new PairFunction<StringBuilder, DotNetGenericParameter, Void>()
{
@Nullable
@Override
@RequiredReadAction
public Void fun(StringBuilder t, DotNetGenericParameter v)
{
appendAttributeList(v, t, v.getAttributes());
if(v.hasModifier(CSharpModifier.OUT))
{
t.append("out ");
}
else if(v.hasModifier(CSharpModifier.IN))
{
t.append("in ");
}
t.append(v.getName());
return null;
}
}, ", ");
builder.append(">");
}
private static void processGenericConstraintList(final StringBuilder builder, final CSharpGenericConstraintOwner owner)
{
CSharpGenericConstraint[] genericConstraints = owner.getGenericConstraints();
if(genericConstraints.length == 0)
{
return;
}
builder.append(" ");
StubBlockUtil.join(builder, genericConstraints, new PairFunction<StringBuilder, CSharpGenericConstraint, Void>()
{
@Nullable
@Override
public Void fun(final StringBuilder builder, CSharpGenericConstraint v)
{
builder.append("where ");
DotNetGenericParameter resolve = v.resolve();
assert resolve != null;
builder.append(resolve.getName());
builder.append(" : ");
CSharpGenericConstraintValue[] genericConstraintValues = v.getGenericConstraintValues();
for(CSharpGenericConstraintValue genericConstraintValue : genericConstraintValues)
{
genericConstraintValue.accept(new CSharpElementVisitor()
{
@Override
public void visitGenericConstraintKeywordValue(CSharpGenericConstraintKeywordValue value)
{
IElementType keywordElementType = value.getKeywordElementType();
if(keywordElementType == CSharpTokens.STRUCT_KEYWORD)
{
builder.append("struct");
}
else if(keywordElementType == CSharpTokens.CLASS_KEYWORD)
{
builder.append("class");
}
else if(keywordElementType == CSharpTokens.NEW_KEYWORD)
{
builder.append("new()");
}
}
@Override
@RequiredReadAction
public void visitGenericConstraintTypeValue(CSharpGenericConstraintTypeValue value)
{
appendTypeRef(owner, builder, value.toTypeRef());
}
});
}
return null;
}
}, " ");
}
private static void processParameterList(final DotNetParameterListOwner declaration, StringBuilder builder, char p1, char p2)
{
builder.append(p1);
StubBlockUtil.join(builder, declaration.getParameters(), new PairFunction<StringBuilder, DotNetParameter, Void>()
{
@Nullable
@Override
@RequiredReadAction
public Void fun(StringBuilder t, DotNetParameter v)
{
appendAttributeList(t, v);
processModifierList(t, v);
DotNetTypeRef typeRef = v.toTypeRef(false);
appendTypeRef(declaration, t, typeRef);
if(typeRef != CSharpStaticTypeRef.__ARGLIST_TYPE)
{
t.append(" ");
appendValidName(t, v.getName());
appendInitializer(t, v);
}
return null;
}
}, ", ");
builder.append(p2);
}
@RequiredReadAction
public static void processAttributeListAsLine(DotNetModifierListOwner owner, List<StubBlock> blocks)
{
DotNetModifierList modifierList = owner.getModifierList();
if(modifierList == null)
{
return;
}
processAttributeListAsLine(owner, blocks, null, modifierList.getAttributes());
}
@RequiredReadAction
public static void processAttributeListAsLine(PsiElement scope, List<StubBlock> blocks, DotNetAttributeTargetType targetType, DotNetAttribute[] attributes)
{
for(DotNetAttribute dotNetAttribute : attributes)
{
StringBuilder builder = new StringBuilder();
builder.append("[");
if(targetType != null)
{
builder.append(targetType.name().toLowerCase(Locale.US)).append(": ");
}
appendTypeRef(scope, builder, dotNetAttribute.toTypeRef());
if(dotNetAttribute instanceof CSharpAttribute)
{
DotNetExpression[] parameterExpressions = ((CSharpAttribute) dotNetAttribute).getParameterExpressions();
if(parameterExpressions.length > 0)
{
builder.append("(");
StubBlockUtil.join(builder, parameterExpressions, new PairFunction<StringBuilder, DotNetExpression, Void>()
{
@Nullable
@Override
@RequiredReadAction
public Void fun(StringBuilder builder, DotNetExpression dotNetExpression)
{
builder.append(dotNetExpression.getText());
return null;
}
}, ", ");
builder.append(")");
}
}
else if(dotNetAttribute instanceof MsilCustomAttribute)
{
CSharpAttributeStubBuilder.append(builder, (MsilCustomAttribute) dotNetAttribute);
}
builder.append("]");
blocks.add(new LineStubBlock(builder));
}
}
@RequiredReadAction
private static void appendAttributeList(final StringBuilder builder, final DotNetModifierListOwner owner)
{
DotNetModifierList modifierList = owner.getModifierList();
if(modifierList == null)
{
return;
}
DotNetAttribute[] attributes = modifierList.getAttributes();
if(attributes.length == 0)
{
return;
}
appendAttributeList(owner, builder, attributes);
}
@RequiredReadAction
private static void appendAttributeList(final PsiElement scope, final StringBuilder builder, final DotNetAttribute[] attributes)
{
if(attributes.length == 0)
{
return;
}
builder.append("[");
StubBlockUtil.join(builder, attributes, new PairFunction<StringBuilder, DotNetAttribute, Void>()
{
@Override
@RequiredReadAction
public Void fun(StringBuilder builder, DotNetAttribute dotNetAttribute)
{
appendTypeRef(scope, builder, dotNetAttribute.toTypeRef());
if(dotNetAttribute instanceof CSharpAttribute)
{
DotNetExpression[] parameterExpressions = ((CSharpAttribute) dotNetAttribute).getParameterExpressions();
if(parameterExpressions.length > 0)
{
builder.append("(");
StubBlockUtil.join(builder, parameterExpressions, new PairFunction<StringBuilder, DotNetExpression, Void>()
{
@Nullable
@Override
@RequiredReadAction
public Void fun(StringBuilder builder, DotNetExpression dotNetExpression)
{
builder.append(dotNetExpression.getText());
return null;
}
}, ", ");
builder.append(")");
}
}
else if(dotNetAttribute instanceof MsilCustomAttribute)
{
CSharpAttributeStubBuilder.append(builder, (MsilCustomAttribute) dotNetAttribute);
}
return null;
}
}, ", ");
builder.append("] ");
}
@RequiredReadAction
private static void processModifierList(StringBuilder builder, DotNetModifierListOwner owner)
{
DotNetModifierList modifierList = owner.getModifierList();
if(modifierList == null)
{
return;
}
for(DotNetModifier dotNetModifier : modifierList.getModifiers())
{
if(dotNetModifier == CSharpModifier.OUT || dotNetModifier == CSharpModifier.IN)
{
continue;
}
if(owner instanceof DotNetVariable && ((DotNetVariable) owner).isConstant() && dotNetModifier == CSharpModifier.STATIC)
{
continue;
}
if(dotNetModifier == CSharpModifier.ABSTRACT && (isInterface(owner) || isInterface(owner.getParent())))
{
continue;
}
if(dotNetModifier == CSharpModifier.PUBLIC && isInterface(owner.getParent()))
{
continue;
}
if(dotNetModifier == CSharpModifier.VIRTUAL && CSharpMethodUtil.isDelegate(owner))
{
continue;
}
if(dotNetModifier == CSharpModifier.STATIC && owner instanceof DotNetXXXAccessor)
{
continue;
}
if((dotNetModifier == CSharpModifier.ABSTRACT || dotNetModifier == CSharpModifier.PUBLIC) && owner instanceof DotNetXXXAccessor &&
isInterface(owner.getParent().getParent()))
{
continue;
}
builder.append(dotNetModifier.getPresentableText()).append(" ");
}
}
private static boolean isInterface(@Nullable PsiElement element)
{
return element instanceof CSharpTypeDeclaration && ((CSharpTypeDeclaration) element).isInterface();
}
public static void appendValidName(StringBuilder builder, String name)
{
if(CSharpNameSuggesterUtil.isKeyword(name))
{
builder.append("@");
}
builder.append(name);
}
@RequiredReadAction
private static void appendInitializer(StringBuilder builder, DotNetVariable variable)
{
DotNetExpression initializer = variable.getInitializer();
if(initializer instanceof MsilConstantValue)
{
IElementType valueType = ((MsilConstantValue) initializer).getValueType();
if(valueType == null)
{
return;
}
String valueText = ((MsilConstantValue) initializer).getValueText();
if(valueText == null)
{
return;
}
builder.append(" = ").append(valueText);
}
else if(initializer != null)
{
builder.append(" = ").append(initializer.getText());
}
}
public List<StubBlock> getBlocks()
{
return myBlocks;
}
}