/*
* 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.source.resolve.overrideSystem;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import org.jetbrains.annotations.NotNull;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Condition;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.ResolveResult;
import com.intellij.psi.ResolveState;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.CommonProcessors;
import com.intellij.util.Processor;
import com.intellij.util.Query;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import consulo.annotations.RequiredReadAction;
import consulo.csharp.lang.psi.CSharpElementCompareUtil;
import consulo.csharp.lang.psi.CSharpIndexMethodDeclaration;
import consulo.csharp.lang.psi.CSharpMethodDeclaration;
import consulo.csharp.lang.psi.CSharpModifier;
import consulo.csharp.lang.psi.impl.msil.CSharpTransform;
import consulo.csharp.lang.psi.impl.resolve.CSharpElementGroupImpl;
import consulo.csharp.lang.psi.impl.resolve.CSharpResolveContextUtil;
import consulo.csharp.lang.psi.impl.source.resolve.ExecuteTarget;
import consulo.csharp.lang.psi.impl.source.resolve.ExecuteTargetUtil;
import consulo.csharp.lang.psi.impl.source.resolve.MemberResolveScopeProcessor;
import consulo.csharp.lang.psi.impl.source.resolve.util.CSharpResolveUtil;
import consulo.csharp.lang.psi.resolve.CSharpElementGroup;
import consulo.csharp.lang.psi.resolve.CSharpResolveContext;
import consulo.csharp.lang.psi.resolve.CSharpResolveSelector;
import consulo.csharp.lang.psi.resolve.MemberByNameSelector;
import consulo.csharp.lang.psi.resolve.StaticResolveSelectors;
import consulo.dotnet.psi.DotNetLikeMethodDeclaration;
import consulo.dotnet.psi.DotNetModifier;
import consulo.dotnet.psi.DotNetModifierList;
import consulo.dotnet.psi.DotNetModifierListOwner;
import consulo.dotnet.psi.DotNetType;
import consulo.dotnet.psi.DotNetTypeDeclaration;
import consulo.dotnet.psi.DotNetVariable;
import consulo.dotnet.psi.DotNetVirtualImplementOwner;
import consulo.dotnet.psi.search.searches.TypeInheritorsSearch;
import consulo.dotnet.resolve.DotNetGenericExtractor;
/**
* @author VISTALL
* @since 14.12.14
*/
public class OverrideUtil
{
private static final Logger LOGGER = Logger.getInstance(OverrideUtil.class);
@RequiredReadAction
public static CSharpModifier getRequiredOverrideModifier(@NotNull DotNetModifierListOwner modifierListOwner)
{
DotNetModifierList modifierList = modifierListOwner.getModifierList();
if(modifierList == null)
{
return null;
}
if(modifierList.hasModifier(CSharpModifier.INTERFACE_ABSTRACT))
{
return null;
}
if(modifierList.hasModifierInTree(CSharpModifier.ABSTRACT) || modifierList.hasModifierInTree(CSharpModifier.VIRTUAL) || modifierList
.hasModifierInTree(CSharpModifier.OVERRIDE))
{
return CSharpModifier.OVERRIDE;
}
return CSharpModifier.NEW;
}
@NotNull
public static PsiElement[] filterOverrideElements(@NotNull PsiScopeProcessor processor,
@NotNull PsiElement scopeElement,
@NotNull PsiElement[] psiElements,
@NotNull OverrideProcessor overrideProcessor)
{
if(psiElements.length == 0)
{
return psiElements;
}
if(!ExecuteTargetUtil.canProcess(processor, ExecuteTarget.ELEMENT_GROUP, ExecuteTarget.EVENT, ExecuteTarget.PROPERTY))
{
List<PsiElement> elements = CSharpResolveUtil.mergeGroupsToIterable(psiElements);
return ContainerUtil.toArray(elements, PsiElement.ARRAY_FACTORY);
}
List<PsiElement> elements = CSharpResolveUtil.mergeGroupsToIterable(psiElements);
return filterOverrideElements(scopeElement, elements, overrideProcessor);
}
@NotNull
@SuppressWarnings("unchecked")
public static PsiElement[] filterOverrideElements(@NotNull PsiElement scopeElement,
@NotNull Collection<PsiElement> elements,
@NotNull OverrideProcessor overrideProcessor)
{
List<PsiElement> copyElements = new ArrayList<PsiElement>(elements);
for(PsiElement element : elements)
{
if(!copyElements.contains(element))
{
continue;
}
if(element instanceof DotNetVirtualImplementOwner)
{
if(element instanceof CSharpMethodDeclaration && ((CSharpMethodDeclaration) element).isDelegate())
{
continue;
}
DotNetVirtualImplementOwner virtualImplementOwner = (DotNetVirtualImplementOwner) element;
DotNetType typeForImplement = virtualImplementOwner.getTypeForImplement();
for(PsiElement tempIterateElement : elements)
{
// skip self
if(tempIterateElement == element)
{
continue;
}
if(CSharpElementCompareUtil.isEqual(tempIterateElement, element, CSharpElementCompareUtil.CHECK_RETURN_TYPE, scopeElement))
{
if(!overrideProcessor.elementOverride(virtualImplementOwner, (DotNetVirtualImplementOwner) tempIterateElement))
{
return PsiElement.EMPTY_ARRAY;
}
copyElements.remove(tempIterateElement);
}
}
// if he have hide impl, remove it
if(typeForImplement != null)
{
copyElements.remove(element);
}
}
}
List<PsiElement> groupElements = new SmartList<PsiElement>();
List<PsiElement> elseElements = new SmartList<PsiElement>();
for(PsiElement copyElement : copyElements)
{
if(copyElement instanceof DotNetLikeMethodDeclaration)
{
groupElements.add(copyElement);
}
else
{
elseElements.add(copyElement);
}
}
if(elseElements.isEmpty() && groupElements.isEmpty())
{
return PsiElement.EMPTY_ARRAY;
}
else if(elseElements.isEmpty())
{
return new PsiElement[]{
new CSharpElementGroupImpl<PsiElement>(scopeElement.getProject(), getNameForGroup(groupElements), groupElements)
};
}
else if(groupElements.isEmpty())
{
return ContainerUtil.toArray(elseElements, PsiElement.ARRAY_FACTORY);
}
else
{
elseElements.add(new CSharpElementGroupImpl<PsiElement>(scopeElement.getProject(), getNameForGroup(groupElements), groupElements));
return ContainerUtil.toArray(elseElements, PsiElement.ARRAY_FACTORY);
}
}
@NotNull
private static String getNameForGroup(List<PsiElement> elements)
{
assert !elements.isEmpty();
PsiElement element = elements.get(0);
if(element instanceof DotNetVariable)
{
return ((DotNetVariable) element).getName();
}
else if(element instanceof CSharpIndexMethodDeclaration)
{
return "this[]";
}
else if(element instanceof DotNetLikeMethodDeclaration)
{
String name = ((DotNetLikeMethodDeclaration) element).getName();
assert name != null : element.getClass().getName();
return name;
}
else
{
LOGGER.error(element.getClass() + " is not handled");
return "override";
}
}
public static boolean isAllowForOverride(PsiElement parent)
{
if(parent instanceof CSharpMethodDeclaration &&
!((CSharpMethodDeclaration) parent).isDelegate() && !((CSharpMethodDeclaration) parent).hasModifier(DotNetModifier.STATIC))
{
return true;
}
return parent instanceof DotNetVirtualImplementOwner;
}
@NotNull
@RequiredReadAction
public static Collection<DotNetVirtualImplementOwner> collectOverridingMembers(final DotNetVirtualImplementOwner target)
{
PsiElement parent = target.getParent();
if(parent == null)
{
return Collections.emptyList();
}
OverrideProcessor.Collector overrideProcessor = new OverrideProcessor.Collector();
MemberResolveScopeProcessor processor = new MemberResolveScopeProcessor(parent, CommonProcessors.<ResolveResult>alwaysTrue(),
new ExecuteTarget[]{
ExecuteTarget.MEMBER,
ExecuteTarget.ELEMENT_GROUP
}, overrideProcessor);
ResolveState state = ResolveState.initial();
if(target instanceof CSharpIndexMethodDeclaration)
{
state = state.put(CSharpResolveUtil.SELECTOR, StaticResolveSelectors.INDEX_METHOD_GROUP);
}
else
{
String name = ((PsiNamedElement) target).getName();
if(name == null)
{
return Collections.emptyList();
}
state = state.put(CSharpResolveUtil.SELECTOR, new MemberByNameSelector(name));
}
CSharpResolveUtil.walkChildren(processor, parent, false, true, state);
List<DotNetVirtualImplementOwner> results = overrideProcessor.getResults();
// need filter result due it ill return all elements with target selector
ListIterator<DotNetVirtualImplementOwner> listIterator = results.listIterator();
while(listIterator.hasNext())
{
DotNetVirtualImplementOwner next = listIterator.next();
if(!CSharpElementCompareUtil.isEqual(next, target, CSharpElementCompareUtil.CHECK_RETURN_TYPE, target))
{
listIterator.remove();
}
}
return results;
}
@NotNull
@RequiredReadAction
public static Collection<DotNetVirtualImplementOwner> collectOverridenMembers(final DotNetVirtualImplementOwner target)
{
PsiElement parent = target.getParent();
if(!(parent instanceof DotNetTypeDeclaration))
{
return Collections.emptyList();
}
final CSharpResolveSelector selector;
if(target instanceof CSharpIndexMethodDeclaration)
{
selector = StaticResolveSelectors.INDEX_METHOD_GROUP;
}
else
{
String name = ((PsiNamedElement) target).getName();
if(name != null)
{
selector = new MemberByNameSelector(name);
}
else
{
selector = null;
}
}
if(selector == null)
{
return Collections.emptyList();
}
final GlobalSearchScope resolveScope = target.getResolveScope();
final List<DotNetVirtualImplementOwner> list = new ArrayList<DotNetVirtualImplementOwner>();
Query<DotNetTypeDeclaration> search = TypeInheritorsSearch.search((DotNetTypeDeclaration) parent, true, CSharpTransform.INSTANCE);
search.forEach(new Processor<DotNetTypeDeclaration>()
{
@Override
@RequiredReadAction
public boolean process(DotNetTypeDeclaration typeDeclaration)
{
CSharpResolveContext context = CSharpResolveContextUtil.createContext(DotNetGenericExtractor.EMPTY, resolveScope, typeDeclaration);
PsiElement[] elements = selector.doSelectElement(context, false);
for(PsiElement element : CSharpResolveUtil.mergeGroupsToIterable(elements))
{
if(element == target)
{
continue;
}
if(CSharpElementCompareUtil.isEqual(element, target, CSharpElementCompareUtil.CHECK_RETURN_TYPE, target))
{
list.add((DotNetVirtualImplementOwner) element);
}
}
return true;
}
});
return list;
}
@NotNull
@RequiredReadAction
public static Collection<DotNetModifierListOwner> collectMembersWithModifier(@NotNull PsiElement element,
@NotNull DotNetGenericExtractor extractor,
@NotNull CSharpModifier modifier)
{
List<DotNetModifierListOwner> psiElements = new SmartList<DotNetModifierListOwner>();
for(PsiElement psiElement : getAllMembers(element, element.getResolveScope(), extractor, false, true))
{
if(psiElement instanceof DotNetModifierListOwner && ((DotNetModifierListOwner) psiElement).hasModifier(modifier))
{
psiElements.add((DotNetModifierListOwner) psiElement);
}
}
return psiElements;
}
@NotNull
@RequiredReadAction
public static Collection<PsiElement> getAllMembers(@NotNull final PsiElement targetTypeDeclaration,
@NotNull GlobalSearchScope scope,
@NotNull DotNetGenericExtractor extractor,
boolean completion,
boolean overrideTool)
{
final CommonProcessors.CollectProcessor<PsiElement> collectProcessor = new CommonProcessors.CollectProcessor<PsiElement>();
CSharpResolveContext context = CSharpResolveContextUtil.createContext(extractor, scope, targetTypeDeclaration);
// process method & properties
context.processElements(collectProcessor, true);
// process index methods
CSharpElementGroup<CSharpIndexMethodDeclaration> group = context.indexMethodGroup(true);
if(group != null)
{
group.process(collectProcessor);
}
Collection<PsiElement> results = collectProcessor.getResults();
List<PsiElement> mergedElements = CSharpResolveUtil.mergeGroupsToIterable(results);
PsiElement[] psiElements = OverrideUtil.filterOverrideElements(targetTypeDeclaration, mergedElements, OverrideProcessor.ALWAYS_TRUE);
List<PsiElement> elements = CSharpResolveUtil.mergeGroupsToIterable(psiElements);
if(overrideTool)
{
// filter self methods, we need it in circular extends
elements = ContainerUtil.filter(elements, new Condition<PsiElement>()
{
@Override
public boolean value(PsiElement element)
{
return element.getParent() != targetTypeDeclaration;
}
});
}
return completion ? elements : ContainerUtil.filter(elements, new Condition<PsiElement>()
{
@Override
public boolean value(PsiElement element)
{
return !(element instanceof DotNetTypeDeclaration);
}
});
}
}