blob: ee11a5f7cc4c53960170e7e70a5f0d7468d39a90 [file] [log] [blame]
/*
* Copyright 2007 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.plugins.relaxNG.model.resolve;
import com.intellij.openapi.util.Factory;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.ModificationTracker;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.search.PsiElementProcessor;
import com.intellij.psi.util.CachedValue;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.xml.XmlFile;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashSet;
import com.intellij.util.xml.DomFileElement;
import com.intellij.util.xml.DomManager;
import gnu.trove.THashSet;
import org.intellij.plugins.relaxNG.compact.psi.RncFile;
import org.intellij.plugins.relaxNG.model.*;
import org.intellij.plugins.relaxNG.xml.dom.RngGrammar;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
/*
* Created by IntelliJ IDEA.
* User: sweinreuter
* Date: 24.08.2007
*/
public class DefinitionResolver extends CommonElement.Visitor implements
CachedValueProvider<Map<String, Set<Define>>>, Factory<Set<Define>> {
private static final Key<CachedValue<Map<String, Set<Define>>>> KEY = Key.create("CACHED_DEFINES");
private static final ThreadLocal<Set<PsiFile>> myVisitedFiles = new ThreadLocal<Set<PsiFile>>();
private static final ThreadLocal<Map<String, Set<Define>>> myDefines = new ThreadLocal<Map<String, Set<Define>>>() {
@Override
protected Map<String, Set<Define>> initialValue() {
return ContainerUtil.newHashMap();
}
};
private final Grammar myScope;
private DefinitionResolver(Grammar scope) {
myScope = scope;
}
@Override
public void visitInclude(Include include) {
include.acceptChildren(this);
final PsiFile value = include.getInclude();
if (myVisitedFiles.get() == null) {
myVisitedFiles.set(ContainerUtil.<PsiFile>newIdentityTroveSet());
}
if (value != null && myVisitedFiles.get().add(value)) {
doVisitRncOrRngFile(value, this);
}
}
private static void doVisitRncOrRngFile(PsiFile file, CommonElement.Visitor visitor) {
if (file instanceof RncFile) {
final Grammar grammar = ((RncFile)file).getGrammar();
if (grammar != null) {
grammar.acceptChildren(visitor);
}
} else if (file instanceof XmlFile) {
final DomManager mgr = DomManager.getDomManager(file.getProject());
final DomFileElement<RngGrammar> element = mgr.getFileElement((XmlFile)file, RngGrammar.class);
if (element != null) {
element.getRootElement().acceptChildren(visitor);
}
}
}
@Override
public void visitDiv(Div div) {
div.acceptChildren(this);
}
@Override
public void visitDefine(Define def) {
ContainerUtil.getOrCreate(myDefines.get(), def.getName(), this).add(def);
}
@Override
public void visitPattern(Pattern pattern) {
}
@Override
public void visitGrammar(Grammar pattern) {
}
@Override
public void visitRef(Ref ref) {
}
@Override
public Set<Define> create() {
return new THashSet<Define>();
}
@Override
public Result<Map<String, Set<Define>>> compute() {
try {
myScope.acceptChildren(this);
final PsiElement psiElement = myScope.getPsiElement();
if (psiElement == null || !psiElement.isValid()) {
return Result.create(null, ModificationTracker.EVER_CHANGED);
}
final PsiFile file = psiElement.getContainingFile();
if (myVisitedFiles.get() != null) {
myVisitedFiles.get().add(file);
return Result.create(myDefines.get(), myVisitedFiles.get().toArray());
} else {
return Result.create(myDefines.get(), file);
}
} finally {
myVisitedFiles.remove();
myDefines.remove();
}
}
@Nullable
public static Set<Define> resolve(Grammar scope, final String value) {
final Map<String, Set<Define>> map = getAllVariants(scope);
if (map == null) {
return null;
}
final Set<Define> set = map.get(value);
// actually we should always do this, but I'm a bit afraid of the performance impact
if (set == null || set.size() == 0) {
final PsiElement element = scope.getPsiElement();
if (element != null) {
final PsiFile file = element.getContainingFile();
if (file instanceof XmlFile) {
final BackwardDefinitionResolver resolver = new BackwardDefinitionResolver(value);
RelaxIncludeIndex.processBackwardDependencies((XmlFile)file, resolver);
return resolver.getResult();
}
}
}
return set;
}
@Nullable
public static Map<String, Set<Define>> getAllVariants(Grammar scope) {
final PsiElement psiElement = scope.getPsiElement();
if (psiElement == null || !psiElement.isValid()) return null;
final CachedValuesManager manager = CachedValuesManager.getManager(psiElement.getProject());
CachedValue<Map<String, Set<Define>>> data = psiElement.getUserData(KEY);
if (data == null || !((DefinitionResolver)data.getValueProvider()).isValid()) {
final DefinitionResolver resolver = new DefinitionResolver(scope);
data = manager.createCachedValue(resolver, false);
psiElement.putUserData(KEY, data);
}
return data.getValue();
}
private boolean isValid() {
final PsiElement element = myScope.getPsiElement();
return element != null && element.isValid();
}
private static class BackwardDefinitionResolver implements PsiElementProcessor<XmlFile> {
private final String myValue;
private Define myResult;
private final Set<PsiFile> myVisitedPsiFiles = new HashSet<PsiFile>();
public BackwardDefinitionResolver(String value) {
myValue = value;
}
@Override
public boolean execute(@NotNull XmlFile element) {
final Grammar g = GrammarFactory.getGrammar(element);
if (g != null) {
g.acceptChildren(new CommonElement.Visitor() {
@Override
public void visitElement(CommonElement pattern) {
if (myResult == null) {
super.visitElement(pattern);
}
}
@Override
public void visitDefine(Define define) {
if (myValue.equals(define.getName())) {
myResult = define;
}
}
@Override
public void visitInclude(Include include) {
final PsiFile file = include.getInclude();
if (file != null && myVisitedPsiFiles.add(file)) {
doVisitRncOrRngFile(file, this);
}
}
});
}
return myResult == null;
}
@Nullable
public Set<Define> getResult() {
return myResult != null ? Collections.singleton(myResult) : null;
}
}
}