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