package org.jetbrains.plugins.clojure.psi.impl;
import com.intellij.psi.*;
import com.intellij.psi.scope.NameHint;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.search.GlobalSearchScope;
import org.jetbrains.plugins.clojure.psi.ClojurePsiElement;
import org.jetbrains.plugins.clojure.psi.api.*;
import org.jetbrains.plugins.clojure.psi.api.symbols.ClSymbol;
import org.jetbrains.plugins.clojure.psi.impl.list.ListDeclarations;
import org.jetbrains.plugins.clojure.psi.impl.ns.NamespaceUtil;
import org.jetbrains.plugins.clojure.psi.resolve.ResolveUtil;
import org.jetbrains.plugins.clojure.psi.util.ClojureKeywords;
import org.jetbrains.plugins.clojure.psi.util.ClojurePsiUtil;
import java.util.*;
/**
* @author ilya
*/
public abstract class ImportOwner {
public static boolean processDeclarations(PsiElement self, PsiScopeProcessor processor, PsiElement place) {
for (PsiElement element : self.getChildren()) {
if (element instanceof ClList || element instanceof ClVector) {
ClListLike directive = (ClListLike) element;
final PsiElement first = directive.getFirstNonLeafElement();
if (first == null) return true;
final String headText = first.getText();
if (!processImports(processor, place, directive, headText)) return false;
if (!processUses(processor, place, directive, headText)) return false;
if (!processRequires(processor, place, directive, headText)) return false;
if (!processRefer(processor, place, directive, headText)) return false;
}
}
return true;
}
/**
* Require directive loads clojure namespaces
* Syntax in namespace:
* {Require in namespace} ::= '(' ':require' {Require Directive}* ')'
* {Require Directive} ::= '(' {Prefix} {Name Directive}* ')' |
* '[' {Prefix} {Name Directive}* ']' |
* '[' {Namespace Qualified Name} ':as' {Identifier} ']'
* {Namespace Qualified Name}
* {Name Directive} ::= '[' {Namespace Name} ':as' {Identifier} ']' |
* {Namespace Name}
*
* Examples:
* (:require clojure.string)
* (:require [clojure.string :as str])
* (:require (clojure.string))
* (:require (clojure string reflect))
* (:require (clojure [string :as str] reflect))
*
* Syntax for function call is the same, you just can quote it (unquoted will not work in this case).
*
* Examples:
* (require 'clojure.string)
* (require '[clojure.string :as str])
* (require '(clojure.string))
* (require '(clojure string reflect))
* (require '(clojure [string :as str] reflect))
*/
public static boolean processRequires(PsiScopeProcessor processor, PsiElement place, ClListLike child, String headText) {
final boolean isRequireKeyword = ClojureKeywords.REQUIRE.equals(headText);
final boolean isRequireFunction = ListDeclarations.REQUIRE.equals(headText);
if (isRequireKeyword || isRequireFunction) {
if (processRequireInner(processor, place, child, isRequireKeyword, isRequireFunction)) return false;
}
return true;
}
private static boolean processRequireInner(PsiScopeProcessor processor, PsiElement place, ClListLike child, boolean requireKeyword, boolean requireFunction) {
for (PsiElement stmt : child.getChildren()) {
if (requireKeyword && !checkRequireStatement(processor, place, child, stmt)) return true;
if (requireFunction && stmt instanceof ClQuotedForm) {
final ClojurePsiElement quotedElement = ((ClQuotedForm) stmt).getQuotedElement();
if (!checkRequireStatement(processor, place, child, quotedElement)) return true;
}
}
return false;
}
private static boolean processReferInner(PsiScopeProcessor processor, PsiElement place, ClListLike child, boolean referKeyword, boolean referFunction) {
for (PsiElement stmt : child.getChildren()) {
if (referKeyword && !checkReferStatement(processor, place, child, stmt)) return true;
if (referFunction && stmt instanceof ClQuotedForm) {
final ClojurePsiElement quotedElement = ((ClQuotedForm) stmt).getQuotedElement();
if (!checkReferStatement(processor, place, child, quotedElement)) return true;
}
}
return false;
}
/**
* (refer ns-symbol & filters)
* filters: :only list of symbols
* :exclude list of symbols
* :rename map of from symbol to symbol
* (refer '[clojure.string :exclude [replace reverse]])
* (refer '[clojure.string :rename {replace str-replace, reverse str-reverse}])
* (refer '[clojure.string :only [join split]])
*/
public static boolean processRefer(PsiScopeProcessor processor, PsiElement place, ClListLike directive, String headText) {
final boolean isReferKeyword = ClojureKeywords.REFER.equals(headText);
final boolean isReferFunction = ListDeclarations.REFER.equals(headText);
if (isReferKeyword || isReferFunction) {
if (processReferInner(processor, place, directive, isReferKeyword, isReferFunction)) return false;
}
return true;
}
/**
* Use = refer + require
*/
public static boolean processUses(PsiScopeProcessor processor, PsiElement place, ClListLike directive, String headText) {
final boolean isUseKeyword = ClojureKeywords.USE.equals(headText);
final boolean isUseFunction = ListDeclarations.USE.equals(headText);
if (isUseKeyword || isUseFunction) {
if (processRequireInner(processor, place, directive, isUseKeyword, isUseFunction)) return false;
if (processReferInner(processor, place, directive, isUseKeyword, isUseFunction)) return false;
}
return true;
}
/**
* Import directive imports Java classes.
* Syntax in namespace:
* {Import in namespace} ::= '(' ':import' {Import Directive}* ')'
* {Import Directive} ::= '(' {Package} {Class Name}* ')' |
* '[' {Package} {Class Name}* ']' |
* {Class Qualified Name}
*
* Examples:
* (:import java.util.Date)
* (:import (java.util Date ArrayList))
* (:import [java.util Date ArrayList])
*
* Syntax for function call is the same, you just can quote it.
*
* Examples:
* (import 'java.util.Date)
* (import java.util.Date)
* (import '(java.util Date))
* (import '[java.util Date])
* (import (java.util Date ArrayList))
*/
public static boolean processImports(PsiScopeProcessor processor, PsiElement place, ClListLike child, String headText) {
final boolean isImportKeyword = ClojureKeywords.IMPORT.equals(headText);
final boolean isImportFunction = ListDeclarations.IMPORT.equals(headText);
if (isImportKeyword || isImportFunction) {
for (PsiElement stmt : child.getChildren()) {
if (!checkImportStatement(processor, place, child, stmt)) return false;
if (isImportFunction && stmt instanceof ClQuotedForm) {
final ClojurePsiElement quotedElement = ((ClQuotedForm) stmt).getQuotedElement();
if (!checkImportStatement(processor, place, child, quotedElement)) return false;
}
}
}
return true;
}
private static boolean checkImportStatement(PsiScopeProcessor processor, PsiElement place, ClListLike child, PsiElement stmt) {
if (stmt instanceof ClSymbol) {
if (!checkImportQualifier(processor, place, child, ((ClSymbol) stmt).getNameString())) return false;
} else if (stmt instanceof ClVector || stmt instanceof ClList) {
final ClListLike listLike = (ClListLike) stmt;
final List<String> qualifiedNames = extractImportQualifiedNames(listLike);
for (String qualifiedName : qualifiedNames) {
if (!checkImportQualifier(processor, place, child, qualifiedName)) return false;
}
}
return true;
}
private static boolean checkRequireStatement(PsiScopeProcessor processor, PsiElement place, ClListLike child, PsiElement stmt) {
if (stmt instanceof ClSymbol) {
if (!checkRequireQualifier(processor, place, child, ((ClSymbol) stmt).getNameString())) return false;
} else if (stmt instanceof ClVector && isSpecialVector((ClVector) stmt, ClojureKeywords.AS)) {
ClVector vector = (ClVector) stmt;
final ClSymbol[] symbols = vector.getAllSymbols();
if (symbols.length > 0) {
final ClSymbol symbol = symbols[0];
if (!processVectorAliasSymbols(processor, vector, symbol)) return false;
if (!checkRequireQualifier(processor, place, child, symbol.getNameString())) {
return false;
}
}
} else if (stmt instanceof ClVector || stmt instanceof ClList) {
final ClListLike listLike = (ClListLike) stmt;
if (!processRequireQualifiedNames(processor, place, child, listLike)) return false;
}
return true;
}
private static boolean checkReferStatement(PsiScopeProcessor processor, PsiElement place, ClListLike child, PsiElement stmt) {
if (stmt instanceof ClSymbol) {
if (!checkReferQualifier(processor, place, child, ((ClSymbol) stmt).getNameString(), new ReferFilter())) return false;
} else if (stmt instanceof ClVector && isSpecialVector((ClVector) stmt)) {
ClVector vector = (ClVector) stmt;
final ClSymbol[] symbols = vector.getAllSymbols();
if (symbols.length > 0) {
final ClSymbol symbol = symbols[0];
final ReferFilter referFilter = collectReferFilter(vector, symbol);
if (!checkReferQualifier(processor, place, child, symbol.getNameString(), referFilter)) {
return false;
}
}
} else if (stmt instanceof ClVector || stmt instanceof ClList) {
final ClListLike listLike = (ClListLike) stmt;
if (!processReferQualifiedNames(processor, place, child, listLike)) return false;
}
return true;
}
private static class ReferFilter {
private List<String> excludes = new ArrayList<String>();
private List<String> only = new ArrayList<String>();
private Map<String, ClSymbol> renames = new HashMap<String, ClSymbol>();
private void setHasOnly(boolean hasOnly) {
this.hasOnly = hasOnly;
}
private boolean hasOnly = false;
public void addExclude(String s) {
excludes.add(s);
}
public void addOnly(String s) {
only.add(s);
}
public void addRename(String from, ClSymbol to) {
renames.put(from, to);
}
public List<String> getExcludes() {
return excludes;
}
private List<String> getOnly() {
return only;
}
private Map<String, ClSymbol> getRenames() {
return renames;
}
public String accept(String name) {
if (excludes.contains(name)) return null;
if (!only.isEmpty() && !only.contains(name)) return null;
final ClSymbol symbol = renames.get(name);
return symbol == null ? name : symbol.getNameString();
}
}
private static ReferFilter collectReferFilter(ClVector vector, ClSymbol firstSymbol) {
final ReferFilter result = new ReferFilter();
for (PsiElement child : vector.getChildren()) {
if (child instanceof ClKeyword) {
final String keywordName = ((ClKeyword) child).getName();
final boolean isOnly = keywordName.equals(ClojureKeywords.ONLY);
final boolean isExclude = keywordName.equals(ClojureKeywords.EXCLUDE);
final boolean isRename = keywordName.equals(ClojureKeywords.RENAME);
if (isOnly || isExclude) {
final PsiElement list = ClojurePsiUtil.getNextNonWhiteSpace(child);
if (list instanceof ClVector || list instanceof ClList) {
ClListLike listLike = (ClListLike) list;
final ClSymbol[] symbols = listLike.getAllSymbols();
for (ClSymbol symbol : symbols) {
if (isOnly) {
result.addOnly(symbol.getNameString());
} else if (isExclude) {
result.addExclude(symbol.getNameString());
}
}
}
} else if (isRename) {
final PsiElement map = ClojurePsiUtil.getNextNonWhiteSpace(child);
if (map instanceof ClMap) {
for (ClMapEntry entry : ((ClMap) map).getEntries()) {
final ClojurePsiElement key = entry.getKey();
final ClojurePsiElement value = entry.getValue();
if (key instanceof ClSymbol && value instanceof ClSymbol) {
result.addRename(((ClSymbol) key).getNameString(), (ClSymbol) value);
}
}
}
}
}
}
return result;
}
private static boolean processVectorAliasSymbols(PsiScopeProcessor processor, ClVector vector, ClSymbol firstSymbol) {
for (PsiElement child : vector.getChildren()) {
if (child instanceof ClKeyword && ((ClKeyword) child).getName().equals(ClojureKeywords.AS)) {
NameHint nameHint = processor.getHint(NameHint.KEY);
final PsiElement symbol = ClojurePsiUtil.getNextNonWhiteSpace(child);
if (symbol instanceof ClSymbol) {
String alias = nameHint == null ? null : nameHint.getName(ResolveState.initial());
final String aliasName = ((ClSymbol) symbol).getName();
if (alias != null && alias.equals(aliasName)) {
for (ResolveResult result : firstSymbol.multiResolve(false)) {
final PsiElement element = result.getElement();
if (element instanceof PsiNamedElement) {
PsiNamedElement namedElement = (PsiNamedElement) element;
return processor.execute(namedElement, ResolveState.initial());
}
}
} else if (nameHint == null) {
if (!processor.execute(symbol, ResolveState.initial())) return false;
}
}
break;
}
}
return true;
}
public static boolean isSpecialVector(ClVector vector) {
return isSpecialVector(vector, ClojureKeywords.AS) ||
isSpecialVector(vector, ClojureKeywords.ONLY) ||
isSpecialVector(vector, ClojureKeywords.RENAME) ||
isSpecialVector(vector, ClojureKeywords.EXCLUDE);
}
public static boolean isSpecialVector(ClVector vector, String keyword) {
for (PsiElement child : vector.getChildren()) {
if (child instanceof ClKeyword && ((ClKeyword) child).getName().equals(keyword)) {
return true;
}
}
return false;
}
private static boolean checkImportQualifier(PsiScopeProcessor processor, PsiElement place, ClListLike child, String qualifiedName) {
final PsiClass clazz = JavaPsiFacade.getInstance(child.getProject()).
findClass(qualifiedName, GlobalSearchScope.allScope(place.getProject()));
return !(clazz != null && !ResolveUtil.processElement(processor, clazz));
}
private static boolean checkRequireQualifier(PsiScopeProcessor processor, PsiElement place, ClListLike child, String qualifiedName) {
//todo: See http://youtrack.jetbrains.com/issue/CLJ-169 for more details
//In current state we don't need to do any work here
return true;
}
private static boolean checkReferQualifier(PsiScopeProcessor processor, PsiElement place, ClListLike child,
String qualifiedName, ReferFilter filter) {
NameHint nameHint = processor.getHint(NameHint.KEY);
String expectedName = null;
if (nameHint != null) expectedName = nameHint.getName(ResolveState.initial());
for (PsiNamedElement element : NamespaceUtil.getDeclaredElements(qualifiedName, place.getProject())) {
if (element != null) {
final String name = element.getName();
final String newName = filter.accept(name);
if (newName != null && (expectedName == null || expectedName.equals(newName))) {
if (newName.equals(name)) {
if (!ResolveUtil.processElement(processor, element)) return false;
} else {
if (!ResolveUtil.processElement(processor, element,
ResolveState.initial().put(ResolveUtil.RENAMED_KEY, newName))) return false;
}
}
}
}
return true;
}
private static List<String> extractImportQualifiedNames(ClListLike listLike) {
final List<String> qualifiedNames = new ArrayList<String>();
final PsiElement fst = listLike.getFirstNonLeafElement();
if (fst instanceof ClSymbol) {
PsiElement next = fst.getNextSibling();
while (next != null) {
if (next instanceof ClSymbol) {
ClSymbol clazzSym = (ClSymbol) next;
qualifiedNames.add(((ClSymbol) fst).getNameString() + "." + clazzSym.getNameString());
}
next = next.getNextSibling();
}
}
return qualifiedNames;
}
private static boolean processRequireQualifiedNames(PsiScopeProcessor processor, PsiElement place, ClListLike child,
ClListLike listLike) {
final PsiElement fst = listLike.getFirstNonLeafElement();
if (fst instanceof ClSymbol) {
PsiElement next = fst.getNextSibling();
while (next != null) {
if (next instanceof ClSymbol) {
ClSymbol clazzSym = (ClSymbol) next;
if (!checkRequireQualifier(processor, place, child, ((ClSymbol) fst).getNameString() + "." + clazzSym.getNameString())) {
return false;
}
} else if (next instanceof ClVector) {
ClVector vector = (ClVector) next;
final ClSymbol[] symbols = vector.getAllSymbols();
if (symbols.length > 0) {
final ClSymbol symbol = symbols[0];
if (isSpecialVector((ClVector) next, ClojureKeywords.AS) &&
!processVectorAliasSymbols(processor, vector, symbol)) return false;
if (!checkRequireQualifier(processor, place, child,
((ClSymbol) fst).getNameString() + "." + symbol.getNameString())) {
return false;
}
}
}
next = next.getNextSibling();
}
}
return true;
}
private static boolean processReferQualifiedNames(PsiScopeProcessor processor, PsiElement place, ClListLike child,
ClListLike listLike) {
final PsiElement fst = listLike.getFirstNonLeafElement();
if (fst instanceof ClSymbol) {
PsiElement next = fst.getNextSibling();
boolean isSimple = true;
while (next != null) {
if (next instanceof ClSymbol) {
isSimple = false;
ClSymbol clazzSym = (ClSymbol) next;
if (!checkReferQualifier(processor, place, child, ((ClSymbol) fst).getNameString() + "." + clazzSym.getNameString(), new ReferFilter())) {
return false;
}
} else if (next instanceof ClVector) {
isSimple = false;
ClVector vector = (ClVector) next;
final ClSymbol[] symbols = vector.getAllSymbols();
if (symbols.length > 0) {
final ClSymbol symbol = symbols[0];
ReferFilter filter = collectReferFilter(vector, symbol);
if (!checkReferQualifier(processor, place, child,
((ClSymbol) fst).getNameString() + "." + symbol.getNameString(), filter)) {
return false;
}
}
}
if (isSimple && listLike instanceof ClVector) {
if (!checkReferQualifier(processor, place, child, ((ClSymbol) fst).getNameString(), new ReferFilter())) return false;
}
next = next.getNextSibling();
}
}
return true;
}
}