package org.jetbrains.plugins.clojure.psi.impl;
import com.intellij.extapi.psi.PsiFileBase;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.psi.*;
import com.intellij.psi.search.PsiElementProcessor;
import com.intellij.psi.stubs.PsiFileStub;
import com.intellij.psi.stubs.StubElement;
import com.intellij.psi.stubs.StubTree;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.intellij.psi.impl.source.PsiFileImpl;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.plugins.clojure.file.ClojureFileType;
import org.jetbrains.plugins.clojure.parser.ClojureElementTypes;
import org.jetbrains.plugins.clojure.psi.api.ClojureFile;
import org.jetbrains.plugins.clojure.psi.api.ClList;
import org.jetbrains.plugins.clojure.psi.api.defs.ClDef;
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.list.ListDeclarations;
import org.jetbrains.plugins.clojure.psi.stubs.api.ClFileStub;
import org.jetbrains.plugins.clojure.psi.util.ClojureKeywords;
import org.jetbrains.plugins.clojure.psi.util.ClojurePsiFactory;
import org.jetbrains.plugins.clojure.psi.util.ClojurePsiUtil;
import org.jetbrains.plugins.clojure.psi.util.ClojureTextUtil;
import org.jetbrains.plugins.clojure.psi.impl.synthetic.ClSyntheticClassImpl;
import org.jetbrains.plugins.clojure.psi.impl.ns.NamespaceUtil;
import org.jetbrains.plugins.clojure.psi.resolve.ResolveUtil;
import org.jetbrains.plugins.clojure.parser.ClojureParser;
import java.util.ArrayList;
import java.util.List;
/**
* User: peter
* Date: Nov 21, 2008
* Time: 9:50:00 AM
* Copyright 2007, 2008, 2009 Red Shark Technology
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* 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.
*/
public class ClojureFileImpl extends PsiFileBase implements ClojureFile {
private PsiElement myContext = null;
private PsiClass myClass;
private boolean myScriptClassInitialized = false;
@Override
public String toString() {
return "ClojureFile";
}
public ClojureFileImpl(FileViewProvider viewProvider) {
super(viewProvider, ClojureFileType.CLOJURE_LANGUAGE);
}
@Override
public PsiElement getContext() {
if (myContext != null) {
return myContext;
}
return super.getContext();
}
public PsiClass getDefinedClass() {
if (!myScriptClassInitialized) {
if (isScript()) {
myClass = new ClSyntheticClassImpl(this);
}
myScriptClassInitialized = true;
}
return myClass;
}
public void setNamespace(String newNs) {
final ClList nsElem = getNamespaceElement();
if (nsElem != null) {
final ClSymbol first = nsElem.getFirstSymbol();
final PsiElement second = nsElem.getSecondNonLeafElement();
if (first != null && second != null) {
final ClojurePsiFactory factory = ClojurePsiFactory.getInstance(getProject());
final ASTNode newNode = factory.createSymbolNodeFromText(newNs);
final ASTNode parentNode = nsElem.getNode();
if (parentNode != null) {
parentNode.replaceChild(second.getNode(), newNode);
}
}
}
}
public String getNamespacePrefix() {
final String ns = getNamespace();
if (ns != null) {
if (ns.contains(".")) {
return ns.substring(0, ns.lastIndexOf("."));
} else {
return ns;
}
}
return null;
}
public String getNamespaceSuffix() {
final String ns = getNamespace();
if (ns != null) {
return ns.substring(ns.lastIndexOf(".") + 1);
}
return null;
}
protected PsiFileImpl clone() {
final ClojureFileImpl clone = (ClojureFileImpl) super.clone();
clone.myContext = myContext;
return clone;
}
@NotNull
public FileType getFileType() {
return ClojureFileType.CLOJURE_FILE_TYPE;
}
@NotNull
public String getPackageName() {
StubElement stub = getStub();
if (stub instanceof ClFileStub) {
return ((ClFileStub) stub).getPackageName().getString();
}
String ns = getNamespace();
if (ns == null) {
return "";
} else {
return ClojureTextUtil.getSymbolPrefix(ns);
}
}
public boolean isScript() {
return true;
}
private boolean isWrongElement(PsiElement element) {
return element == null ||
(element instanceof LeafPsiElement || element instanceof PsiWhiteSpace || element instanceof PsiComment);
}
public PsiElement getFirstNonLeafElement() {
PsiElement first = getFirstChild();
while (first != null && isWrongElement(first)) {
first = first.getNextSibling();
}
return first;
}
public PsiElement getNonLeafElement(int k) {
final List<PsiElement> elements = ContainerUtil.filter(getChildren(), new Condition<PsiElement>() {
public boolean value(PsiElement psiElement) {
return !isWrongElement(psiElement);
}
});
if (k - 1 >= elements.size()) return null;
return elements.get(k-1);
}
public PsiElement getLastNonLeafElement() {
PsiElement lastChild = getLastChild();
while (lastChild != null && isWrongElement(lastChild)) {
lastChild = lastChild.getPrevSibling();
}
return lastChild;
}
public <T> T findFirstChildByClass(Class<T> aClass) {
PsiElement element = getFirstChild();
while (element != null && !aClass.isInstance(element)) {
element = element.getNextSibling();
}
return (T) element;
}
public PsiElement getSecondNonLeafElement() {
return null;
}
public void setContext(PsiElement context) {
if (context != null) {
myContext = context;
}
}
public List<ClDef> getFileDefinitions() {
final List<ClDef> result = new ArrayList<ClDef>();
StubTree stubTree = getStubTree();
if (stubTree != null) {
for (StubElement<?> element : stubTree.getPlainList()) {
if (element.getStubType() == ClojureElementTypes.DEF || element.getStubType() == ClojureElementTypes.DEFMETHOD) {
PsiElement psi = element.getPsi();
if (psi instanceof ClDef) {
result.add((ClDef) psi);
}
}
}
} else {
PsiTreeUtil.processElements(this, new PsiElementProcessor() {
public boolean execute(@NotNull PsiElement element) {
if (element instanceof ClDef) {
result.add((ClDef) element);
}
return true;
}
});
}
return result;
}
public boolean isClassDefiningFile() {
StubElement stub = getStub();
if (stub instanceof ClFileStub) {
return ((ClFileStub) stub).isClassDefinition();
}
final ClList ns = ClojurePsiUtil.findFormByName(this, "ns");
if (ns == null) return false;
final ClSymbol first = ns.findFirstChildByClass(ClSymbol.class);
if (first == null) return false;
final ClSymbol snd = PsiTreeUtil.getNextSiblingOfType(first, ClSymbol.class);
if (snd == null) return false;
return ClojurePsiUtil.findNamespaceKeyByName(ns, ClojureKeywords.GEN_CLASS) != null;
}
public String getNamespace() {
final ClList ns = getNamespaceElement();
if (ns == null) return null;
final ClSymbol first = ns.findFirstChildByClass(ClSymbol.class);
if (first == null) return null;
final ClSymbol snd = PsiTreeUtil.getNextSiblingOfType(first, ClSymbol.class);
if (snd == null) return null;
return snd.getNameString();
}
public ClNs getNamespaceElement() {
return ((ClNs) ClojurePsiUtil.findFormByNameSet(this, ClojureParser.NS_TOKENS));
}
@NotNull
public ClNs findOrCreateNamespaceElement() throws IncorrectOperationException {
final ClNs ns = getNamespaceElement();
if (ns != null) return ns;
commitDocument();
final ClojurePsiFactory factory = ClojurePsiFactory.getInstance(getProject());
final ClList nsList = factory.createListFromText(ListDeclarations.NS + " " + getName());
final PsiElement anchor = getFirstChild();
if (anchor != null) {
return (ClNs)addBefore(nsList, anchor);
} else {
return (ClNs)add (nsList);
}
}
public String getClassName() {
StubElement stub = getStub();
if (stub instanceof ClFileStub) {
return ((ClFileStub) stub).getClassName().getString();
}
String namespace = getNamespace();
if (namespace == null) return null;
int i = namespace.lastIndexOf(".");
return i > 0 && i < namespace.length() - 1 ? namespace.substring(i + 1) : namespace;
}
@Override
public boolean processDeclarations(@NotNull PsiScopeProcessor processor, @NotNull ResolveState state, PsiElement lastParent, @NotNull PsiElement place) {
//Process precedent read forms
ResolveUtil.processChildren(this, processor, state, lastParent, place);
final JavaPsiFacade facade = JavaPsiFacade.getInstance(getProject());
// Add all java.lang classes
final PsiPackage javaLang = facade.findPackage(ClojurePsiUtil.JAVA_LANG);
if (javaLang != null) {
for (PsiClass clazz : javaLang.getClasses()) {
if (!ResolveUtil.processElement(processor, clazz)) {
return false;
}
}
}
//Add top-level package names
final PsiPackage rootPackage = JavaPsiFacade.getInstance(getProject()).findPackage("");
if (rootPackage != null) {
NamespaceUtil.getNamespaceElement(rootPackage).processDeclarations(processor, state, null, place);
}
// Add all symbols from default namespaces
for (PsiNamedElement element : NamespaceUtil.getDefaultDefinitions(getProject())) {
if (PsiTreeUtil.findCommonParent(element, place) != element && !ResolveUtil.processElement(processor, element)) {
return false;
}
}
return super.processDeclarations(processor, state, lastParent, place);
}
public PsiElement setClassName(@NonNls String s) {
//todo implement me!
return null;
}
protected void commitDocument() {
final Project project = getProject();
final Document document = PsiDocumentManager.getInstance(project).getDocument(this);
if (document != null) {
PsiDocumentManager.getInstance(project).commitDocument(document);
}
}
/*public void addImportForClass(PsiClass clazz) {
final String qualifiedName = clazz.getQualifiedName();
final ClNs namespaceElement = getNamespaceElement();
PsiElement child = getFirstChild();
if (namespaceElement != null) {
child = namespaceElement.getNextSibling();
}
final int i = qualifiedName.lastIndexOf('.');
if (i == -1) {
addNewImportForPath(qualifiedName);
return;
}
final ArrayList<ClList> lists = new ArrayList<ClList>();
while (true) {
if (child instanceof ClList) {
ClList list = (ClList) child;
final String name = list.getFirstSymbol().getName();
if (name.equals(ListDeclarations.IMPORT)) {
lists.add(list);
} else {
break;
}
} else if (!isWrongElement(child)) {
break;
}
child = child.getNextSibling();
}
if (lists.isEmpty()) {
addNewImportForPath(qualifiedName);
return;
}
addNewImportForPath(qualifiedName); //todo: find appropriate import and add it here, then replace import
}
private void addNewImportForPath(String path) {
final ClList importList = ClojurePsiFactory.getInstance(getProject()).createListFromText("(import " + path + ")");
final ClNs namespaceElement = getNamespaceElement();
if (namespaceElement != null) {
addAfter(importList, namespaceElement);
} else {
add(importList);
}
}*/
}