| /* |
| * 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.extensions.Extensions; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Computable; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.RecursionGuard; |
| import com.intellij.openapi.util.RecursionManager; |
| import com.intellij.psi.xml.XmlElement; |
| import com.intellij.psi.xml.XmlFile; |
| import com.intellij.reference.SoftReference; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.Function; |
| import com.intellij.util.Processor; |
| import com.intellij.util.containers.ConcurrentHashMap; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.xml.DomElement; |
| import com.intellij.util.xml.GenericDomValue; |
| import com.intellij.util.xml.JavaMethod; |
| import com.intellij.util.xml.reflect.*; |
| import gnu.trove.THashSet; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * @author peter |
| */ |
| public class DynamicGenericInfo extends DomGenericInfoEx { |
| private static final Key<SoftReference<ConcurrentHashMap<ChildrenDescriptionsHolder, ChildrenDescriptionsHolder>>> HOLDERS_CACHE = Key.create("DOM_CHILDREN_HOLDERS_CACHE"); |
| private static final RecursionGuard ourGuard = RecursionManager.createGuard("dynamicGenericInfo"); |
| private final StaticGenericInfo myStaticGenericInfo; |
| @NotNull private final DomInvocationHandler myInvocationHandler; |
| private volatile boolean myInitialized; |
| private volatile ChildrenDescriptionsHolder<AttributeChildDescriptionImpl> myAttributes; |
| private volatile ChildrenDescriptionsHolder<FixedChildDescriptionImpl> myFixeds; |
| private volatile ChildrenDescriptionsHolder<CollectionChildDescriptionImpl> myCollections; |
| private volatile List<CustomDomChildrenDescriptionImpl> myCustomChildren; |
| |
| public DynamicGenericInfo(@NotNull final DomInvocationHandler handler, final StaticGenericInfo staticGenericInfo) { |
| myInvocationHandler = handler; |
| myStaticGenericInfo = staticGenericInfo; |
| |
| myAttributes = staticGenericInfo.getAttributes(); |
| myFixeds = staticGenericInfo.getFixed(); |
| myCollections = staticGenericInfo.getCollections(); |
| } |
| |
| @Override |
| public Invocation createInvocation(final JavaMethod method) { |
| return myStaticGenericInfo.createInvocation(method); |
| } |
| |
| @Override |
| public final boolean checkInitialized() { |
| if (myInitialized) return true; |
| myStaticGenericInfo.buildMethodMaps(); |
| |
| if (!myInvocationHandler.exists()) return true; |
| |
| return ourGuard.doPreventingRecursion(myInvocationHandler, false, new Computable<Boolean>() { |
| @Override |
| public Boolean compute() { |
| DomExtensionsRegistrarImpl registrar = runDomExtenders(); |
| |
| //noinspection SynchronizationOnLocalVariableOrMethodParameter |
| synchronized (myInvocationHandler) { |
| if (!myInitialized) { |
| if (registrar != null) { |
| applyExtensions(registrar); |
| } |
| myInitialized = true; |
| } |
| } |
| return Boolean.TRUE; |
| } |
| }) == Boolean.TRUE; |
| } |
| |
| private void applyExtensions(DomExtensionsRegistrarImpl registrar) { |
| XmlFile file = myInvocationHandler.getFile(); |
| |
| final List<DomExtensionImpl> fixeds = registrar.getFixeds(); |
| final List<DomExtensionImpl> collections = registrar.getCollections(); |
| final List<DomExtensionImpl> attributes = registrar.getAttributes(); |
| if (!attributes.isEmpty()) { |
| ChildrenDescriptionsHolder<AttributeChildDescriptionImpl> newAttributes = new ChildrenDescriptionsHolder<AttributeChildDescriptionImpl>(myStaticGenericInfo.getAttributes()); |
| for (final DomExtensionImpl extension : attributes) { |
| newAttributes.addDescription(extension.addAnnotations(new AttributeChildDescriptionImpl(extension.getXmlName(), extension.getType()))); |
| } |
| myAttributes = internChildrenHolder(file, newAttributes); |
| } |
| |
| if (!fixeds.isEmpty()) { |
| ChildrenDescriptionsHolder<FixedChildDescriptionImpl> newFixeds = new ChildrenDescriptionsHolder<FixedChildDescriptionImpl>(myStaticGenericInfo.getFixed()); |
| for (final DomExtensionImpl extension : fixeds) { |
| //noinspection unchecked |
| newFixeds.addDescription(extension.addAnnotations(new FixedChildDescriptionImpl(extension.getXmlName(), extension.getType(), extension.getCount(), ArrayUtil.EMPTY_COLLECTION_ARRAY))); |
| } |
| myFixeds = internChildrenHolder(file, newFixeds); |
| } |
| if (!collections.isEmpty()) { |
| ChildrenDescriptionsHolder<CollectionChildDescriptionImpl> newCollections = new ChildrenDescriptionsHolder<CollectionChildDescriptionImpl>(myStaticGenericInfo.getCollections()); |
| for (final DomExtensionImpl extension : collections) { |
| newCollections.addDescription(extension.addAnnotations(new CollectionChildDescriptionImpl(extension.getXmlName(), extension.getType(), |
| Collections.<JavaMethod>emptyList() |
| ))); |
| } |
| myCollections = internChildrenHolder(file, newCollections); |
| } |
| |
| final List<DomExtensionImpl> customs = registrar.getCustoms(); |
| myCustomChildren = customs.isEmpty() ? null : ContainerUtil.map(customs, new Function<DomExtensionImpl, CustomDomChildrenDescriptionImpl>() { |
| @Override |
| public CustomDomChildrenDescriptionImpl fun(DomExtensionImpl extension) { |
| return new CustomDomChildrenDescriptionImpl(extension); |
| } |
| }); |
| } |
| |
| private static <T extends DomChildDescriptionImpl> ChildrenDescriptionsHolder<T> internChildrenHolder(XmlFile file, ChildrenDescriptionsHolder<T> holder) { |
| SoftReference<ConcurrentHashMap<ChildrenDescriptionsHolder, ChildrenDescriptionsHolder>> ref = file.getUserData(HOLDERS_CACHE); |
| ConcurrentHashMap<ChildrenDescriptionsHolder, ChildrenDescriptionsHolder> cache = SoftReference.dereference(ref); |
| if (cache == null) { |
| cache = new ConcurrentHashMap<ChildrenDescriptionsHolder, ChildrenDescriptionsHolder>(); |
| file.putUserData(HOLDERS_CACHE, new SoftReference<ConcurrentHashMap<ChildrenDescriptionsHolder, ChildrenDescriptionsHolder>>(cache)); |
| } |
| ChildrenDescriptionsHolder existing = cache.get(holder); |
| if (existing != null) { |
| //noinspection unchecked |
| return existing; |
| } |
| cache.put(holder, holder); |
| return holder; |
| } |
| |
| @Nullable |
| private DomExtensionsRegistrarImpl runDomExtenders() { |
| DomExtensionsRegistrarImpl registrar = null; |
| final Project project = myInvocationHandler.getManager().getProject(); |
| DomExtenderEP[] extenders = Extensions.getExtensions(DomExtenderEP.EP_NAME); |
| if (extenders.length > 0) { |
| for (final DomExtenderEP extenderEP : extenders) { |
| registrar = extenderEP.extend(project, myInvocationHandler, registrar); |
| } |
| } |
| |
| final AbstractDomChildDescriptionImpl description = myInvocationHandler.getChildDescription(); |
| if (description != null) { |
| final List<DomExtender> extendersFromParent = description.getUserData(DomExtensionImpl.DOM_EXTENDER_KEY); |
| if (extendersFromParent != null) { |
| if (registrar == null) registrar = new DomExtensionsRegistrarImpl(); |
| for (final DomExtender extender : extendersFromParent) { |
| //noinspection unchecked |
| extender.registerExtensions(myInvocationHandler.getProxy(), registrar); |
| } |
| } |
| } |
| return registrar; |
| } |
| |
| @Override |
| public XmlElement getNameElement(DomElement element) { |
| return myStaticGenericInfo.getNameElement(element); |
| } |
| |
| @Override |
| public GenericDomValue getNameDomElement(DomElement element) { |
| return myStaticGenericInfo.getNameDomElement(element); |
| } |
| |
| @Override |
| @NotNull |
| public List<? extends CustomDomChildrenDescription> getCustomNameChildrenDescription() { |
| checkInitialized(); |
| if (myCustomChildren != null) return myCustomChildren; |
| return myStaticGenericInfo.getCustomNameChildrenDescription(); |
| } |
| |
| @Override |
| public String getElementName(DomElement element) { |
| return myStaticGenericInfo.getElementName(element); |
| } |
| |
| @Override |
| @NotNull |
| public List<AbstractDomChildDescriptionImpl> getChildrenDescriptions() { |
| checkInitialized(); |
| final ArrayList<AbstractDomChildDescriptionImpl> list = new ArrayList<AbstractDomChildDescriptionImpl>(); |
| myAttributes.dumpDescriptions(list); |
| myFixeds.dumpDescriptions(list); |
| myCollections.dumpDescriptions(list); |
| list.addAll(myStaticGenericInfo.getCustomNameChildrenDescription()); |
| return list; |
| } |
| |
| @Override |
| @NotNull |
| public final List<FixedChildDescriptionImpl> getFixedChildrenDescriptions() { |
| checkInitialized(); |
| return myFixeds.getDescriptions(); |
| } |
| |
| @Override |
| @NotNull |
| public final List<CollectionChildDescriptionImpl> getCollectionChildrenDescriptions() { |
| checkInitialized(); |
| return myCollections.getDescriptions(); |
| } |
| |
| @Override |
| public FixedChildDescriptionImpl getFixedChildDescription(String tagName) { |
| checkInitialized(); |
| return myFixeds.findDescription(tagName); |
| } |
| |
| @Override |
| public DomFixedChildDescription getFixedChildDescription(@NonNls String tagName, @NonNls String namespace) { |
| checkInitialized(); |
| return myFixeds.getDescription(tagName, namespace); |
| } |
| |
| @Override |
| public CollectionChildDescriptionImpl getCollectionChildDescription(String tagName) { |
| checkInitialized(); |
| return myCollections.findDescription(tagName); |
| } |
| |
| @Override |
| public DomCollectionChildDescription getCollectionChildDescription(@NonNls String tagName, @NonNls String namespace) { |
| checkInitialized(); |
| return myCollections.getDescription(tagName, namespace); |
| } |
| |
| @Override |
| public AttributeChildDescriptionImpl getAttributeChildDescription(String attributeName) { |
| checkInitialized(); |
| return myAttributes.findDescription(attributeName); |
| } |
| |
| |
| @Override |
| public DomAttributeChildDescription getAttributeChildDescription(@NonNls String attributeName, @NonNls String namespace) { |
| checkInitialized(); |
| return myAttributes.getDescription(attributeName, namespace); |
| } |
| |
| @Override |
| public boolean isTagValueElement() { |
| return myStaticGenericInfo.isTagValueElement(); |
| } |
| |
| @Override |
| @NotNull |
| public List<AttributeChildDescriptionImpl> getAttributeChildrenDescriptions() { |
| checkInitialized(); |
| return myAttributes.getDescriptions(); |
| } |
| |
| @Override |
| public boolean processAttributeChildrenDescriptions(final Processor<AttributeChildDescriptionImpl> processor) { |
| final Set<AttributeChildDescriptionImpl> visited = new THashSet<AttributeChildDescriptionImpl>(); |
| if (!myStaticGenericInfo.processAttributeChildrenDescriptions(new Processor<AttributeChildDescriptionImpl>() { |
| @Override |
| public boolean process(AttributeChildDescriptionImpl attributeChildDescription) { |
| visited.add(attributeChildDescription); |
| return processor.process(attributeChildDescription); |
| } |
| })) { |
| return false; |
| } |
| for (final AttributeChildDescriptionImpl description : getAttributeChildrenDescriptions()) { |
| if (!visited.contains(description) && !processor.process(description)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| } |