/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* 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 com.intellij.coldFusion.model.psi;
import com.intellij.coldFusion.model.CfmlScopesInfo;
import com.intellij.coldFusion.model.psi.impl.CfmlNamedAttributeImpl;
import com.intellij.coldFusion.model.psi.impl.CfmlTagInvokeImpl;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.resolve.JavaMethodCandidateInfo;
import com.intellij.psi.resolve.JavaMethodResolveHelper;
import com.intellij.psi.scope.BaseScopeProcessor;
import com.intellij.psi.scope.JavaScopeProcessorEvent;
import com.intellij.psi.scope.PsiScopeProcessor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Set;
import static com.intellij.psi.PsiModifier.*;
import static com.intellij.util.containers.ContainerUtil.addIfNotNull;
public abstract class CfmlVariantsProcessor<T> extends BaseScopeProcessor {
public static class CfmlProcessorEvent implements PsiScopeProcessor.Event {
private CfmlProcessorEvent() {
}
public static final CfmlProcessorEvent SET_INITIAL_CLASS = new CfmlProcessorEvent();
}
private final Set<T> myResult = new LinkedHashSet<>();
private final String myReferenceName;
private final JavaMethodResolveHelper myMethods;
private final boolean myIsMethodCall;
private final boolean myIsForCompletion;
private final PsiElement myElement;
private boolean myStaticScopeFlag = false;
private PsiClass myInitialClass = null;
private boolean myWasConstructorFound = false;
private int myScope = CfmlScopesInfo.DEFAULT_SCOPE;
protected CfmlVariantsProcessor(final PsiElement element, final PsiElement parent, @Nullable String referenceName) {
if (element instanceof CfmlReferenceExpression) {
final PsiElement scope = ((CfmlReferenceExpression)element).getScope();
if (scope != null) {
myScope = CfmlScopesInfo.getScopeByString(scope.getText());
}
}
myElement = element;
myIsForCompletion = referenceName == null;
myReferenceName = referenceName != null ? referenceName.toLowerCase() : null;
myIsMethodCall = parent instanceof CfmlFunctionCallExpression || parent instanceof CfmlTagInvokeImpl;
if (parent instanceof CfmlFunctionCallExpression && !myIsForCompletion) {
final PsiType[] parameterTypes = ((CfmlFunctionCallExpression)parent).getArgumentTypes();
myMethods = new JavaMethodResolveHelper(parent, parent.getContainingFile(), parameterTypes);
}
else {
myMethods = new JavaMethodResolveHelper(parent, parent.getContainingFile(), null);
}
}
@Override
public void handleEvent(@NotNull Event event, Object associated) {
if (event == JavaScopeProcessorEvent.START_STATIC) {
myStaticScopeFlag = true;
}
else if (event == CfmlProcessorEvent.SET_INITIAL_CLASS && associated instanceof PsiClass) {
myInitialClass = (PsiClass)associated;
myWasConstructorFound = false;
}
}
@Override
public boolean execute(@NotNull final PsiElement element, @NotNull final ResolveState state) {
// continue if not a definition
if (!(element instanceof PsiNamedElement)) {
return true;
}
// continue if has no name
if (StringUtil.isEmpty(((PsiNamedElement)element).getName())) {
return true;
}
String elementName = ((PsiNamedElement)element).getName();
PsiElement namedElement = element instanceof CfmlNamedAttributeImpl ? element.getParent() : element;
// if declared after using
/*
if (myElement.getContainingFile() == element.getContainingFile()) {
if (myElement.getTextRange().getStartOffset() < element.getTextRange().getStartOffset()) {
return true;
}
}
*/
if (!CfmlScopesInfo.isConvenient(namedElement, myScope)) {
return true;
}
// continue if a field or a class
if (namedElement instanceof PsiClass) {
return true;
}
// continue if element is hidden (has private modifier, package_local or protected) (?)
if (namedElement instanceof PsiModifierListOwner) {
final PsiModifierListOwner owner = (PsiModifierListOwner)namedElement;
if (owner.hasModifierProperty(PRIVATE) || owner.hasModifierProperty(PACKAGE_LOCAL) || owner.hasModifierProperty(PROTECTED)) {
return true;
}
}
boolean isJavaMethodCall = namedElement instanceof PsiMethod;
if (isJavaMethodCall) {
final PsiMethod method = (PsiMethod)namedElement;
if (method.isConstructor()) {
final PsiClass methodClass = method.getContainingClass();
if (methodClass == null) {
return true;
}
if (myStaticScopeFlag &&
(methodClass.equals(myInitialClass) || !myWasConstructorFound) &&
(myIsForCompletion || "init".equals(myReferenceName))) {
myWasConstructorFound = true;
if (!methodClass.equals(myInitialClass) && !myIsForCompletion) {
addIfNotNull(myResult, execute(myInitialClass, false));
return true;
}
else {
addIfNotNull(myResult, execute(method, myMethods.getResolveError() == JavaMethodResolveHelper.ErrorType.RESOLVE));
return true;
}
}
}
}
if (namedElement instanceof PsiModifierListOwner) {
final PsiModifierListOwner owner = (PsiModifierListOwner)namedElement;
if (myStaticScopeFlag && !owner.hasModifierProperty(STATIC)) {
return true;
}
}
boolean isMyMethodCall = namedElement instanceof CfmlFunction;
// continue if names differ
if (!myIsForCompletion) {
final String referenceNameLoweCase = myReferenceName.toLowerCase(Locale.ENGLISH);
if (myIsMethodCall &&
(referenceNameLoweCase.startsWith("get") || referenceNameLoweCase.startsWith("set")) &&
referenceNameLoweCase.substring(3).equalsIgnoreCase(elementName)
) {
if (!referenceNameLoweCase.startsWith("get") || methodCallArity() == 0) {
addIfNotNull(myResult, execute((PsiNamedElement)element, false));
}
return myResult.isEmpty();
}
if (!referenceNameLoweCase.equalsIgnoreCase(elementName)) {
return true;
}
}
// continue if not the same type as parent
if (!myIsForCompletion && (isJavaMethodCall || isMyMethodCall) != myIsMethodCall) {
return true;
}
if (isJavaMethodCall) {
myMethods.addMethod((PsiMethod)namedElement, state.get(PsiSubstitutor.KEY), false);
return true;
}
T execute = execute((PsiNamedElement)element, false);
if (execute != null) {
addIfNotNull(myResult, execute);
if (myIsForCompletion || myResult.isEmpty()) {
return true;
} else if (namedElement instanceof CfmlVariable) {
return !((CfmlVariable)namedElement).isTrulyDeclaration();
}
return false;
}
return true;
}
private int methodCallArity() {
if (!myIsMethodCall) return 0;
final CfmlArgumentList argumentList = ((CfmlFunctionCall)myElement.getParent()).findArgumentList();
if (argumentList != null) {
return argumentList.getArguments().length;
}
return 0;
}
@Nullable
protected abstract T execute(final PsiNamedElement element, final boolean error);
public T[] getVariants(T[] array) {
if (myMethods != null) {
for (final JavaMethodCandidateInfo method : myMethods.getMethods()) {
T execute = execute(method.getMethod(), myMethods.getResolveError() == JavaMethodResolveHelper.ErrorType.RESOLVE);
if (execute != null) {
addIfNotNull(myResult, execute);
}
}
}
return myResult.toArray(array);
}
}