/* * Copyright 2013 Guidewire Software, Inc. */ package gw.plugin.ij.lang.psi.impl.expressions; import com.intellij.codeInsight.completion.CompletionInitializationContext; import com.intellij.lang.ASTNode; import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; import com.intellij.psi.impl.source.codeStyle.CodeEditUtil; import com.intellij.psi.impl.source.tree.TreeElement; import com.intellij.psi.util.PsiMatcherImpl; import com.intellij.psi.util.PsiMatchers; import com.intellij.util.IncorrectOperationException; import gw.lang.parser.IExpansionPropertyInfo; import gw.lang.parser.expressions.IMemberAccessExpression; import gw.lang.reflect.INamespaceType; import gw.lang.reflect.IPropertyInfo; import gw.lang.reflect.IType; import gw.plugin.ij.lang.GosuTokenImpl; import gw.plugin.ij.lang.GosuTokenTypes; import gw.plugin.ij.lang.parser.GosuCompositeElement; import gw.plugin.ij.lang.parser.GosuElementTypes; import gw.plugin.ij.lang.psi.api.types.IGosuCodeReferenceElement; import gw.plugin.ij.lang.psi.api.types.IGosuTypeElement; import gw.plugin.ij.lang.psi.api.types.IGosuTypeParameterList; import gw.plugin.ij.lang.psi.impl.GosuElementVisitor; import gw.plugin.ij.lang.psi.impl.resolvers.PsiFeatureResolver; import gw.plugin.ij.lang.psi.impl.statements.typedef.GosuSyntheticClassDefinitionImpl; import gw.plugin.ij.lang.psi.util.ElementTypeMatcher; import gw.plugin.ij.lang.psi.util.GosuPsiParseUtil; import gw.plugin.ij.util.ExecutionUtil; import gw.plugin.ij.util.SafeCallable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class GosuFieldAccessExpressionImpl extends GosuReferenceExpressionImpl<IMemberAccessExpression> implements IGosuCodeReferenceElement, IGosuTypeElement, MultiRangeReference { private static final String[] PROP_PREFIXES = {"get", "set", "is"}; public GosuFieldAccessExpressionImpl(GosuCompositeElement node) { super(node); } @Nullable public PsiElement getReferenceNameElement() { PsiElement child = findLastChildByType( GosuTokenTypes.TT_IDENTIFIER ); if( child == null ) { // Could be a non-reserved keyword child = getLastChild(); if( child instanceof GosuTokenImpl ) { return child; } } return child; } @Override public IGosuCodeReferenceElement getQualifier() { final PsiElement firstChild = getFirstChild(); return firstChild instanceof IGosuCodeReferenceElement ? (IGosuCodeReferenceElement) firstChild : null; } @Override public void setQualifier(IGosuCodeReferenceElement newQualifier) { throw new UnsupportedOperationException("Men at work"); } @Nullable public IGosuTypeParameterList getTypeParameterList() { return null; } @Override public PsiType[] getTypeArguments() { return PsiType.EMPTY_ARRAY; } @Override public PsiElement resolve() { return ExecutionUtil.execute(new SafeCallable<PsiElement>(this) { @Nullable public PsiElement execute() throws Exception { IMemberAccessExpression parsedElement = getParsedElement(); if (parsedElement == null) { return null; } IType type = parsedElement.getType(); if (type instanceof INamespaceType) { String packageName = type.getName(); PsiPackage aPackage = JavaPsiFacade.getInstance(getProject()).findPackage(packageName); return (aPackage == null || !aPackage.isValid()) ? null : aPackage; } final IPropertyInfo pi = getPropertyInfo(parsedElement); return pi != null ? PsiFeatureResolver.resolveProperty(pi, GosuFieldAccessExpressionImpl.this) : null; } }); } @Nullable private IPropertyInfo getPropertyInfo(IMemberAccessExpression parsedElement) { try { IPropertyInfo pi = parsedElement.getPropertyInfo(); // dlank: Moved the delegation resolution to the resolveProperty() method so that delegating property infos would // have a chance to resolve to a different place than the property to which they are delegating. // while (pi instanceof IPropertyInfoDelegate) { // pi = ((IPropertyInfoDelegate) pi).getSource(); // } while (pi instanceof IExpansionPropertyInfo) { pi = ((IExpansionPropertyInfo) pi).getDelegate(); } return pi; } catch (RuntimeException e) { // No property exists, pe is errant return null; } } //TODO-dp consider adding an extension point for these kinds of special cases public TextRange getRangeInElement() { if (isDisplayKey(getParsedElement())) { return new TextRange(0, getTextLength()); } else { return super.getRangeInElement(); } } public static boolean isDisplayKey(@Nullable IMemberAccessExpression parsedElement) { if (parsedElement == null) { return false; } IType rootType = parsedElement.getRootType(); return rootType != null && rootType.getName().startsWith("displaykey_") && !CompletionInitializationContext.DUMMY_IDENTIFIER_TRIMMED.equals(parsedElement.getMemberName()); } protected PsiElement handleElementRenameInner(String newElementName) throws IncorrectOperationException { if (isDisplayKey(getParsedElement())) { GosuCompositeElement oldNode = this.getNode(); PsiElement element = GosuPsiParseUtil.createReferenceNameFromText(this, "displaykey." + newElementName); ASTNode newNode = new PsiMatcherImpl(element.getContainingFile()) .descendant(PsiMatchers.hasClass(GosuSyntheticClassDefinitionImpl.class)) .descendant(new ElementTypeMatcher(GosuElementTypes.ELEM_TYPE_ReturnStatement)) .descendant(PsiMatchers.hasClass(GosuFieldAccessExpressionImpl.class)) .getElement().getNode(); CodeEditUtil.setOldIndentation((TreeElement) newNode, 0); // this is to avoid a stupid exception oldNode.getTreeParent().replaceChild(oldNode, newNode); return this; } else { newElementName = makePropertyName( newElementName ); return super.handleElementRenameInner(newElementName); } } private String makePropertyName( String name ) { for( String prefix: PROP_PREFIXES ) { if( name.length() > prefix.length() && name.startsWith( prefix ) && Character.isUpperCase( name.charAt( prefix.length() ) ) ) { return name.substring( prefix.length() ); } } return name; } @Override public List<TextRange> getRanges() { if (isDisplayKey(getParsedElement())) { List<TextRange> ranges = new ArrayList<>(); int startOffset = getNode().getStartOffset(); for (PsiElement psiChild = getFirstChild(); psiChild != null; psiChild = psiChild.getNextSibling()) { TextRange range = psiChild.getTextRange(); ranges.add(range.shiftRight(-startOffset)); } if (!ranges.isEmpty()) { return ranges; } } return Collections.singletonList(getRangeInElement()); } @Override public void accept( @NotNull PsiElementVisitor visitor ) { // if( visitor instanceof JavaElementVisitor && !(visitor instanceof HighlightVisitorImpl) ) { // ((JavaElementVisitor)visitor).visitCallExpression( this ); // } else if( visitor instanceof GosuElementVisitor) { ((GosuElementVisitor)visitor).visitFieldAccessExpression(this); } else { visitor.visitElement( this ); } } }