/*
* 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.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.coldFusion.UI.CfmlLookUpItemUtil;
import com.intellij.coldFusion.model.CfmlUtil;
import com.intellij.coldFusion.model.files.CfmlFile;
import com.intellij.coldFusion.model.lexer.CfmlTokenTypes;
import com.intellij.coldFusion.model.lexer.CfscriptTokenTypes;
import com.intellij.diagnostic.LogMessageEx;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Attachment;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.CheckUtil;
import com.intellij.psi.impl.DebugUtil;
import com.intellij.psi.scope.JavaScopeProcessorEvent;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.Function;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.PlatformIcons;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* Created by Lera Nikolaenko
* Date: 11.02.2009
*/
public class CfmlReferenceExpression extends AbstractQualifiedReference<CfmlReferenceExpression>
implements CfmlReference, CfmlExpression, CfmlTypedElement {
private static final Logger LOG = Logger.getInstance(CfmlReferenceExpression.class.getName());
public CfmlReferenceExpression(@NotNull ASTNode node) {
super(node);
}
@Override
protected boolean processUnqualifiedVariants(PsiScopeProcessor processor) {
PsiFile containingFile = getContainingFile();
if (containingFile instanceof CfmlFile) {
CfmlComponent componentDefinition = ((CfmlFile)containingFile).getComponentDefinition();
// process functions
// functions should be processed !first! as explicitly defined getter overlaps the implicit one
CfmlFunction[] functions;
if (componentDefinition != null) {
functions = componentDefinition.getFunctionsWithSupers(this.getFirstChild() instanceof CfmlSuperComponentReference);
}
else {
PsiFile file = this.getContainingFile();
if (file instanceof CfmlFile) {
functions = ((CfmlFile)file).getGlobalFunctions().toArray(CfmlFunction.EMPTY_ARRAY);
}
else {
functions = CfmlFunction.EMPTY_ARRAY;
}
}
for (CfmlFunction function : functions) {
if (!processor.execute(function, ResolveState.initial())) {
return false;
}
}
// process properties
if (componentDefinition != null) {
CfmlProperty[] propertiesWithSupers =
componentDefinition.getPropertiesWithSupers(this.getFirstChild() instanceof CfmlSuperComponentReference);
for (CfmlProperty property : propertiesWithSupers) {
if (!processor.execute(property, ResolveState.initial())) {
return false;
}
}
}
}
return super.processUnqualifiedVariants(processor);
}
@Override
protected boolean processVariantsInner(PsiScopeProcessor processor) {
CfmlTypedElement typedOwner = CfmlPsiUtil.getTypedQualifierInner(this);
PsiType type = null;
if (typedOwner != null) {
type = typedOwner.getPsiType();
}
else {
return processUnqualifiedVariants(processor);
}
// CfmlReferenceExpression qualifier = CfmlPsiUtil.getQualifierInner(this);
if (type instanceof PsiClassType) {
PsiClass psiClass;
if (type instanceof CfmlFunctionCallExpression.PsiClassStaticType) {
psiClass = PsiUtil.resolveClassInType(((CfmlFunctionCallExpression.PsiClassStaticType)type).getRawType());
processor.handleEvent(JavaScopeProcessorEvent.START_STATIC, null);
}
else {
psiClass = PsiUtil.resolveClassInType(type);
}
processor.handleEvent(CfmlVariantsProcessor.CfmlProcessorEvent.SET_INITIAL_CLASS, psiClass);
if (psiClass != null && !psiClass.processDeclarations(processor, ResolveState.initial(), null, this)) {
return false;
}
}
else if (type instanceof CfmlComponentType) {
Collection<CfmlComponent> components = ((CfmlComponentType)type).resolve();
for (CfmlComponent component : components) {
if (!component.processDeclarations(processor, ResolveState.initial(), null, this)) {
return false;
}
}
}
if (typedOwner instanceof CfmlFunctionCallExpression) {
typedOwner = ((CfmlFunctionCallExpression)typedOwner).getReferenceExpression();
}
if (typedOwner instanceof CfmlReference) {
final PsiElement psiElement = ((CfmlReference)typedOwner).resolve();
return psiElement == null || psiElement.processDeclarations(processor, ResolveState.initial(), null, this);
}
return true;
}
@Override
public boolean isReferenceTo(PsiElement element) {
if (element instanceof CfmlProperty && element.getContainingFile() == getContainingFile()) {
String name = ((CfmlProperty)element).getName();
String referenceText = getText();
if ((referenceText.toLowerCase().startsWith("get") ||
referenceText.toLowerCase().startsWith("set")) &&
referenceText.substring(3).equalsIgnoreCase(name)) {
return true;
}
else {
return false;
}
}
final PsiManager manager = getManager();
for (final ResolveResult result : multiResolve(false)) {
final PsiElement target = result.getElement();
if (manager.areElementsEquivalent(element, target)) {
return true;
}
}
return false;
//return super.isReferenceTo(element); //To change body of overridden methods use File | Settings | File Templates.
}
protected ResolveResult[] resolveInner() {
final String referenceName = getReferenceName();
if (referenceName == null) {
return ResolveResult.EMPTY_ARRAY;
}
final CfmlVariantsProcessor<ResolveResult> processor = new CfmlVariantsProcessor<ResolveResult>(this, getParent(), referenceName) {
//Map<String, PsiNamedElement> myVariables = new HashMap<String, PsiNamedElement>();
protected ResolveResult execute(final PsiNamedElement element, final boolean error) {
return new PsiElementResolveResult(element, false);
}
};
boolean ifContinue = true;
final PsiElement parent = getParent();
if (parent instanceof CfmlAssignmentExpression) {
CfmlAssignmentExpression assignment = (CfmlAssignmentExpression)parent;
CfmlVariable var = assignment.getAssignedVariable();
if (var != null && assignment.getAssignedVariableElement() == this) {
ifContinue = processor.execute(var, ResolveState.initial());
}
}
if (ifContinue) {
processVariantsInner(processor);
}
final ResolveResult[] variantsResults = processor.getVariants(ResolveResult.EMPTY_ARRAY);
List<ResolveResult> results = new ArrayList<ResolveResult>();
for (ResolveResult variantsResult : variantsResults) {
if (variantsResult.getElement() != null) {
PsiElement parentRef = variantsResult.getElement().getParent();
if (!(parentRef instanceof CfmlReferenceExpression) ||
(variantsResult.getElement() instanceof CfmlAssignmentExpression.AssignedVariable &&
CfmlUtil.hasEqualScope(this, (CfmlReferenceExpression)parentRef))) {
results.add(variantsResult);
}
}
}
if (results.isEmpty()) {
return ResolveResult.EMPTY_ARRAY;
}
// resolve to truly declaration if found, otherwise resolve to the nearest assignment
if (results.size() > 1) {
ResolveResult result = results.get(results.size() - 1);
PsiElement element = result.getElement();
if (element instanceof CfmlVariable && ((CfmlVariable)element).isTrulyDeclaration()) {
return new ResolveResult[]{result};
}
}
return new ResolveResult[]{results.get(0)};
}
@NotNull
protected CfmlReferenceExpression parseReference(String newText) {
return CfmlPsiUtil.createReferenceExpression(newText, getProject());
}
protected PsiElement getSeparator() {
return findChildByType(CfscriptTokenTypes.POINT);
}
@Override
public TextRange getRangeInElement() {
if (getScope() != null) {
return new TextRange(0, getTextLength());
}
return super.getRangeInElement();
}
private static boolean checkType(@Nullable PsiElement element, IElementType type) {
if (element == null) {
return false;
}
ASTNode node = element.getNode();
if (node != null && node.getElementType().equals(type)) {
return true;
}
return false;
}
@Nullable
public PsiElement getScope() {
PsiElement identifier = findChildByType(CfscriptTokenTypes.IDENTIFIER);
if (identifier != null) {
PsiElement prevSubling = identifier.getPrevSibling();
if (prevSubling != null &&
checkType(prevSubling, CfscriptTokenTypes.POINT) &&
checkType(prevSubling.getPrevSibling(), CfscriptTokenTypes.SCOPE_KEYWORD)) {
return prevSubling.getPrevSibling();
}
}
return null;
}
protected PsiElement getReferenceNameElement() {
PsiElement identifier = findChildByType(CfscriptTokenTypes.IDENTIFIER);
if (identifier == null) {
return findChildByType(CfmlTokenTypes.STRING_TEXT);
}
/*
if (getScope() != null) {
return identifier.getParent();
}
*/
return identifier;
}
@NotNull
public Object[] getVariants() {
final CfmlVariantsProcessor<PsiNamedElement> processor = new CfmlVariantsProcessor<PsiNamedElement>(this, getParent(), null) {
Set<String> myVariablesNames = new HashSet<String>();
protected PsiNamedElement execute(final PsiNamedElement element, final boolean error) {
if (element instanceof CfmlVariable) {
if (myVariablesNames.add(element.getName())) {
return element;
}
return null;
}
else {
// only variables can be scoped
PsiElement scope = getScope();
if (scope != null && !scope.getText().equalsIgnoreCase("this")) {
return null;
}
}
return element;
}
};
processVariantsInner(processor);
PsiNamedElement[] variants = processor.getVariants(PsiNamedElement.EMPTY_ARRAY);
HashSet<LookupElement> result = new HashSet<LookupElement>();
for (PsiNamedElement namedElement : variants) {
if (namedElement instanceof CfmlProperty) {
final String capitalizedName = StringUtil.capitalize(StringUtil.notNullize(namedElement.getName()));
if (((CfmlProperty)namedElement).hasGetter() ||
(namedElement.getParent() instanceof CfmlComponent &&
(((CfmlComponent)namedElement.getParent()).hasImplicitAccessors() ||
((CfmlComponent)namedElement.getParent()).isPersistent()))) {
result.add(LookupElementBuilder.create(namedElement, "get" + capitalizedName + "()").withCaseSensitivity(false)
.withIcon(PlatformIcons.METHOD_ICON));
}
if (((CfmlProperty)namedElement).hasSetter() ||
(namedElement.getParent() instanceof CfmlComponent &&
(((CfmlComponent)namedElement.getParent()).hasImplicitAccessors() ||
((CfmlComponent)namedElement.getParent()).isPersistent()))) {
result.add(LookupElementBuilder.create(namedElement, "set" + capitalizedName + "()").withCaseSensitivity(false)
.withIcon(PlatformIcons.METHOD_ICON));
}
}
}
result.addAll(ContainerUtil.map2Set(variants, new Function<PsiNamedElement, LookupElement>() {
public LookupElement fun(final PsiNamedElement element) {
PsiElement scope = getScope();
return CfmlLookUpItemUtil.namedElementToLookupItem(element, scope != null ? scope.getText() : null);
}
}));
return result.toArray();
}
public PsiType getPsiType() {
if (getParent() instanceof CfmlFunctionCallExpression) {
final PsiType type = ((CfmlFunctionCallExpression)getParent()).getExternalType();
if (type != null) {
return type;
}
}
final PsiElement element = resolve();
if (element instanceof CfmlVariable) {
if ((element instanceof CfmlAssignmentExpression.AssignedVariable)) {
CfmlExpression rightExpr = ((CfmlAssignmentExpression.AssignedVariable)element).getRightHandExpr();
if (rightExpr == this || (rightExpr instanceof CfmlFunctionCallExpression &&
((CfmlFunctionCallExpression)rightExpr).getExternalType() == null &&
((CfmlFunctionCallExpression)rightExpr).getReferenceExpression() == this)) {
LOG.error(LogMessageEx.createEvent("CFML parsing problem",
"Please report the problem to JetBrains with the file attached\nProblem at" +
(rightExpr != null ? rightExpr.getText() : null) +
"\n" +
DebugUtil.currentStackTrace(),
"CFML parsing problem",
"CFML parsing problem",
new Attachment("problem.cfml", element.getContainingFile().getText())));
return null;
}
}
return ((CfmlVariable)element).getPsiType();
}
if (element instanceof PsiMethod) {
PsiMethod method = (PsiMethod)element;
return method.getReturnType();
}
if (element instanceof CfmlFunction) {
return ((CfmlFunction)element).getReturnType();
}
return null;
}
@Override
public String getReferenceName() {
PsiElement identifier = getReferenceNameElement();
return identifier != null ? identifier.getText() : "";
}
@Override
public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
CheckUtil.checkWritable(this);
PsiElement newElement;
final PsiElement referenceNameElement = getReferenceNameElement();
if (referenceNameElement != null) {
final ASTNode referenceNode = referenceNameElement.getNode();
if (referenceNode == null) {
return this;
}
if (referenceNode.getElementType() == CfmlTokenTypes.STRING_TEXT) {
newElement = CfmlPsiUtil.createConstantString(newElementName, getProject());
}
else {
newElement = CfmlPsiUtil.createIdentifier(newElementName, getProject());
}
//noinspection ConstantConditions
getNode().replaceChild(referenceNode, newElement.getNode());
}
return this;
}
public String toString() {
return getNode().getElementType().toString();
}
}