blob: 68358ba9dea2778fc5c32ee9be2b4e5768e93725 [file] [log] [blame]
/*
* Copyright 2005 Sascha Weinreuter
*
* 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 org.intellij.lang.xpath.psi.impl;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.CommonProcessors;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import org.intellij.lang.xpath.xslt.impl.XsltIncludeIndex;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class ResolveUtil {
private final Set<PsiElement> myHistory = ContainerUtil.newIdentityTroveSet();
private ResolveUtil() {
}
@Nullable
public static Collection<XmlFile> getDependencies(XmlFile element) {
final CommonProcessors.CollectUniquesProcessor<XmlFile> processor = new CommonProcessors.CollectUniquesProcessor<XmlFile>() {
@Override
public boolean process(XmlFile file) {
if (!getResults().contains(file)) {
XsltIncludeIndex.processForwardDependencies(file, this);
}
return super.process(file);
}
};
XsltIncludeIndex.processForwardDependencies(element, processor);
return processor.getResults();
}
@Nullable
public static PsiFile resolveFile(String name, PsiFile baseFile) {
if (baseFile == null) return null;
final VirtualFile virtualFile = VfsUtil.findRelativeFile(name, baseFile.getVirtualFile());
if (virtualFile != null) {
final PsiFile file = baseFile.getManager().findFile(virtualFile);
if (file != baseFile && file instanceof XmlFile) {
return file;
}
}
return null;
}
@Nullable
public static PsiFile resolveFile(XmlAttribute location, PsiFile baseFile) {
if (location == null) return null;
final XmlAttributeValue valueElement = location.getValueElement();
if (valueElement == null) return null;
// prefer direct relative path
final String value = valueElement.getValue();
final PsiFile file = resolveFile(value, baseFile);
if (file != baseFile && file instanceof XmlFile) {
return file;
}
final PsiReference[] references = valueElement.getReferences();
for (PsiReference reference : references) {
final PsiElement target = reference.resolve();
if (target == null && reference instanceof PsiPolyVariantReference) {
final ResolveResult[] results = ((PsiPolyVariantReference)reference).multiResolve(false);
for (ResolveResult result : results) {
if (result.isValidResult()) {
// TODO: how to weigh/prioritize the results?
final PsiElement element = result.getElement();
if (element != baseFile && element instanceof XmlFile) {
return (PsiFile)target;
}
}
}
} else if (target != baseFile && target instanceof XmlFile) {
return (PsiFile)target;
}
}
return null;
}
public interface Matcher {
@Nullable
XmlTag getRoot();
boolean isRecursive();
@Nullable
Result match(XmlTag element);
Matcher variantMatcher();
class Result {
final PsiElement result;
final Matcher chain;
public Result(PsiElement element) { result = element; chain = null; }
public Result(Matcher matcher) { chain = matcher; result = null; }
public static Result create(PsiElement element) { return new Result(element); }
public static Result create(Matcher matcher) { return new Result(matcher); }
}
}
@Nullable
public static PsiElement resolve(final Matcher matcher) {
if (matcher == null) return null;
final List<PsiElement> found = process(matcher, true);
return found.size() > 0 ? found.get(0) : null;
}
public static PsiElement[] collect(final Matcher matcher) {
if (matcher == null) return PsiElement.EMPTY_ARRAY;
final List<PsiElement> found = process(matcher, false);
return PsiUtilCore.toPsiElementArray(found);
}
private static class Stop extends RuntimeException {
public static final Stop DONE = new Stop();
public Throwable fillInStackTrace() {
return this;
}
}
private static List<PsiElement> process(final Matcher matcher, final boolean resolve) {
return new ResolveUtil()._process(matcher, resolve);
}
private List<PsiElement> _process(final Matcher matcher, final boolean resolve) {
final XmlTag root = matcher.getRoot();
if (root == null || myHistory.contains(root)) {
return Collections.emptyList();
}
myHistory.add(root);
final List<PsiElement> found = new ArrayList<PsiElement>();
try {
if (matcher.isRecursive()) {
root.accept(new XmlRecursiveElementVisitor(){
@Override
public void visitXmlTag(XmlTag tag) {
final Matcher.Result match = matcher.match(tag);
if (match != null) {
if (match.chain != null) {
found.addAll(_process(match.chain, resolve));
} else {
assert match.result != null;
found.add(match.result);
if (resolve) throw Stop.DONE;
}
}
super.visitXmlTag(tag);
}
});
} else {
root.acceptChildren(new XmlElementVisitor() {
@Override
public void visitXmlTag(XmlTag tag) {
final Matcher.Result match = matcher.match(tag);
if (match != null) {
if (match.chain != null) {
found.addAll(_process(match.chain, resolve));
} else {
assert match.result != null;
found.add(match.result);
if (resolve) throw Stop.DONE;
}
}
}
});
}
} catch (Stop e) {
/* processing stopped */
}
return found;
}
public interface XmlProcessor extends Processor<XmlTag> {
boolean process(XmlTag tag);
}
public interface ResolveProcessor extends XmlProcessor {
PsiElement getResult();
}
@Nullable
public static PsiElement treeWalkUp(final XmlProcessor processor, PsiElement elt) {
if (elt == null) return null;
PsiElement cur = elt;
do {
if (cur instanceof XmlTag) {
final XmlTag tag = (XmlTag)cur;
if (!processor.process(tag)) {
if (processor instanceof ResolveProcessor) {
return ((ResolveProcessor)processor).getResult();
}
return null;
}
}
if (cur instanceof PsiFile) break;
cur = PsiTreeUtil.getPrevSiblingOfType(cur, XmlTag.class);
} while (cur != null);
return treeWalkUp(processor, elt.getContext());
}
}