/*
* 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.lang.psi.impl.msil;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import consulo.annotations.RequiredReadAction;
import consulo.csharp.lang.psi.CSharpMethodDeclaration;
import consulo.csharp.lang.psi.CSharpModifier;
import consulo.csharp.lang.psi.impl.DotNetTypes2;
import consulo.csharp.lang.psi.impl.light.builder.CSharpLightNamespaceDeclarationBuilder;
import consulo.csharp.lang.psi.impl.source.resolve.type.CSharpArrayTypeRef;
import consulo.csharp.lang.psi.impl.source.resolve.type.CSharpGenericWrapperTypeRef;
import consulo.csharp.lang.psi.impl.source.resolve.type.CSharpPointerTypeRef;
import consulo.csharp.lang.psi.impl.source.resolve.type.CSharpRefTypeRef;
import consulo.csharp.lang.psi.impl.source.resolve.type.CSharpTypeRefByQName;
import consulo.dotnet.DotNetTypes;
import consulo.dotnet.psi.DotNetAttribute;
import consulo.dotnet.psi.DotNetGenericParameterListOwner;
import consulo.dotnet.psi.DotNetInheritUtil;
import consulo.dotnet.psi.DotNetModifier;
import consulo.dotnet.psi.DotNetModifierList;
import consulo.dotnet.psi.DotNetModifierListOwner;
import consulo.dotnet.psi.DotNetNamedElement;
import consulo.dotnet.psi.DotNetTypeDeclaration;
import consulo.dotnet.resolve.DotNetGenericWrapperTypeRef;
import consulo.dotnet.resolve.DotNetPointerTypeRef;
import consulo.dotnet.resolve.DotNetRefTypeRef;
import consulo.dotnet.resolve.DotNetTypeRef;
import consulo.msil.lang.psi.MsilClassEntry;
import consulo.msil.lang.psi.MsilMethodEntry;
import consulo.msil.lang.psi.MsilModifierElementType;
import consulo.msil.lang.psi.MsilTokens;
import consulo.msil.lang.psi.impl.type.MsilArrayTypRefImpl;
import consulo.msil.lang.psi.impl.type.MsilNativeTypeRefImpl;
import consulo.msil.lang.psi.impl.type.MsilPointerTypeRefImpl;
import consulo.msil.lang.psi.impl.type.MsilRefTypeRefImpl;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
/**
* @author VISTALL
* @since 22.05.14
*/
public class MsilToCSharpUtil
{
private static final Key<PsiElement> MSIL_WRAPPER_VALUE = Key.create("msil.to.csharp.wrapper.key");
@RequiredReadAction
public static boolean hasCSharpInMsilModifierList(CSharpModifier modifier, DotNetModifierList modifierList)
{
MsilModifierElementType elementType = null;
switch(modifier)
{
case PUBLIC:
if(hasModifierInParentIfType(modifierList, MsilTokens.INTERFACE_KEYWORD))
{
return false;
}
elementType = MsilTokens.PUBLIC_KEYWORD;
break;
case PRIVATE:
elementType = MsilTokens.PRIVATE_KEYWORD;
break;
case PROTECTED:
elementType = MsilTokens.PROTECTED_KEYWORD;
break;
case STATIC:
// all members with extension attribute are static
if(hasAttribute(modifierList, DotNetTypes.System.Runtime.CompilerServices.ExtensionAttribute))
{
return true;
}
elementType = MsilTokens.STATIC_KEYWORD;
break;
case SEALED:
// hide sealed attribute
if(hasAttribute(modifierList, DotNetTypes.System.Runtime.CompilerServices.ExtensionAttribute))
{
return false;
}
elementType = MsilTokens.SEALED_KEYWORD;
break;
case ASYNC:
return hasAttribute(modifierList, DotNetTypes2.System.Runtime.CompilerServices.AsyncStateMachineAttribute);
case INTERNAL:
elementType = MsilTokens.ASSEMBLY_KEYWORD;
break;
case VIRTUAL:
if(hasModifierInParentIfType(modifierList, MsilTokens.INTERFACE_KEYWORD))
{
return false;
}
elementType = MsilTokens.VIRTUAL_KEYWORD;
break;
case READONLY:
elementType = MsilTokens.INITONLY_KEYWORD;
break;
case UNSAFE:
break;
case PARAMS:
return hasAttribute(modifierList, DotNetTypes.System.ParamArrayAttribute);
case ABSTRACT:
if(hasModifierInParentIfType(modifierList, MsilTokens.INTERFACE_KEYWORD))
{
return false;
}
// hide abstract attribute
if(hasAttribute(modifierList, DotNetTypes.System.Runtime.CompilerServices.ExtensionAttribute))
{
return false;
}
elementType = MsilTokens.ABSTRACT_KEYWORD;
break;
}
return elementType != null && modifierList.hasModifier(elementType);
}
@SuppressWarnings("unchecked")
@RequiredReadAction
private static <T extends DotNetModifierListOwner> boolean hasModifierInParentIfType(DotNetModifierList msilModifierList, DotNetModifier modifier)
{
PsiElement parent = msilModifierList.getParent();
if(parent == null)
{
return false;
}
PsiElement parent1 = parent.getParent();
if(!(parent1 instanceof MsilClassEntry))
{
return false;
}
T modifierListOwner = (T) parent1;
return modifierListOwner.hasModifier(modifier);
}
@RequiredReadAction
private static boolean hasAttribute(DotNetModifierList modifierList, String qName)
{
for(DotNetAttribute attribute : modifierList.getAttributes())
{
DotNetTypeDeclaration typeDeclaration = attribute.resolveToType();
if(typeDeclaration != null && Comparing.equal(typeDeclaration.getPresentableQName(), qName))
{
return true;
}
}
return false;
}
@NotNull
@RequiredReadAction
public static PsiElement wrap(@NotNull PsiElement element, @Nullable PsiElement parent)
{
return wrap(element, parent, new GenericParameterContext(null));
}
@NotNull
@RequiredReadAction
public static PsiElement wrap(@NotNull PsiElement element, @Nullable PsiElement parent, @NotNull GenericParameterContext context)
{
if(element instanceof MsilClassEntry)
{
PsiElement wrapElement = null;
if(parent == null)
{
// do not return already wrapped element when we have parent(for nested classes) due we will override parent
wrapElement = element.getUserData(MSIL_WRAPPER_VALUE);
if(wrapElement != null)
{
return wrapElement;
}
}
if(parent == null)
{
String parentQName = ((MsilClassEntry) element).getPresentableParentQName();
if(!StringUtil.isEmpty(parentQName))
{
parent = new CSharpLightNamespaceDeclarationBuilder(element.getProject(), parentQName);
}
}
wrapElement = wrapToDelegateMethod((MsilClassEntry) element, parent, context);
if(wrapElement == null)
{
wrapElement = new MsilClassAsCSharpTypeDefinition(parent, (MsilClassEntry) element, context);
}
element.putUserData(MSIL_WRAPPER_VALUE, wrapElement);
return wrapElement;
}
return element;
}
@Nullable
@RequiredReadAction
private static CSharpMethodDeclaration wrapToDelegateMethod(@NotNull MsilClassEntry typeDeclaration, @Nullable PsiElement parent, @NotNull GenericParameterContext context)
{
if(DotNetInheritUtil.isInheritor(typeDeclaration, DotNetTypes.System.MulticastDelegate, false))
{
MsilMethodEntry msilMethodEntry = (MsilMethodEntry) ContainerUtil.find((typeDeclaration).getMembers(), new Condition<DotNetNamedElement>()
{
@Override
public boolean value(DotNetNamedElement element)
{
return element instanceof MsilMethodEntry && Comparing.equal(element.getName(), "Invoke");
}
});
assert msilMethodEntry != null : typeDeclaration.getPresentableQName();
return new MsilMethodAsCSharpMethodDeclaration(parent, typeDeclaration, context, msilMethodEntry);
}
else
{
return null;
}
}
@NotNull
@RequiredReadAction
public static DotNetTypeRef extractToCSharp(@NotNull DotNetTypeRef typeRef, @NotNull PsiElement scope)
{
if(typeRef == DotNetTypeRef.ERROR_TYPE)
{
return DotNetTypeRef.ERROR_TYPE;
}
if(typeRef instanceof MsilNativeTypeRefImpl)
{
return new CSharpTypeRefByQName(scope, typeRef.toString());
}
else if(typeRef instanceof MsilArrayTypRefImpl)
{
int[] lowerValues = ((MsilArrayTypRefImpl) typeRef).getLowerValues();
return new CSharpArrayTypeRef(scope, extractToCSharp(((MsilArrayTypRefImpl) typeRef).getInnerTypeRef(), scope), lowerValues.length == 0 ? 0 : lowerValues.length - 1);
}
else if(typeRef instanceof MsilPointerTypeRefImpl)
{
return new CSharpPointerTypeRef(scope, extractToCSharp(((DotNetPointerTypeRef) typeRef).getInnerTypeRef(), scope));
}
else if(typeRef instanceof MsilRefTypeRefImpl)
{
DotNetTypeRef innerTypeRef = extractToCSharp(((DotNetRefTypeRef) typeRef).getInnerTypeRef(), scope);
return new CSharpRefTypeRef(CSharpRefTypeRef.Type.ref, innerTypeRef);
}
else if(typeRef instanceof DotNetGenericWrapperTypeRef)
{
DotNetTypeRef innerTypeRef = ((DotNetGenericWrapperTypeRef) typeRef).getInnerTypeRef();
DotNetTypeRef[] arguments = ((DotNetGenericWrapperTypeRef) typeRef).getArgumentTypeRefs();
DotNetTypeRef inner = extractToCSharp(innerTypeRef, scope);
PsiElement element = inner.resolve().getElement();
if(element instanceof DotNetGenericParameterListOwner)
{
int genericParametersCount = ((DotNetGenericParameterListOwner) element).getGenericParametersCount();
if(genericParametersCount == 0)
{
return inner;
}
DotNetTypeRef[] anotherArray = ArrayUtil.reverseArray(arguments);
List<DotNetTypeRef> list = new ArrayList<DotNetTypeRef>(genericParametersCount);
for(int i = 0; i < genericParametersCount; i++)
{
list.add(extractToCSharp(anotherArray[i], scope));
}
list = ContainerUtil.reverse(list);
return new CSharpGenericWrapperTypeRef(inner, ContainerUtil.toArray(list, DotNetTypeRef.ARRAY_FACTORY));
}
else // fallback
{
DotNetTypeRef[] newArguments = new DotNetTypeRef[arguments.length];
for(int i = 0; i < newArguments.length; i++)
{
newArguments[i] = extractToCSharp(arguments[i], scope);
}
return new CSharpGenericWrapperTypeRef(inner, newArguments);
}
}
return new MsilDelegateTypeRef(scope, typeRef);
}
}