blob: 93744a94a3806148232c1996abe2aa9a8f1d66fc [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.descriptors;
import com.intellij.codeInsight.daemon.Validator;
import com.intellij.javaee.ExternalResourceManager;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.ModificationTracker;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.search.PsiElementProcessor;
import com.intellij.psi.util.*;
import com.intellij.psi.xml.XmlDocument;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashMap;
import com.intellij.xml.XmlElementDescriptor;
import com.intellij.xml.XmlNSDescriptorEx;
import com.intellij.xml.impl.schema.AnyXmlElementDescriptor;
import org.intellij.plugins.relaxNG.ApplicationLoader;
import org.intellij.plugins.relaxNG.model.resolve.RelaxIncludeIndex;
import org.intellij.plugins.relaxNG.validation.RngParser;
import org.intellij.plugins.relaxNG.validation.XmlInstanceValidator;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.kohsuke.rngom.digested.DElementPattern;
import org.kohsuke.rngom.digested.DPattern;
import org.kohsuke.rngom.nc.NameClass;
import javax.xml.namespace.QName;
import java.util.*;
/**
* Created by IntelliJ IDEA.
* User: sweinreuter
* Date: 18.07.2007
*/
public class RngNsDescriptor implements XmlNSDescriptorEx, Validator {
private final Map<QName, CachedValue<XmlElementDescriptor>> myDescriptorsMap =
Collections.synchronizedMap(new HashMap<QName, CachedValue<XmlElementDescriptor>>());
private static final Key<ParameterizedCachedValue<XmlElementDescriptor, RngNsDescriptor>> ROOT_KEY = Key.create("ROOT_DESCRIPTOR");
private XmlFile myFile;
private PsiElement myElement;
private String myUrl;
private DPattern myPattern;
private PsiManager myManager;
@Override
@Nullable
public XmlElementDescriptor getElementDescriptor(@NotNull XmlTag tag) {
if (myPattern == null) {
return null;
}
XmlTag _tag = tag;
final LinkedList<XmlTag> chain = new LinkedList<XmlTag>();
while (_tag != null) {
chain.addFirst(_tag);
_tag = _tag.getParentTag();
}
XmlElementDescriptor desc;
do {
desc = findRootDescriptor(chain.removeFirst());
} while (desc == null && chain.size() > 0);
if (desc != null) {
for (XmlTag xmlTag : chain) {
desc = desc.getElementDescriptor(xmlTag, xmlTag.getParentTag());
if (desc == null) {
break;
}
}
}
if (desc == null || desc instanceof AnyXmlElementDescriptor) {
return findRootDescriptor(tag);
}
return desc;
}
private XmlElementDescriptor findRootDescriptor(final XmlTag tag) {
return CachedValuesManager.getManager(tag.getProject())
.getParameterizedCachedValue(tag, ROOT_KEY, new ParameterizedCachedValueProvider<XmlElementDescriptor, RngNsDescriptor>() {
@Override
public CachedValueProvider.Result<XmlElementDescriptor> compute(RngNsDescriptor o) {
final XmlElementDescriptor descr = o.findRootDescriptorInner(tag);
if (descr != null) {
return CachedValueProvider.Result.create(descr, tag, descr.getDependences(), o.getDependences());
}
else {
return CachedValueProvider.Result.create(null, tag, o.getDependences());
}
}
}, false, this);
}
private XmlElementDescriptor findRootDescriptorInner(XmlTag tag) {
final List<DElementPattern> allNamedPatterns =
ContainerUtil.findAll(ChildElementFinder.find(-1, myPattern), NamedPatternFilter.INSTANCE);
XmlElementDescriptor descriptor = findDescriptor(tag, allNamedPatterns);
return descriptor != null ? descriptor : findDescriptor(tag, ChildElementFinder.find(myPattern));
}
private XmlElementDescriptor findRootDescriptorInner(QName qName) {
return findDescriptor(qName, ContainerUtil.findAll(
ChildElementFinder.find(-1, myPattern), NamedPatternFilter.INSTANCE));
}
public XmlElementDescriptor findDescriptor(XmlTag tag, List<DElementPattern> list) {
final QName qName = new QName(tag.getNamespace(), tag.getLocalName());
return findDescriptor(qName, list);
}
private XmlElementDescriptor findDescriptor(final QName qName, List<DElementPattern> list) {
int max = -1;
DElementPattern maxPattern = null;
for (DElementPattern pattern : list) {
final NameClass nameClass = pattern.getName();
if (nameClass.contains(qName)) {
final int spec = nameClass.containsSpecificity(qName);
if (spec > max) {
maxPattern = pattern;
max = spec;
}
}
}
final List<DElementPattern> patterns = ContainerUtil.findAll(list, new Condition<DElementPattern>() {
@Override
public boolean value(DElementPattern pattern) {
final NameClass nameClass = pattern.getName();
return nameClass.contains(qName);
}
});
if (maxPattern != null) {
if (patterns.size() > 1) {
return initDescriptor(new CompositeDescriptor(this, maxPattern, patterns));
} else {
return initDescriptor(new RngElementDescriptor(this, maxPattern));
}
} else {
return null;
}
}
@Override
@NotNull
public XmlElementDescriptor[] getRootElementsDescriptors(@Nullable XmlDocument document) {
if (myPattern == null) {
return XmlElementDescriptor.EMPTY_ARRAY;
}
final List<DElementPattern> list = ChildElementFinder.find(-1, myPattern);
return convertElementDescriptors(list);
}
XmlElementDescriptor[] convertElementDescriptors(List<DElementPattern> patterns) {
patterns = ContainerUtil.findAll(patterns, NamedPatternFilter.INSTANCE);
final Map<QName, List<DElementPattern>> name2patterns = new HashMap<QName, List<DElementPattern>>();
for (DElementPattern pattern : patterns) {
for (QName qName : pattern.getName().listNames()) {
List<DElementPattern> dPatterns = name2patterns.get(qName);
if (dPatterns == null) {
dPatterns = new ArrayList<DElementPattern>();
name2patterns.put(qName, dPatterns);
}
if (!dPatterns.contains(pattern)) dPatterns.add(pattern);
}
}
final List<XmlElementDescriptor> result = new ArrayList<XmlElementDescriptor>();
for (QName qName : name2patterns.keySet()) {
final List<DElementPattern> patternList = name2patterns.get(qName);
final XmlElementDescriptor descriptor = findDescriptor(qName, patternList);
if (descriptor != null) {
result.add(descriptor);
}
}
return result.toArray(new XmlElementDescriptor[result.size()]);
}
protected XmlElementDescriptor initDescriptor(@NotNull XmlElementDescriptor descriptor) {
return descriptor;
}
@Override
@NotNull
public XmlFile getDescriptorFile() {
return myFile;
}
@Override
public boolean isHierarhyEnabled() {
return false;
}
@Override
public synchronized PsiElement getDeclaration() {
if (!myElement.isValid() || !myFile.isValid()) {
if (myUrl != null) {
final VirtualFile fileByUrl = VirtualFileManager.getInstance().findFileByUrl(myUrl);
if (fileByUrl != null) {
final PsiFile file = myManager.findFile(fileByUrl);
if (file instanceof XmlFile) {
init(((XmlFile)file).getDocument());
}
}
}
}
return myFile.isValid() ? myFile.getDocument() : null;
}
@Override
@NonNls
public String getName(PsiElement context) {
return getName();
}
@Override
@NonNls
public String getName() {
return getDescriptorFile().getName();
}
@Override
public Object[] getDependences() {
if (myPattern != null) {
if (DumbService.isDumb(myElement.getProject())) {
return new Object[] { ModificationTracker.EVER_CHANGED, ExternalResourceManager.getInstance()};
}
final Object[] a = { myElement, ExternalResourceManager.getInstance() };
final PsiElementProcessor.CollectElements<XmlFile> processor = new PsiElementProcessor.CollectElements<XmlFile>();
RelaxIncludeIndex.processForwardDependencies(myFile, processor);
if (processor.getCollection().size() > 0) {
return ArrayUtil.mergeArrays(a, processor.toArray());
} else {
return a;
}
}
return new Object[]{ ModificationTracker.EVER_CHANGED };
}
@Override
public synchronized void init(PsiElement element) {
myElement = element;
myFile = element instanceof XmlFile ? (XmlFile)element : (XmlFile)element.getContainingFile();
myManager = myFile.getManager();
final VirtualFile file = myFile.getVirtualFile();
if (file != null) {
myUrl = file.getUrl();
}
myPattern = RngParser.getCachedPattern(getDescriptorFile(), RngParser.DEFAULT_HANDLER);
}
@Override
public void validate(@NotNull PsiElement context, @NotNull final ValidationHost host) {
final XmlDocument doc = PsiTreeUtil.getContextOfType(context, XmlDocument.class, false);
if (doc == null) {
return;
}
final XmlTag rootTag = doc.getRootTag();
if (rootTag == null) {
return;
}
// RNG XML itself is validated by parsing it with Jing, so we don't want to schema-validate it
if (!ApplicationLoader.RNG_NAMESPACE.equals(rootTag.getNamespace())) {
XmlInstanceValidator.doValidation(doc, host, getDescriptorFile());
}
}
//@Override
@Override
public XmlElementDescriptor getElementDescriptor(String localName, String namespace) {
final QName qName = new QName(namespace, localName);
CachedValue<XmlElementDescriptor> cachedValue = myDescriptorsMap.get(qName);
if (cachedValue == null) {
cachedValue =
CachedValuesManager.getManager(myElement.getProject()).createCachedValue(new CachedValueProvider<XmlElementDescriptor>() {
@Override
public Result<XmlElementDescriptor> compute() {
final XmlElementDescriptor descriptor = findRootDescriptorInner(qName);
return descriptor != null
? new Result<XmlElementDescriptor>(descriptor, descriptor.getDependences())
: new Result<XmlElementDescriptor>(null, getDependences());
}
}, false);
myDescriptorsMap.put(qName, cachedValue);
}
return cachedValue.getValue();
}
}