Package org.jetbrains.plugins.clojure.psi.impl.symbols

Source Code of org.jetbrains.plugins.clojure.psi.impl.symbols.ClSymbolImpl$MyFakeClassPsiReference

package org.jetbrains.plugins.clojure.psi.impl.symbols;

import com.intellij.lang.ASTNode;
import com.intellij.navigation.ItemPresentation;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Iconable;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.ResolveCache;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.stubs.StubIndex;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.MethodSignature;
import com.intellij.util.Function;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.HashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.clojure.ClojureIcons;
import org.jetbrains.plugins.clojure.lexer.ClojureTokenTypes;
import org.jetbrains.plugins.clojure.lexer.TokenSets;
import org.jetbrains.plugins.clojure.psi.ClojurePsiElementImpl;
import org.jetbrains.plugins.clojure.psi.api.*;
import org.jetbrains.plugins.clojure.psi.api.ns.ClNs;
import org.jetbrains.plugins.clojure.psi.api.symbols.ClSymbol;
import org.jetbrains.plugins.clojure.psi.impl.ImportOwner;
import org.jetbrains.plugins.clojure.psi.impl.list.ListDeclarations;
import org.jetbrains.plugins.clojure.psi.impl.ns.ClSyntheticNamespace;
import org.jetbrains.plugins.clojure.psi.impl.ns.NamespaceUtil;
import org.jetbrains.plugins.clojure.psi.resolve.ClojureResolveResult;
import org.jetbrains.plugins.clojure.psi.resolve.ClojureResolveResultImpl;
import org.jetbrains.plugins.clojure.psi.resolve.ResolveUtil;
import org.jetbrains.plugins.clojure.psi.resolve.completion.CompleteSymbol;
import org.jetbrains.plugins.clojure.psi.resolve.completion.CompletionProcessor;
import org.jetbrains.plugins.clojure.psi.resolve.processors.ResolveKind;
import org.jetbrains.plugins.clojure.psi.resolve.processors.ResolveProcessor;
import org.jetbrains.plugins.clojure.psi.resolve.processors.SymbolResolveProcessor;
import org.jetbrains.plugins.clojure.psi.stubs.index.ClojureNsNameIndex;
import org.jetbrains.plugins.clojure.psi.util.ClojureKeywords;
import org.jetbrains.plugins.clojure.psi.util.ClojurePsiFactory;

import javax.swing.*;
import java.util.Collection;
import java.util.List;

/**
* @author ilyas
*/
public class ClSymbolImpl extends ClojurePsiElementImpl implements ClSymbol {
  public ClSymbolImpl(ASTNode node) {
    super(node);
  }

  @NotNull
  @Override
  public PsiReference[] getReferences() {
    PsiReference fakeClassReference = new MyFakeClassPsiReference();
    PsiReference[] refs = {this, fakeClassReference};
    return refs;
  }

  @Override
  public PsiReference getReference() {
    return this;
  }

  @Override
  public String toString() {
    return "ClSymbol";
  }

  public PsiElement getElement() {
    return this;
  }

  public TextRange getRangeInElement() {
    final PsiElement refNameElement = getReferenceNameElement();
    if (refNameElement != null) {
      final int offsetInParent = refNameElement.getStartOffsetInParent();
      return new TextRange(offsetInParent, offsetInParent + refNameElement.getTextLength());
    }
    return new TextRange(0, getTextLength());
  }

  @Nullable
  public PsiElement getReferenceNameElement() {
    final ASTNode lastChild = getNode().getLastChildNode();
    if (lastChild == null) return null;
    for (IElementType elementType : TokenSets.REFERENCE_NAMES.getTypes()) {
      if (lastChild.getElementType() == elementType) return lastChild.getPsi();
    }

    return null;
  }

  @Nullable
  public String getReferenceName() {
    PsiElement nameElement = getReferenceNameElement();
    if (nameElement != null) {
      if (nameElement.getNode().getElementType() == ClojureTokenTypes.symATOM)
        return nameElement.getText();
    }
    return null;
  }

  @NotNull
  public ResolveResult[] multiResolve(boolean incomplete) {
    final ResolveCache resolveCache = ResolveCache.getInstance(getProject());
    return resolveCache.resolveWithCaching(this, RESOLVER, true, incomplete);
  }

  public PsiElement setName(@NotNull @NonNls String newName) throws IncorrectOperationException {
    final ASTNode newNode = ClojurePsiFactory.getInstance(getProject()).createSymbolNodeFromText(newName);
    getParent().getNode().replaceChild(getNode(), newNode);
    return newNode.getPsi();
  }

  @Override
  public Icon getIcon(int flags) {
    return ClojureIcons.SYMBOL;
  }

  @Override
  public ItemPresentation getPresentation() {
    return new ItemPresentation() {
      public String getPresentableText() {
        final String name = getName();
        return name == null ? "<undefined>" : name;
      }

      @Nullable
      public String getLocationString() {
        String name = getContainingFile().getName();
        //todo show namespace
        return "(in " + name + ")";
      }

      @Nullable
      public Icon getIcon(boolean open) {
        return ClSymbolImpl.this.getIcon(Iconable.ICON_FLAG_VISIBILITY | Iconable.ICON_FLAG_READ_STATUS);
      }

      @Nullable
      public TextAttributesKey getTextAttributesKey() {
        return null;
      }
    };
  }


  public ResolveKind[] getKinds() {
    return ResolveKind.allKinds();
  }
  public static class MyResolver implements ResolveCache.PolyVariantResolver<ClSymbol> {

    public ResolveResult[] resolve(ClSymbol symbol, boolean incompleteCode) {
      final String name = symbol.getReferenceName();
      if (name == null) return null;

      // Resolve Java methods invocations
      ClSymbol qualifier = symbol.getQualifierSymbol();
      final String nameString = symbol.getNameString();
      if (qualifier == null && nameString.startsWith(".")) {
        return resolveJavaMethodReference(symbol, null, false);
      }

      ResolveKind[] kinds = symbol.getKinds();
      if (nameString.endsWith(".")) {
        kinds = ResolveKind.javaClassesKinds();
      }
      ResolveProcessor processor = new SymbolResolveProcessor(StringUtil.trimEnd(name, "."), symbol, incompleteCode, kinds);
      resolveImpl(symbol, processor);

      if (nameString.contains(".")) {
        ResolveProcessor nsProcessor = new SymbolResolveProcessor(nameString, symbol, incompleteCode, ResolveKind.namesSpaceKinds());
        resolveNamespace(symbol, nsProcessor);
      }

      ClojureResolveResult[] candidates = processor.getCandidates();
      if (candidates.length > 0) return candidates;

      return ClojureResolveResult.EMPTY_ARRAY;
    }

    public static ResolveResult[] resolveJavaMethodReference(final ClSymbol symbol, @Nullable PsiElement start, final boolean forCompletion) {
      final CompletionProcessor processor = new CompletionProcessor(symbol, symbol.getKinds());
      if (start == null) start = symbol;
      ResolveUtil.treeWalkUp(start, processor);
      final String name = symbol.getReferenceName();
      assert name != null;

      final String originalName = StringUtil.trimStart(name, ".");
      final PsiElement[] elements = ResolveUtil.mapToElements(processor.getCandidates());
      final HashMap<MethodSignature, HashSet<PsiMethod>> sig2Method = CompleteSymbol.collectAvailableMethods(elements);
      final List<MethodSignature> goodSignatures = ContainerUtil.findAll(sig2Method.keySet(), new Condition<MethodSignature>() {
        public boolean value(MethodSignature methodSignature) {
          return forCompletion || originalName.equals(methodSignature.getName());
        }
      });

      final HashSet<ClojureResolveResult> results = new HashSet<ClojureResolveResult>();
      for (MethodSignature signature : goodSignatures) {
        final HashSet<PsiMethod> methodSet = sig2Method.get(signature);
        for (PsiMethod method : methodSet) {
          results.add(new ClojureResolveResultImpl(method, true));
        }
      }

      return results.toArray(new ClojureResolveResult[results.size()]);
    }

    private void resolveImpl(ClSymbol symbol, ResolveProcessor processor) {
      final ClSymbol qualifier = symbol.getQualifierSymbol();

      //process other places
      if (qualifier == null) {
        ResolveUtil.treeWalkUp(symbol, processor);
      } else {
        for (ResolveResult result : qualifier.multiResolve(false)) {
          final PsiElement element = result.getElement();
          if (element != null) {
            final PsiElement sep = symbol.getSeparatorToken();
            if (sep != null && "/".equals(sep.getText())) {

              //get class elements
              if (element instanceof PsiClass) {
                element.processDeclarations(processor, ResolveState.initial(), null, symbol);
              }

              //get namespace declarations
              if (element instanceof ClSyntheticNamespace) {
                final String fqn = ((ClSyntheticNamespace) element).getQualifiedName();
                // namespace declarations
                for (PsiNamedElement named : NamespaceUtil.getDeclaredElements(fqn, element.getProject())) {
                  if (!ResolveUtil.processElement(processor, named)) return;
                }
              }

            } else if (sep == null || ".".equals(sep.getText())) {
              element.processDeclarations(processor, ResolveState.initial(), null, symbol);
            }
          }
        }
      }
    }

    private void resolveNamespace(ClSymbol symbol, ResolveProcessor processor) {
      // process namespaces
      final Project project = symbol.getProject();
      final GlobalSearchScope scope = GlobalSearchScope.allScope(project);
      final Collection<ClNs> nses = StubIndex.getInstance().get(ClojureNsNameIndex.KEY, symbol.getNameString(), project, scope);
      for (ClNs ns : nses) {
        ResolveUtil.processElement(processor, ns);
      }
    }
  }

  @Nullable
  public ClSymbol getRawQualifierSymbol() {
    return findChildByClass(ClSymbol.class);
  }

  /**
   * For references, which hasn't prefix
   * (import '(prefix symbol))
   * (require '(prefix symbol))
   * (require '[prefix symbol])
   * (require '(prefix [symbol :as id]))
   * @return qualifier symbol
   */
  @Nullable
  public ClSymbol getQualifierSymbol() {
    final ClSymbol rawQualifierSymbol = getRawQualifierSymbol();
    if (rawQualifierSymbol != null) return rawQualifierSymbol;
    final PsiElement parent = getParent();
    return getQualifiedNameInner(parent, false);
  }

  private ClSymbol getQualifiedNameInner(PsiElement parent, boolean onlyRequireOrUse) {
    if (parent instanceof ClList) {
      return getListQualifier(onlyRequireOrUse, (ClList) parent, parent.getParent(), false);
    } else if (parent instanceof ClVector && ImportOwner.isSpecialVector((ClVector) parent)) {
      final ClSymbol[] symbols = ((ClVector) parent).getAllSymbols();
      return symbols[0] == this ? getQualifiedNameInner(parent.getParent(), true) : null;
    } else if (parent instanceof ClVector) {
      return getVectorQualifier(onlyRequireOrUse, (ClVector) parent, parent.getParent(), false);
    }
    return null;
  }

  private ClSymbol getListQualifier(boolean onlyRequireOrUse, ClList list, PsiElement listParent, boolean isQuoted) {
    if (listParent instanceof ClQuotedForm) {
      return getListQualifier(onlyRequireOrUse, list, listParent.getParent(), true);
    } else if (listParent instanceof ClList) {
      final PsiElement listParentFirstSymbol = ((ClList) listParent).getFirstNonLeafElement();
      if (listParentFirstSymbol instanceof ClSymbol || listParentFirstSymbol instanceof ClKeyword) {
        final String name;
        if (listParentFirstSymbol instanceof ClSymbol) {
          name = ((ClSymbol) listParentFirstSymbol).getNameString();
        } else {
          name = ((ClKeyword) listParentFirstSymbol).getName();
        }
        boolean isOk = false;
        if ((name.equals(ClojureKeywords.IMPORT) || name.equals(ListDeclarations.IMPORT)) && !onlyRequireOrUse) isOk = true;
        else if ((name.equals(ClojureKeywords.REQUIRE) || name.equals(ClojureKeywords.USE)) && !isQuoted) isOk = true;
        else if ((name.equals(ListDeclarations.REQUIRE) || name.equals(ListDeclarations.USE)) && isQuoted) isOk = true;
        final ClSymbol firstSymbol = list.getFirstSymbol();
        if (isOk && firstSymbol != this && firstSymbol instanceof ClSymbol) {
          return firstSymbol;
        }
      }
    }
    return null;
  }

  private ClSymbol getVectorQualifier(boolean onlyRequireOrUse, ClVector vector, PsiElement list, boolean isQuoted) {
    if (list instanceof ClList) {
      final PsiElement firstSymbol = ((ClList) list).getFirstNonLeafElement();
      if (firstSymbol instanceof ClSymbol || firstSymbol instanceof ClKeyword) {
        final String name;
        if (firstSymbol instanceof ClSymbol) {
          name = ((ClSymbol) firstSymbol).getNameString();
        } else {
          name = ((ClKeyword) firstSymbol).getName();
        }
        boolean isOk = false;
        if ((name.equals(ClojureKeywords.IMPORT) || name.equals(ListDeclarations.IMPORT)) && !onlyRequireOrUse) isOk = true;
        else if ((name.equals(ClojureKeywords.REQUIRE) || name.equals(ClojureKeywords.USE)) && !isQuoted) isOk = true;
        else if ((name.equals(ListDeclarations.REQUIRE) || name.equals(ListDeclarations.USE)) && isQuoted) isOk = true;
        if (isOk) {
          final PsiElement firstNonLeafElement = vector.getFirstNonLeafElement();
          if (firstNonLeafElement != null && firstNonLeafElement != this && firstNonLeafElement instanceof ClSymbol) {
            return (ClSymbol) firstNonLeafElement;
          }
        }
      }
    } else if (list instanceof ClQuotedForm) {
      return getVectorQualifier(onlyRequireOrUse, vector, list.getParent(), true);
    }
    return null;
  }

  public boolean isQualified() {
    return getQualifierSymbol() != null;
  }

  @Override
  public String getName() {
    return getNameString();
  }

  @Nullable
  public PsiElement getSeparatorToken() {
    return findChildByType(TokenSets.DOTS);
  }

  private static final MyResolver RESOLVER = new MyResolver();

  public PsiElement resolve() {
    final ResolveCache resolveCache = ResolveCache.getInstance(getProject());
    ResolveResult[] results = resolveCache.resolveWithCaching(this, RESOLVER, false, false);
    return results.length == 1 ? results[0].getElement() : null;
  }

  @NotNull
  public String getCanonicalText() {
    return getText();
  }

  private List<PsiElement> multipleResolveResults() {
    final ResolveCache resolveCache = ResolveCache.getInstance(getProject());
    final ResolveResult[] results = resolveCache.resolveWithCaching(this, RESOLVER, false, false);
    return ContainerUtil.map(results, new Function<ResolveResult, PsiElement>() {
      public PsiElement fun(ResolveResult resolveResult) {
        return resolveResult.getElement();
      }
    });
  }

  public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
    PsiElement nameElement = getReferenceNameElement();
    if (nameElement != null) {
      ASTNode node = nameElement.getNode();
      ASTNode newNameNode = ClojurePsiFactory.getInstance(getProject()).createSymbolNodeFromText(newElementName);
      assert newNameNode != null && node != null;
      node.getTreeParent().replaceChild(node, newNameNode);
    }
    return this;
  }

  public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
    if (isReferenceTo(element)) return this;
    final PsiFile file = getContainingFile();
    if (element instanceof PsiClass && (file instanceof ClojureFile)) {
      // todo test me!!
      final PsiClass clazz = (PsiClass) element;
      final Application application = ApplicationManager.getApplication();
      application.runWriteAction(new Runnable() {
        public void run() {
          final ClNs ns = ((ClojureFile) file).findOrCreateNamespaceElement();
          ns.addImportForClass(ClSymbolImpl.this, clazz);
        }
      });
      return this;
    }
    return this;
  }

  public boolean isReferenceTo(PsiElement element) {
    return multipleResolveResults().contains(element);
  }

  @NotNull
  public Object[] getVariants() {
    return CompleteSymbol.getVariants(this);
  }

  public boolean isSoft() {
    return false;
  }

  @NotNull
  public String getNameString() {
    return getText();
  }

  private class MyFakeClassPsiReference implements PsiReference {
    public PsiElement getElement() {
      return ClSymbolImpl.this;
    }

    public TextRange getRangeInElement() {
      return new TextRange(0, 0);
    }

    public PsiElement resolve() {
      for (PsiElement element : multipleResolveResults()) {
        if (element instanceof PsiClass) {
          return element;
        }
      }
      return null;
    }

    @NotNull
    public String getCanonicalText() {
      return ClSymbolImpl.this.getCanonicalText();
    }

    public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException {
      return null;
    }

    public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
      return null;
    }

    public boolean isReferenceTo(PsiElement element) {
      return ClSymbolImpl.this.isReferenceTo(element);
    }

    @NotNull
    public Object[] getVariants() {
      return new Object[0];
    }

    public boolean isSoft() {
      return false;
    }
  }
}
TOP

Related Classes of org.jetbrains.plugins.clojure.psi.impl.symbols.ClSymbolImpl$MyFakeClassPsiReference

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.