| /* |
| * 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()); |
| } |
| } |