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