blob: f5a68aab0ee507ecd3785e9733a82c6ee823cf46 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* 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 com.intellij.util.xml.impl;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.util.NullableComputable;
import com.intellij.openapi.util.RecursionGuard;
import com.intellij.openapi.util.RecursionManager;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.patterns.ElementPattern;
import com.intellij.psi.PsiElement;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlElementType;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.semantic.SemContributor;
import com.intellij.semantic.SemRegistrar;
import com.intellij.semantic.SemService;
import com.intellij.util.ArrayUtil;
import com.intellij.util.NullableFunction;
import com.intellij.util.Processor;
import com.intellij.util.xml.EvaluatedXmlName;
import com.intellij.util.xml.EvaluatedXmlNameImpl;
import com.intellij.util.xml.XmlName;
import com.intellij.util.xml.reflect.CustomDomChildrenDescription;
import com.intellij.util.xml.reflect.DomChildrenDescription;
import com.intellij.util.xml.reflect.DomCollectionChildDescription;
import com.intellij.util.xml.reflect.DomFixedChildDescription;
import com.intellij.util.xml.stubs.DomStub;
import com.intellij.util.xml.stubs.ElementStub;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Type;
import java.util.*;
import static com.intellij.patterns.XmlPatterns.*;
/**
* @author peter
*/
public class DomSemContributor extends SemContributor {
private final SemService mySemService;
public DomSemContributor(SemService semService) {
mySemService = semService;
}
@Override
public void registerSemProviders(SemRegistrar registrar) {
registrar.registerSemElementProvider(DomManagerImpl.FILE_DESCRIPTION_KEY, xmlFile(), new NullableFunction<XmlFile, FileDescriptionCachedValueProvider>() {
@Override
public FileDescriptionCachedValueProvider fun(XmlFile xmlFile) {
ApplicationManager.getApplication().assertReadAccessAllowed();
return new FileDescriptionCachedValueProvider(DomManagerImpl.getDomManager(xmlFile.getProject()), xmlFile);
}
});
registrar.registerSemElementProvider(DomManagerImpl.DOM_HANDLER_KEY, xmlTag().withParent(psiElement(XmlElementType.XML_DOCUMENT).withParent(xmlFile())), new NullableFunction<XmlTag, DomInvocationHandler>() {
@Override
public DomInvocationHandler fun(XmlTag xmlTag) {
final FileDescriptionCachedValueProvider provider =
mySemService.getSemElement(DomManagerImpl.FILE_DESCRIPTION_KEY, xmlTag.getContainingFile());
assert provider != null;
final DomFileElementImpl element = provider.getFileElement();
if (element != null) {
final DomRootInvocationHandler handler = element.getRootHandler();
if (handler.getXmlTag() == xmlTag) {
return handler;
}
}
return null;
}
});
final ElementPattern<XmlTag> nonRootTag = xmlTag().withParent(or(xmlTag(), xmlEntityRef().withParent(xmlTag())));
registrar.registerSemElementProvider(DomManagerImpl.DOM_INDEXED_HANDLER_KEY, nonRootTag, new NullableFunction<XmlTag, IndexedElementInvocationHandler>() {
@Override
public IndexedElementInvocationHandler fun(XmlTag tag) {
final XmlTag parentTag = PhysicalDomParentStrategy.getParentTag(tag);
assert parentTag != null;
DomInvocationHandler parent = getParentDom(parentTag);
if (parent == null) return null;
final String localName = tag.getLocalName();
final String namespace = tag.getNamespace();
final DomFixedChildDescription description =
findChildrenDescription(parent.getGenericInfo().getFixedChildrenDescriptions(), tag, parent);
if (description != null) {
final int totalCount = description.getCount();
int index = 0;
PsiElement current = tag;
while (true) {
current = current.getPrevSibling();
if (current == null) {
break;
}
if (current instanceof XmlTag) {
final XmlTag xmlTag = (XmlTag)current;
if (localName.equals(xmlTag.getLocalName()) && namespace.equals(xmlTag.getNamespace())) {
index++;
if (index >= totalCount) {
return null;
}
}
}
}
final DomManagerImpl myDomManager = parent.getManager();
return new IndexedElementInvocationHandler(parent.createEvaluatedXmlName(description.getXmlName()), (FixedChildDescriptionImpl)description, index,
new PhysicalDomParentStrategy(tag, myDomManager), myDomManager, null);
}
return null;
}
});
registrar.registerSemElementProvider(DomManagerImpl.DOM_COLLECTION_HANDLER_KEY, nonRootTag, new NullableFunction<XmlTag, CollectionElementInvocationHandler>() {
@Override
public CollectionElementInvocationHandler fun(XmlTag tag) {
final XmlTag parentTag = PhysicalDomParentStrategy.getParentTag(tag);
assert parentTag != null;
DomInvocationHandler parent = getParentDom(parentTag);
if (parent == null) return null;
final DomCollectionChildDescription description = findChildrenDescription(parent.getGenericInfo().getCollectionChildrenDescriptions(), tag, parent);
if (description != null) {
DomStub parentStub = parent.getStub();
if (parentStub != null) {
int index = ArrayUtil.indexOf(parentTag.findSubTags(tag.getName(), tag.getNamespace()), tag);
ElementStub stub = parentStub.getElementStub(tag.getLocalName(), index);
if (stub != null) {
XmlName name = description.getXmlName();
EvaluatedXmlNameImpl evaluatedXmlName = EvaluatedXmlNameImpl.createEvaluatedXmlName(name, name.getNamespaceKey(), true);
return new CollectionElementInvocationHandler(evaluatedXmlName, (AbstractDomChildDescriptionImpl)description, parent.getManager(), stub);
}
}
return new CollectionElementInvocationHandler(description.getType(), tag, (AbstractCollectionChildDescription)description, parent, null);
}
return null;
}
});
registrar.registerSemElementProvider(DomManagerImpl.DOM_CUSTOM_HANDLER_KEY, nonRootTag, new NullableFunction<XmlTag, CollectionElementInvocationHandler>() {
private final RecursionGuard myGuard = RecursionManager.createGuard("customDomParent");
@Override
public CollectionElementInvocationHandler fun(XmlTag tag) {
if (StringUtil.isEmpty(tag.getName())) return null;
final XmlTag parentTag = PhysicalDomParentStrategy.getParentTag(tag);
assert parentTag != null;
DomInvocationHandler parent = myGuard.doPreventingRecursion(tag, true, new NullableComputable<DomInvocationHandler>() {
@Override
public DomInvocationHandler compute() {
return getParentDom(parentTag);
}
});
if (parent == null) return null;
DomGenericInfoEx info = parent.getGenericInfo();
final List<? extends CustomDomChildrenDescription> customs = info.getCustomNameChildrenDescription();
if (customs.isEmpty()) return null;
if (mySemService.getSemElement(DomManagerImpl.DOM_INDEXED_HANDLER_KEY, tag) == null &&
mySemService.getSemElement(DomManagerImpl.DOM_COLLECTION_HANDLER_KEY, tag) == null) {
String localName = tag.getLocalName();
XmlFile file = parent.getFile();
for (final DomFixedChildDescription description : info.getFixedChildrenDescriptions()) {
XmlName xmlName = description.getXmlName();
if (localName.equals(xmlName.getLocalName()) && DomImplUtil.isNameSuitable(xmlName, tag, parent, file)) {
return null;
}
}
for (CustomDomChildrenDescription description : customs) {
if (description.getTagNameDescriptor() != null) {
AbstractCollectionChildDescription desc = (AbstractCollectionChildDescription)description;
Type type = description.getType();
return new CollectionElementInvocationHandler(type, tag, desc, parent, null);
}
}
}
return null;
}
});
registrar.registerSemElementProvider(DomManagerImpl.DOM_ATTRIBUTE_HANDLER_KEY, xmlAttribute(), new NullableFunction<XmlAttribute, AttributeChildInvocationHandler>() {
@Override
public AttributeChildInvocationHandler fun(final XmlAttribute attribute) {
final XmlTag tag = PhysicalDomParentStrategy.getParentTag(attribute);
final DomInvocationHandler handler = getParentDom(tag);
if (handler == null) return null;
final String localName = attribute.getLocalName();
final Ref<AttributeChildInvocationHandler> result = Ref.create(null);
handler.getGenericInfo().processAttributeChildrenDescriptions(new Processor<AttributeChildDescriptionImpl>() {
@Override
public boolean process(AttributeChildDescriptionImpl description) {
if (description.getXmlName().getLocalName().equals(localName)) {
final EvaluatedXmlName evaluatedXmlName = handler.createEvaluatedXmlName(description.getXmlName());
final String ns = evaluatedXmlName.getNamespace(tag, handler.getFile());
//see XmlTagImpl.getAttribute(localName, namespace)
if (ns.equals(tag.getNamespace()) && localName.equals(attribute.getName()) ||
ns.equals(attribute.getNamespace())) {
final DomManagerImpl myDomManager = handler.getManager();
final AttributeChildInvocationHandler attributeHandler =
new AttributeChildInvocationHandler(evaluatedXmlName, description, myDomManager,
new PhysicalDomParentStrategy(attribute, myDomManager), null);
result.set(attributeHandler);
return false;
}
}
return true;
}
});
return result.get();
}
});
}
@Nullable
private static DomInvocationHandler getParentDom(@NotNull XmlTag tag) {
LinkedHashSet<XmlTag> allParents = new LinkedHashSet<XmlTag>();
PsiElement each = tag;
while (each instanceof XmlTag && allParents.add((XmlTag)each)) {
each = PhysicalDomParentStrategy.getParentTagCandidate((XmlTag)each);
}
ArrayList<XmlTag> list = new ArrayList<XmlTag>(allParents);
Collections.reverse(list);
DomManagerImpl manager = DomManagerImpl.getDomManager(tag.getProject());
for (XmlTag xmlTag : list) {
manager.getDomHandler(xmlTag);
}
return manager.getDomHandler(tag);
}
@Nullable
private static <T extends DomChildrenDescription> T findChildrenDescription(List<T> descriptions, XmlTag tag, DomInvocationHandler parent) {
final String localName = tag.getLocalName();
String namespace = null;
final String qName = tag.getName();
final XmlFile file = parent.getFile();
//noinspection ForLoopReplaceableByForEach
for (int i = 0, size = descriptions.size(); i < size; i++) {
final T description = descriptions.get(i);
final XmlName xmlName = description.getXmlName();
if (localName.equals(xmlName.getLocalName()) || qName.equals(xmlName.getLocalName())) {
final EvaluatedXmlName evaluatedXmlName = parent.createEvaluatedXmlName(xmlName);
if (DomImplUtil.isNameSuitable(evaluatedXmlName,
localName,
qName,
namespace == null ? namespace = tag.getNamespace() : namespace,
file)) {
return description;
}
}
}
return null;
}
}