/*
* 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.extensionResolver;
import java.util.Collection;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import consulo.annotations.RequiredReadAction;
import consulo.csharp.lang.psi.CSharpCallArgument;
import consulo.csharp.lang.psi.CSharpCallArgumentListOwner;
import consulo.csharp.lang.psi.CSharpMethodDeclaration;
import consulo.csharp.lang.psi.CSharpModifier;
import consulo.csharp.lang.psi.CSharpReferenceExpression;
import consulo.csharp.lang.psi.impl.CSharpTypeUtil;
import consulo.csharp.lang.psi.impl.light.CSharpLightMethodDeclaration;
import consulo.csharp.lang.psi.impl.light.CSharpLightParameterList;
import consulo.csharp.lang.psi.impl.resolve.CSharpElementGroupImpl;
import consulo.csharp.lang.psi.impl.resolve.CSharpResolveContextUtil;
import consulo.csharp.lang.psi.impl.source.resolve.CSharpResolveResult;
import consulo.csharp.lang.psi.impl.source.resolve.StubScopeProcessor;
import consulo.csharp.lang.psi.impl.source.resolve.genericInference.GenericInferenceUtil;
import consulo.csharp.lang.psi.impl.source.resolve.type.wrapper.GenericUnwrapTool;
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.dotnet.psi.DotNetParameter;
import consulo.dotnet.psi.DotNetParameterList;
import consulo.dotnet.psi.DotNetParameterListOwner;
import consulo.dotnet.resolve.DotNetGenericExtractor;
import consulo.dotnet.resolve.DotNetTypeRef;
import com.intellij.pom.Navigatable;
import com.intellij.psi.PsiElement;
import com.intellij.psi.ResolveState;
import com.intellij.util.Processor;
import com.intellij.util.SmartList;
/**
* @author VISTALL
* @since 26.03.14
*/
public class ExtensionResolveScopeProcessor extends StubScopeProcessor
{
private final CSharpReferenceExpression myExpression;
private final boolean myCompletion;
private final StubScopeProcessor myProcessor;
@Nullable
private final CSharpCallArgumentListOwner myCallArgumentListOwner;
private final DotNetTypeRef myQualifierTypeRef;
private final List<CSharpMethodDeclaration> myResolvedElements = new SmartList<CSharpMethodDeclaration>();
private ExtensionQualifierAsCallArgumentWrapper myArgumentWrapper;
public ExtensionResolveScopeProcessor(@NotNull DotNetTypeRef qualifierTypeRef,
@NotNull CSharpReferenceExpression expression,
boolean completion,
@NotNull StubScopeProcessor processor,
@Nullable CSharpCallArgumentListOwner callArgumentListOwner)
{
myQualifierTypeRef = qualifierTypeRef;
myExpression = expression;
myCompletion = completion;
myProcessor = processor;
myCallArgumentListOwner = callArgumentListOwner;
myArgumentWrapper = new ExtensionQualifierAsCallArgumentWrapper(expression.getProject(), qualifierTypeRef);
}
@RequiredReadAction
@Override
public boolean execute(@NotNull final PsiElement element, ResolveState state)
{
if(myCompletion)
{
DotNetGenericExtractor extractor = state.get(CSharpResolveUtil.EXTRACTOR);
assert extractor != null;
CSharpResolveContext context = CSharpResolveContextUtil.createContext(extractor, myExpression.getResolveScope(), element);
context.processExtensionMethodGroups(new Processor<CSharpElementGroup<CSharpMethodDeclaration>>()
{
@Override
@RequiredReadAction
public boolean process(CSharpElementGroup<CSharpMethodDeclaration> elementGroup)
{
Collection<CSharpMethodDeclaration> elements = elementGroup.getElements();
for(CSharpMethodDeclaration psiElement : elements)
{
GenericInferenceUtil.GenericInferenceResult inferenceResult = inferenceGenericExtractor(psiElement);
DotNetTypeRef firstParameterTypeRef = getFirstTypeRefOrParameter(psiElement, inferenceResult.getExtractor());
if(!CSharpTypeUtil.isInheritableWithImplicit(firstParameterTypeRef, myQualifierTypeRef, myExpression))
{
continue;
}
myProcessor.pushResultExternally(new CSharpResolveResult(transform(psiElement, inferenceResult, null)));
}
return true;
}
});
}
else
{
CSharpResolveSelector selector = state.get(CSharpResolveUtil.SELECTOR);
if(selector == null)
{
return true;
}
DotNetGenericExtractor extractor = state.get(CSharpResolveUtil.EXTRACTOR);
assert extractor != null;
CSharpResolveContext context = CSharpResolveContextUtil.createContext(extractor, myExpression.getResolveScope(), element);
PsiElement[] psiElements = selector.doSelectElement(context, false);
for(PsiElement e : psiElements)
{
CSharpElementGroup<?> elementGroup = (CSharpElementGroup<?>) e;
groupIteration:
for(PsiElement psiElement : elementGroup.getElements())
{
CSharpMethodDeclaration methodDeclaration = (CSharpMethodDeclaration) psiElement;
// dont need add twice or more
for(CSharpMethodDeclaration resolvedElement : myResolvedElements)
{
if(resolvedElement.isEquivalentTo(methodDeclaration))
{
continue groupIteration;
}
}
GenericInferenceUtil.GenericInferenceResult inferenceResult = inferenceGenericExtractor(methodDeclaration);
DotNetTypeRef firstParameterTypeRef = getFirstTypeRefOrParameter(methodDeclaration, inferenceResult.getExtractor());
if(!CSharpTypeUtil.isInheritableWithImplicit(firstParameterTypeRef, myQualifierTypeRef, myExpression))
{
continue;
}
myResolvedElements.add(transform(methodDeclaration, inferenceResult, element));
}
}
}
return true;
}
@NotNull
public GenericInferenceUtil.GenericInferenceResult inferenceGenericExtractor(CSharpMethodDeclaration methodDeclaration)
{
CSharpCallArgument[] arguments = myCallArgumentListOwner == null ? CSharpCallArgument.EMPTY_ARRAY : myCallArgumentListOwner.getCallArguments();
CSharpCallArgument[] newArguments = new CSharpCallArgument[arguments.length + 1];
System.arraycopy(arguments, 0, newArguments, 1, arguments.length);
newArguments[0] = myArgumentWrapper;
DotNetTypeRef[] typeArgumentRefs = myExpression.getTypeArgumentListRefs();
return GenericInferenceUtil.inferenceGenericExtractor(newArguments, typeArgumentRefs, myExpression, methodDeclaration);
}
public void consumeAsMethodGroup()
{
if(myResolvedElements.isEmpty())
{
return;
}
CSharpMethodDeclaration methodDeclaration = myResolvedElements.get(0);
assert methodDeclaration != null;
CSharpElementGroupImpl element = new CSharpElementGroupImpl<CSharpMethodDeclaration>(myExpression.getProject(), methodDeclaration.getName(), myResolvedElements);
myProcessor.pushResultExternally(new CSharpResolveResult(element, true));
}
@NotNull
@RequiredReadAction
private DotNetTypeRef getFirstTypeRefOrParameter(DotNetParameterListOwner owner, DotNetGenericExtractor extractor)
{
DotNetParameter[] parameters = owner.getParameters();
assert parameters.length != 0;
assert parameters[0].hasModifier(CSharpModifier.THIS);
return GenericUnwrapTool.exchangeTypeRef(parameters[0].toTypeRef(false), extractor, myExpression);
}
private static CSharpMethodDeclaration transform(final CSharpMethodDeclaration methodDeclaration, @NotNull GenericInferenceUtil.GenericInferenceResult inferenceResult,
@Nullable PsiElement providerElement)
{
DotNetParameterList parameterList = methodDeclaration.getParameterList();
assert parameterList != null;
DotNetParameter[] oldParameters = methodDeclaration.getParameters();
DotNetParameter[] parameters = new DotNetParameter[oldParameters.length - 1];
System.arraycopy(oldParameters, 1, parameters, 0, parameters.length);
CSharpLightParameterList lightParameterList = new CSharpLightParameterList(parameterList, parameters);
CSharpLightMethodDeclaration declaration = new CSharpLightMethodDeclaration(methodDeclaration, lightParameterList)
{
@Override
public boolean canNavigate()
{
return true;
}
@Override
public void navigate(boolean requestFocus)
{
((Navigatable) methodDeclaration).navigate(requestFocus);
}
};
CSharpMethodDeclaration extractedMethod = GenericUnwrapTool.extract(declaration, inferenceResult.getExtractor());
extractedMethod.putUserData(CSharpResolveUtil.EXTENSION_METHOD_WRAPPER, methodDeclaration);
extractedMethod.putUserData(GenericInferenceUtil.INFERENCE_RESULT, inferenceResult);
if(providerElement != null)
{
extractedMethod.putUserData(CSharpResolveResult.FORCE_PROVIDER_ELEMENT, providerElement);
}
return extractedMethod;
}
}