blob: 17c6f1ab37ae9950b96e2e3737738ed5115bbd0a [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.xml.impl.schema;
import com.intellij.codeInsight.daemon.Validator;
import com.intellij.javaee.ExternalResourceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.psi.meta.PsiMetaData;
import com.intellij.psi.search.PsiElementProcessor;
import com.intellij.psi.util.CachedValue;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
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.SmartList;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import com.intellij.xml.XmlAttributeDescriptor;
import com.intellij.xml.XmlElementDescriptor;
import com.intellij.xml.XmlNSDescriptor;
import com.intellij.xml.XmlNSDescriptorEx;
import com.intellij.xml.impl.ExternalDocumentValidator;
import com.intellij.xml.util.XmlUtil;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author Mike
*/
@SuppressWarnings({"HardCodedStringLiteral"})
public class XmlNSDescriptorImpl implements XmlNSDescriptorEx,Validator<XmlDocument>, DumbAware, XmlNSTypeDescriptorProvider {
@NonNls
public static final String XSD_PREFIX = "xsd";
@NonNls public static final String SCHEMA_TAG_NAME = "schema";
@NonNls public static final String IMPORT_TAG_NAME = "import";
@NonNls static final String ELEMENT_TAG_NAME = "element";
@NonNls static final String ATTRIBUTE_TAG_NAME = "attribute";
@NonNls static final String COMPLEX_TYPE_TAG_NAME = "complexType";
@NonNls static final String SEQUENCE_TAG_NAME = "sequence";
private static final Logger LOG = Logger.getInstance("#com.intellij.xml.impl.schema.XmlNSDescriptorImpl");
@NonNls private static final Set<String> STD_TYPES = new HashSet<String>();
private static final Set<String> UNDECLARED_STD_TYPES = new HashSet<String>();
@NonNls private static final String INCLUDE_TAG_NAME = "include";
@NonNls private static final String REDEFINE_TAG_NAME = "redefine";
private static final ThreadLocal<Set<PsiFile>> myRedefinedDescriptorsInProcessing = new ThreadLocal<Set<PsiFile>>();
private final Map<QNameKey, CachedValue<XmlElementDescriptor>> myDescriptorsMap = Collections.synchronizedMap(new HashMap<QNameKey, CachedValue<XmlElementDescriptor>>());
private final Map<Pair<QNameKey, XmlTag>, CachedValue<TypeDescriptor>> myTypesMap = Collections.synchronizedMap(new HashMap<Pair<QNameKey,XmlTag>, CachedValue<TypeDescriptor>>());
private XmlFile myFile;
private XmlTag myTag;
private String myTargetNamespace;
private volatile Object[] dependencies;
private MultiMap<String,XmlTag> mySubstitutions;
public XmlNSDescriptorImpl(XmlFile file) {
init(file.getDocument());
}
public XmlNSDescriptorImpl() {
}
private static void collectDependencies(@Nullable XmlTag myTag, @NotNull XmlFile myFile, @NotNull Set<PsiFile> visited) {
if (visited.contains(myFile)) return;
visited.add( myFile );
if (myTag == null) return;
XmlTag[] tags = myTag.getSubTags();
for (final XmlTag tag : tags) {
if (equalsToSchemaName(tag, INCLUDE_TAG_NAME) ||
equalsToSchemaName(tag, IMPORT_TAG_NAME)
) {
final String schemaLocation = tag.getAttributeValue("schemaLocation");
if (schemaLocation != null) {
final XmlFile xmlFile = XmlUtil.findNamespace(myFile, schemaLocation);
addDependency(xmlFile, visited);
}
} else if (equalsToSchemaName(tag, REDEFINE_TAG_NAME)) {
myRedefinedDescriptorsInProcessing.set(visited);
try {
final XmlFile file = getRedefinedElementDescriptorFile(tag);
addDependency(file, visited);
} finally {
myRedefinedDescriptorsInProcessing.set(null);
}
}
}
final String schemaLocationDeclaration = myTag.getAttributeValue("schemaLocation", XmlUtil.XML_SCHEMA_INSTANCE_URI);
if(schemaLocationDeclaration != null) {
final StringTokenizer tokenizer = new StringTokenizer(schemaLocationDeclaration);
while(tokenizer.hasMoreTokens()){
final String uri = tokenizer.nextToken();
if(tokenizer.hasMoreTokens()){
PsiFile resourceLocation = ExternalResourceManager.getInstance().getResourceLocation(tokenizer.nextToken(), myFile, null);
if (resourceLocation == null) resourceLocation = ExternalResourceManager.getInstance().getResourceLocation(uri, myFile, null);
if (resourceLocation instanceof XmlFile) addDependency((XmlFile)resourceLocation, visited);
}
}
}
}
private static void addDependency(final XmlFile file, final Set<PsiFile> visited) {
if (file != null) {
final XmlDocument document = file.getDocument();
collectDependencies(document != null ? document.getRootTag():null, file, visited);
}
}
private static boolean checkSchemaNamespace(String name, XmlTag context){
final String namespace = context.getNamespaceByPrefix(XmlUtil.findPrefixByQualifiedName(name));
if(namespace.length() > 0){
return checkSchemaNamespace(namespace);
}
return XSD_PREFIX.equals(XmlUtil.findPrefixByQualifiedName(name));
}
public static boolean checkSchemaNamespace(String namespace) {
return XmlUtil.XML_SCHEMA_URI.equals(namespace) ||
XmlUtil.XML_SCHEMA_URI2.equals(namespace) ||
XmlUtil.XML_SCHEMA_URI3.equals(namespace);
}
public static boolean checkSchemaNamespace(XmlTag context) {
LOG.assertTrue(context.isValid());
final String namespace = context.getNamespace();
if (namespace.length() > 0) {
return checkSchemaNamespace(namespace);
}
return StringUtil.startsWithConcatenation(context.getName(), XSD_PREFIX, ":");
}
static @NotNull XmlNSDescriptorImpl getNSDescriptorToSearchIn(XmlTag rootTag, final String name, XmlNSDescriptorImpl defaultNSDescriptor) {
if (name == null) return defaultNSDescriptor;
final String namespacePrefix = XmlUtil.findPrefixByQualifiedName(name);
if (namespacePrefix.length() > 0) {
final String namespace = rootTag.getNamespaceByPrefix(namespacePrefix);
final XmlNSDescriptor nsDescriptor = rootTag.getNSDescriptor(namespace, true);
if (nsDescriptor instanceof XmlNSDescriptorImpl) {
return (XmlNSDescriptorImpl)nsDescriptor;
}
}
return defaultNSDescriptor;
}
@Nullable
private static XmlElementDescriptor getDescriptorFromParent(final XmlTag tag, XmlElementDescriptor elementDescriptor) {
final PsiElement parent = tag.getParent();
if (parent instanceof XmlTag) {
final XmlElementDescriptor descriptor = ((XmlTag)parent).getDescriptor();
if (descriptor != null) elementDescriptor = descriptor.getElementDescriptor(tag, (XmlTag)parent);
}
return elementDescriptor;
}
public static boolean processTagsInNamespace(@NotNull final XmlTag rootTag, String[] tagNames, PsiElementProcessor<XmlTag> processor) {
return processTagsInNamespaceInner(rootTag, tagNames, processor, null);
}
private static boolean processTagsInNamespaceInner(@NotNull final XmlTag rootTag, final String[] tagNames,
final PsiElementProcessor<XmlTag> processor, Set<XmlTag> visitedTags) {
if (visitedTags == null) visitedTags = new HashSet<XmlTag>(3);
else if (visitedTags.contains(rootTag)) return true;
visitedTags.add(rootTag);
XmlTag[] tags = rootTag.getSubTags();
NextTag:
for (XmlTag tag : tags) {
for(String tagName:tagNames) {
if (equalsToSchemaName(tag, tagName)) {
final String name = tag.getAttributeValue("name");
if (name != null) {
if (!processor.execute(tag)) {
return false;
}
}
continue NextTag;
}
}
if (equalsToSchemaName(tag, INCLUDE_TAG_NAME)) {
final String schemaLocation = tag.getAttributeValue("schemaLocation");
if (schemaLocation != null) {
final XmlFile xmlFile = XmlUtil.findNamespace(rootTag.getContainingFile(), schemaLocation);
if (xmlFile != null) {
final XmlDocument includedDocument = xmlFile.getDocument();
if (includedDocument != null) {
if (!processTagsInNamespaceInner(includedDocument.getRootTag(), tagNames, processor, visitedTags)) return false;
}
}
}
}
}
return true;
}
public static boolean equalsToSchemaName(@NotNull XmlTag tag, @NonNls String schemaName) {
return schemaName.equals(tag.getLocalName()) && checkSchemaNamespace(tag);
}
private static @Nullable XmlTag findSpecialTag(@NonNls String name, @NonNls String specialName, XmlTag rootTag, XmlNSDescriptorImpl descriptor,
HashSet<XmlTag> visited) {
XmlNSDescriptorImpl nsDescriptor = getNSDescriptorToSearchIn(rootTag, name, descriptor);
if (nsDescriptor != descriptor) {
final XmlDocument document = nsDescriptor.getDescriptorFile() != null ? nsDescriptor.getDescriptorFile().getDocument():null;
if (document == null) return null;
return findSpecialTag(
XmlUtil.findLocalNameByQualifiedName(name),
specialName,
document.getRootTag(),
nsDescriptor,
visited
);
}
if (visited == null) visited = new HashSet<XmlTag>(1);
else if (visited.contains(rootTag)) return null;
visited.add(rootTag);
XmlTag[] tags = rootTag.getSubTags();
return findSpecialTagIn(tags, specialName, name, rootTag, descriptor, visited);
}
private static XmlTag findSpecialTagIn(final XmlTag[] tags,
final String specialName,
final String name,
final XmlTag rootTag,
final XmlNSDescriptorImpl descriptor, final HashSet<XmlTag> visited) {
for (XmlTag tag : tags) {
if (equalsToSchemaName(tag, specialName)) {
String attribute = tag.getAttributeValue("name");
if (name.equals(attribute)
|| name.contains(":") && name.substring(name.indexOf(":") + 1).equals(attribute)) {
return tag;
}
} else if (equalsToSchemaName(tag, INCLUDE_TAG_NAME) ||
( equalsToSchemaName(tag, IMPORT_TAG_NAME) &&
rootTag.getNamespaceByPrefix(
XmlUtil.findPrefixByQualifiedName(name)
).equals(tag.getAttributeValue("namespace"))
)
) {
final String schemaLocation = tag.getAttributeValue("schemaLocation");
if (schemaLocation != null) {
final XmlFile xmlFile = XmlUtil.findNamespace(rootTag.getContainingFile(), schemaLocation);
if (xmlFile != null) {
final XmlDocument document = xmlFile.getDocument();
if (document != null) {
final XmlTag rTag = findSpecialTag(name, specialName, document.getRootTag(), descriptor, visited);
if (rTag != null) return rTag;
}
}
}
} else if (equalsToSchemaName(tag, REDEFINE_TAG_NAME)) {
XmlTag rTag = findSpecialTagIn(tag.getSubTags(), specialName, name, rootTag, descriptor, visited);
if (rTag != null) return rTag;
final XmlNSDescriptorImpl nsDescriptor = getRedefinedElementDescriptor(tag);
if (nsDescriptor != null) {
final XmlTag redefinedRootTag = ((XmlDocument)nsDescriptor.getDeclaration()).getRootTag();
rTag = findSpecialTagIn(redefinedRootTag.getSubTags(), specialName, name, redefinedRootTag, nsDescriptor, visited);
if (rTag != null) return rTag;
}
}
}
return null;
}
public static XmlNSDescriptorImpl getRedefinedElementDescriptor(final XmlTag parentTag) {
XmlFile file = getRedefinedElementDescriptorFile(parentTag);
if (file != null) {
final XmlDocument document = file.getDocument();
final PsiMetaData metaData = document != null ? document.getMetaData():null;
if (metaData instanceof XmlNSDescriptorImpl) return (XmlNSDescriptorImpl)metaData;
}
return null;
}
public static XmlFile getRedefinedElementDescriptorFile(final XmlTag parentTag) {
final String schemaL = parentTag.getAttributeValue(XmlUtil.SCHEMA_LOCATION_ATT);
if (schemaL != null) {
final PsiReference[] references = parentTag.getAttribute(XmlUtil.SCHEMA_LOCATION_ATT, null).getValueElement().getReferences();
if (references.length > 0) {
final PsiElement psiElement = references[references.length - 1].resolve();
if (psiElement instanceof XmlFile) {
return ((XmlFile)psiElement);
}
}
}
return null;
}
@Override
public XmlFile getDescriptorFile() {
return myFile;
}
@Override
public boolean isHierarhyEnabled() {
return true;
}
public String getDefaultNamespace(){
return myTargetNamespace != null ? myTargetNamespace : "";
}
@Override
@Nullable
public XmlElementDescriptor getElementDescriptor(String localName, String namespace) {
return getElementDescriptor(localName, namespace, new HashSet<XmlNSDescriptorImpl>(), false);
}
@Nullable
public XmlElementDescriptor getElementDescriptor(String localName, String namespace, Set<XmlNSDescriptorImpl> visited, boolean reference) {
if(visited.contains(this)) return null;
final QNameKey pair = new QNameKey(namespace, localName);
final CachedValue<XmlElementDescriptor> descriptor = myDescriptorsMap.get(pair);
if(descriptor != null) {
final XmlElementDescriptor value = descriptor.getValue();
if (value == null || value.getDeclaration().isValid()) return value;
}
final XmlTag rootTag = myTag;
if (rootTag == null) return null;
XmlTag[] tags = rootTag.getSubTags();
visited.add( this );
LOG.assertTrue(rootTag.isValid());
for (final XmlTag tag : tags) {
if (equalsToSchemaName(tag, ELEMENT_TAG_NAME)) {
String name = tag.getAttributeValue("name");
if (name != null) {
if (checkElementNameEquivalence(localName, namespace, name, tag)) {
final CachedValue<XmlElementDescriptor> cachedValue = CachedValuesManager.getManager(tag.getProject()).createCachedValue(new CachedValueProvider<XmlElementDescriptor>() {
@Override
public Result<XmlElementDescriptor> compute() {
final String name = tag.getAttributeValue("name");
if (name != null && !name.equals(pair.second)) {
myDescriptorsMap.remove(pair);
return new Result<XmlElementDescriptor>(null, PsiModificationTracker.MODIFICATION_COUNT);
}
final XmlElementDescriptor xmlElementDescriptor = createElementDescriptor(tag);
return new Result<XmlElementDescriptor>(xmlElementDescriptor, xmlElementDescriptor.getDependences());
}
}, false);
myDescriptorsMap.put(pair, cachedValue);
return cachedValue.getValue();
}
}
}
else if (equalsToSchemaName(tag, INCLUDE_TAG_NAME) ||
(reference &&
equalsToSchemaName(tag, IMPORT_TAG_NAME) &&
( namespace.equals(tag.getAttributeValue("namespace")) ||
namespace.length() == 0 && tag.getAttributeValue("namespace") == null
)
)
) {
final String schemaLocation = tag.getAttributeValue("schemaLocation");
if (schemaLocation != null) {
final XmlFile xmlFile = XmlUtil.findNamespace(rootTag.getContainingFile(), schemaLocation);
if (xmlFile != null) {
final XmlDocument includedDocument = xmlFile.getDocument();
if (includedDocument != null) {
final PsiMetaData data = includedDocument.getMetaData();
if (data instanceof XmlNSDescriptorImpl) {
final XmlElementDescriptor elementDescriptor =
((XmlNSDescriptorImpl)data).getElementDescriptor(localName, namespace, visited, reference);
if (elementDescriptor != null) {
//final CachedValue<XmlElementDescriptor> value = includedDocument.getManager().getCachedValuesManager()
// .createCachedValue(new CachedValueProvider<XmlElementDescriptor>() {
// public Result<XmlElementDescriptor> compute() {
// return new Result<XmlElementDescriptor>(elementDescriptor, elementDescriptor.getDependences());
// }
// }, false);
//return value.getValue();
return elementDescriptor;
}
}
}
}
}
} else if (equalsToSchemaName(tag, REDEFINE_TAG_NAME)) {
final XmlNSDescriptorImpl nsDescriptor = getRedefinedElementDescriptor(tag);
if (nsDescriptor != null) {
final XmlElementDescriptor xmlElementDescriptor = nsDescriptor.getElementDescriptor(localName, namespace, visited, reference);
if (xmlElementDescriptor instanceof XmlElementDescriptorImpl) {
return new RedefinedElementDescriptor((XmlElementDescriptorImpl)xmlElementDescriptor, this);
}
}
}
}
return null;
}
protected XmlElementDescriptor createElementDescriptor(final XmlTag tag) {
return new XmlElementDescriptorImpl(tag);
}
private boolean checkElementNameEquivalence(String localName, String namespace, String fqn, XmlTag context){
final String localAttrName = XmlUtil.findLocalNameByQualifiedName(fqn);
if (!localAttrName.equals(localName)) return false;
final String attrNamespace = context.getNamespaceByPrefix(XmlUtil.findPrefixByQualifiedName(fqn));
if (attrNamespace.equals(namespace)) return true;
if(myTargetNamespace == null){
if(XmlUtil.EMPTY_URI.equals(attrNamespace))
return true;
}
else {
if (myTargetNamespace.equals(namespace)) return true;
return context.getNSDescriptor(namespace, true) == this; // schema's targetNamespace could be different from file systemId
}
return false;
}
@Nullable
public XmlAttributeDescriptor getAttribute(String localName, String namespace, final XmlTag context) {
return getAttributeImpl(localName, namespace, null);
}
@Nullable
private XmlAttributeDescriptor getAttributeImpl(String localName, String namespace, @Nullable Set<XmlTag> visited) {
if (myTag == null) return null;
XmlNSDescriptorImpl nsDescriptor = (XmlNSDescriptorImpl)myTag.getNSDescriptor(namespace, true);
if (nsDescriptor != this && nsDescriptor != null) {
return nsDescriptor.getAttributeImpl(
localName,
namespace,
visited
);
}
if (visited == null) visited = new HashSet<XmlTag>(1);
else if(visited.contains(myTag)) return null;
visited.add(myTag);
XmlTag[] tags = myTag.getSubTags();
for (XmlTag tag : tags) {
if (equalsToSchemaName(tag, ATTRIBUTE_TAG_NAME)) {
String name = tag.getAttributeValue("name");
if (name != null) {
if (checkElementNameEquivalence(localName, namespace, name, tag)) {
return createAttributeDescriptor(tag);
}
}
} else if (equalsToSchemaName(tag, INCLUDE_TAG_NAME) ||
(equalsToSchemaName(tag, IMPORT_TAG_NAME) &&
namespace.equals(tag.getAttributeValue("namespace"))
)
) {
final String schemaLocation = tag.getAttributeValue("schemaLocation");
if (schemaLocation != null) {
final XmlFile xmlFile = XmlUtil.findNamespace(myTag.getContainingFile(), schemaLocation);
if (xmlFile != null) {
final XmlDocument includedDocument = xmlFile.getDocument();
if (includedDocument != null) {
final PsiMetaData data = includedDocument.getMetaData();
if(data instanceof XmlNSDescriptorImpl){
final XmlAttributeDescriptor attributeDescriptor = ((XmlNSDescriptorImpl)data).getAttributeImpl(localName, namespace,visited);
if(attributeDescriptor != null){
final CachedValue<XmlAttributeDescriptor> value = CachedValuesManager.getManager(includedDocument.getProject()).createCachedValue(
new CachedValueProvider<XmlAttributeDescriptor>(){
@Override
public Result<XmlAttributeDescriptor> compute() {
Object[] deps = attributeDescriptor.getDependences();
if (deps.length == 0) {
LOG.error(attributeDescriptor + " returned no dependencies");
}
return new Result<XmlAttributeDescriptor>(attributeDescriptor, deps);
}
},
false
);
return value.getValue();
}
}
}
}
}
}
}
return null;
}
protected XmlAttributeDescriptorImpl createAttributeDescriptor(final XmlTag tag) {
return new XmlAttributeDescriptorImpl(tag);
}
@Override
public TypeDescriptor getTypeDescriptor(XmlTag descriptorTag) {
String type = descriptorTag.getAttributeValue("type");
if (type != null) {
return getTypeDescriptor(type, descriptorTag);
}
return findTypeDescriptorImpl(descriptorTag, null, null, null);
}
@Override
public TypeDescriptor getTypeDescriptor(final String name, XmlTag context) {
if(checkSchemaNamespace(name, context)){
final String localNameByQualifiedName = XmlUtil.findLocalNameByQualifiedName(name);
if (STD_TYPES.contains(localNameByQualifiedName) &&
( name.length() == localNameByQualifiedName.length() ||
UNDECLARED_STD_TYPES.contains(localNameByQualifiedName)
)
)
return new StdTypeDescriptor(localNameByQualifiedName);
}
return findTypeDescriptor(name, context);
}
@Nullable
public XmlElementDescriptor getDescriptorByType(String qName, XmlTag instanceTag){
if(myTag == null) return null;
final TypeDescriptor typeDescriptor = findTypeDescriptor(qName, instanceTag);
if(!(typeDescriptor instanceof ComplexTypeDescriptor)) return null;
return new XmlElementDescriptorByType(instanceTag, (ComplexTypeDescriptor)typeDescriptor);
}
@Nullable
protected TypeDescriptor findTypeDescriptor(final String qname) {
return findTypeDescriptor(qname, myTag);
}
@Nullable
protected TypeDescriptor findTypeDescriptor(final String qname, XmlTag context) {
String namespace = context.getNamespaceByPrefix(XmlUtil.findPrefixByQualifiedName(qname));
return findTypeDescriptor(XmlUtil.findLocalNameByQualifiedName(qname), namespace);
}
@Nullable
private TypeDescriptor findTypeDescriptor(String localName, String namespace) {
return findTypeDescriptorImpl(myTag, localName, namespace, null);
}
@Nullable
protected TypeDescriptor findTypeDescriptorImpl(XmlTag rootTag, final String name, String namespace, Set<XmlTag> visited) {
XmlNSDescriptorImpl responsibleDescriptor = this;
if (namespace != null && namespace.length() != 0 && !namespace.equals(getDefaultNamespace())) {
final XmlNSDescriptor nsDescriptor = rootTag.getNSDescriptor(namespace, true);
if (nsDescriptor instanceof XmlNSDescriptorImpl) {
responsibleDescriptor = (XmlNSDescriptorImpl)nsDescriptor;
}
}
if (responsibleDescriptor != this) {
return responsibleDescriptor.findTypeDescriptor(XmlUtil.findLocalNameByQualifiedName(name));
}
if (rootTag == null) return null;
if (visited != null) {
if (visited.contains(rootTag)) return null;
visited.add(rootTag);
}
final Pair<QNameKey, XmlTag> pair = Pair.create(new QNameKey(name, namespace), rootTag);
final CachedValue<TypeDescriptor> descriptor = myTypesMap.get(pair);
if(descriptor != null) {
TypeDescriptor value = descriptor.getValue();
if (value == null ||
( value instanceof ComplexTypeDescriptor &&
((ComplexTypeDescriptor)value).getDeclaration().isValid()
)
)
return value;
}
XmlTag[] tags = rootTag.getSubTags();
if (visited == null) {
visited = new HashSet<XmlTag>(1);
visited.add(rootTag);
}
return doFindIn(tags, name, namespace, pair, rootTag, visited);
}
private TypeDescriptor doFindIn(final XmlTag[] tags, final String name, final String namespace, final Pair<QNameKey, XmlTag> pair, final XmlTag rootTag, final Set<XmlTag> visited) {
for (final XmlTag tag : tags) {
if (equalsToSchemaName(tag, "complexType")) {
if (name == null) {
CachedValue<TypeDescriptor> value = createAndPutTypesCachedValue(tag, pair);
return value.getValue();
}
String nameAttribute = tag.getAttributeValue("name");
if (isSameName(name, namespace, nameAttribute)) {
CachedValue<TypeDescriptor> cachedValue = createAndPutTypesCachedValue(tag, pair);
return cachedValue.getValue();
}
}
else if (equalsToSchemaName(tag, "simpleType")) {
if (name == null) {
CachedValue<TypeDescriptor> value = createAndPutTypesCachedValueSimpleType(tag, pair);
return value.getValue();
}
String nameAttribute = tag.getAttributeValue("name");
if (isSameName(name, namespace, nameAttribute)) {
CachedValue<TypeDescriptor> cachedValue = createAndPutTypesCachedValue(tag, pair);
return cachedValue.getValue();
}
}
else if (equalsToSchemaName(tag, INCLUDE_TAG_NAME) ||
( equalsToSchemaName(tag, IMPORT_TAG_NAME) &&
(namespace == null || !namespace.equals(getDefaultNamespace()))
)
) {
final String schemaLocation = tag.getAttributeValue("schemaLocation");
if (schemaLocation != null) {
final XmlFile xmlFile = XmlUtil.findNamespace(rootTag.getContainingFile(), schemaLocation);
if (xmlFile != null) {
final XmlDocument document = xmlFile.getDocument();
if (document != null) {
final CachedValue<TypeDescriptor> value = CachedValuesManager.getManager(tag.getProject()).createCachedValue(new CachedValueProvider<TypeDescriptor>() {
@Override
public Result<TypeDescriptor> compute() {
final String currentName = tag.getAttributeValue("name");
if (( currentName != null &&
!currentName.equals(XmlUtil.findLocalNameByQualifiedName(name)) ) ||
!xmlFile.isValid() ||
xmlFile.getDocument() == null
) {
myTypesMap.remove(pair);
return new Result<TypeDescriptor>(null, PsiModificationTracker.MODIFICATION_COUNT);
}
final XmlDocument document = xmlFile.getDocument();
final XmlNSDescriptorImpl nsDescriptor = findNSDescriptor(tag, document);
if (nsDescriptor == null) {
myTypesMap.remove(pair);
return new Result<TypeDescriptor>(null, PsiModificationTracker.MODIFICATION_COUNT);
}
final XmlTag rTag = document.getRootTag();
final TypeDescriptor complexTypeDescriptor = nsDescriptor.findTypeDescriptorImpl(rTag, name, namespace, visited);
return new Result<TypeDescriptor>(complexTypeDescriptor, rTag);
}
}, false
);
if (value.getValue() != null) {
myTypesMap.put(pair, value);
return value.getValue();
}
}
}
}
} else if (equalsToSchemaName(tag, REDEFINE_TAG_NAME)) {
final XmlTag[] subTags = tag.getSubTags();
TypeDescriptor descriptor = doFindIn(subTags, name, namespace, pair, rootTag, visited);
if (descriptor != null) return descriptor;
final XmlNSDescriptorImpl nsDescriptor = getRedefinedElementDescriptor(tag);
if (nsDescriptor != null) {
final XmlTag redefinedRootTag = ((XmlDocument)nsDescriptor.getDeclaration()).getRootTag();
descriptor = doFindIn(redefinedRootTag.getSubTags(), name, namespace, pair, redefinedRootTag, visited);
if (descriptor != null) return descriptor;
}
}
}
return null;
}
private boolean isSameName(@NotNull String name, String namespace, String nameAttribute) {
return nameAttribute != null &&
(nameAttribute.equals(name) || (name.contains(":") && nameAttribute.equals(name.substring(name.indexOf(":") + 1)))) &&
(namespace == null || namespace.length() == 0 || namespace.equals(getDefaultNamespace()))
;
}
private XmlNSDescriptorImpl findNSDescriptor(final XmlTag tag, final XmlDocument document) {
final XmlNSDescriptorImpl nsDescriptor;
if(IMPORT_TAG_NAME.equals(tag.getLocalName())) {
final XmlNSDescriptor importedDescriptor = (XmlNSDescriptor)document.getMetaData();
nsDescriptor = (importedDescriptor instanceof XmlNSDescriptorImpl) ?
(XmlNSDescriptorImpl)importedDescriptor:
this;
}
else {
nsDescriptor = this;
}
return nsDescriptor;
}
private CachedValue<TypeDescriptor> createAndPutTypesCachedValueSimpleType(final XmlTag tag, final Pair<QNameKey, XmlTag> pair) {
final CachedValue<TypeDescriptor> value = CachedValuesManager.getManager(tag.getProject()).createCachedValue(new CachedValueProvider<TypeDescriptor>() {
@Override
public CachedValueProvider.Result<TypeDescriptor> compute() {
final SimpleTypeDescriptor simpleTypeDescriptor = new SimpleTypeDescriptor(tag);
return new Result<TypeDescriptor>(simpleTypeDescriptor, tag);
}
}, false);
myTypesMap.put(pair, value);
return value;
}
private CachedValue<TypeDescriptor> createAndPutTypesCachedValue(final XmlTag tag, final Pair<QNameKey, XmlTag> pair) {
final CachedValue<TypeDescriptor> value = CachedValuesManager.getManager(tag.getProject()).createCachedValue(
new CachedValueProvider<TypeDescriptor>() {
@Override
public CachedValueProvider.Result<TypeDescriptor> compute() {
final String name = tag.getAttributeValue("name");
if (name != null &&
pair.first != null &&
pair.first.first != null &&
!name.equals(XmlUtil.findLocalNameByQualifiedName(pair.first.first))
) {
myTypesMap.remove(pair);
return new Result<TypeDescriptor>(null, PsiModificationTracker.MODIFICATION_COUNT);
}
final ComplexTypeDescriptor complexTypeDescriptor = new ComplexTypeDescriptor(XmlNSDescriptorImpl.this, tag);
return new Result<TypeDescriptor>(complexTypeDescriptor, tag);
}
}, false);
myTypesMap.put(pair, value);
return value;
}
@Override
public XmlElementDescriptor getElementDescriptor(@NotNull XmlTag tag) {
PsiElement parent = tag.getParent();
final String namespace = tag.getNamespace();
while(parent instanceof XmlTag && !namespace.equals(((XmlTag)parent).getNamespace()))
parent = parent.getContext();
if (parent instanceof XmlTag) {
final XmlTag parentTag = (XmlTag)parent;
final XmlElementDescriptor parentDescriptor = parentTag.getDescriptor();
if(parentDescriptor != null){
XmlElementDescriptor elementDescriptorFromParent = parentDescriptor.getElementDescriptor(tag, parentTag);
if (elementDescriptorFromParent == null) {
elementDescriptorFromParent = getDescriptorFromParent(tag, elementDescriptorFromParent);
}
if (elementDescriptorFromParent instanceof AnyXmlElementDescriptor) {
final XmlElementDescriptor elementDescriptor = getElementDescriptor(tag.getLocalName(), namespace);
if (elementDescriptor != null) return elementDescriptor;
}
return elementDescriptorFromParent;
}
else{
return null;
}
}
else {
XmlElementDescriptor elementDescriptor = getElementDescriptor(tag.getLocalName(), tag.getNamespace());
if (elementDescriptor == null) {
elementDescriptor = getDescriptorFromParent(tag, elementDescriptor);
}
return elementDescriptor;
}
}
@Override
@NotNull
public XmlElementDescriptor[] getRootElementsDescriptors(@Nullable final XmlDocument doc) {
class CollectElementsProcessor implements PsiElementProcessor<XmlTag> {
final List<XmlElementDescriptor> result = new ArrayList<XmlElementDescriptor>();
@Override
public boolean execute(@NotNull final XmlTag element) {
ContainerUtil.addIfNotNull(result, getElementDescriptor(element.getAttributeValue("name"), getDefaultNamespace()));
return true;
}
}
CollectElementsProcessor processor = new CollectElementsProcessor() {
@Override
public boolean execute(@NotNull final XmlTag element) {
if (!XmlElementDescriptorImpl.isAbstractDeclaration(element)) return super.execute(element);
return true;
}
};
processTagsInNamespace(myTag, new String[] {ELEMENT_TAG_NAME}, processor);
return processor.result.toArray(new XmlElementDescriptor[processor.result.size()]);
}
public XmlAttributeDescriptor[] getRootAttributeDescriptors(final XmlTag context) {
class CollectAttributesProcessor implements PsiElementProcessor<XmlTag> {
final List<XmlAttributeDescriptor> result = new ArrayList<XmlAttributeDescriptor>();
@Override
public boolean execute(@NotNull final XmlTag element) {
result.add(createAttributeDescriptor(element));
return true;
}
}
CollectAttributesProcessor processor = new CollectAttributesProcessor();
processTagsInNamespace(myTag, new String[] {ATTRIBUTE_TAG_NAME}, processor);
return processor.result.toArray(new XmlAttributeDescriptor[processor.result.size()]);
}
@Nullable
public XmlTag findGroup(String name) {
return findSpecialTag(name,"group",myTag, this, null);
}
@Nullable
public XmlTag findAttributeGroup(String name) {
return findSpecialTag(name, "attributeGroup", myTag, this, null);
}
public XmlElementDescriptor[] getSubstitutes(String localName, String namespace) {
if (!initSubstitutes()) {
return XmlElementDescriptor.EMPTY_ARRAY;
}
Collection<XmlTag> substitutions = mySubstitutions.get(localName);
if (substitutions.isEmpty()) return XmlElementDescriptor.EMPTY_ARRAY;
List<XmlElementDescriptor> result = new SmartList<XmlElementDescriptor>();
for (XmlTag tag : substitutions) {
final String substAttr = tag.getAttributeValue("substitutionGroup");
if (substAttr != null && checkElementNameEquivalence(localName, namespace, substAttr, tag)) {
result.add(createElementDescriptor(tag));
}
}
return result.toArray(new XmlElementDescriptor[result.size()]);
}
private boolean initSubstitutes() {
if (mySubstitutions == null && myTag != null) {
mySubstitutions = new MultiMap<String, XmlTag>();
if (myTag == null) return false;
XmlTag[] tags = myTag.getSubTags();
for (XmlTag tag : tags) {
if (equalsToSchemaName(tag, ELEMENT_TAG_NAME)) {
final String substAttr = tag.getAttributeValue("substitutionGroup");
if (substAttr != null) {
String substLocalName = XmlUtil.findLocalNameByQualifiedName(substAttr);
mySubstitutions.putValue(substLocalName, tag);
}
}
}
}
return mySubstitutions != null;
}
@Override
public PsiElement getDeclaration(){
return myFile.getDocument();
}
@Override
public String getName(PsiElement context){
return getName();
}
@Override
public String getName(){
return "";
}
@Override
public void init(PsiElement element){
myFile = (XmlFile) element.getContainingFile();
if (element instanceof XmlTag) {
myTag = (XmlTag)element;
} else {
final XmlDocument document = myFile.getDocument();
if (document != null) {
myTag = document.getRootTag();
}
}
if (myTag != null) {
myTargetNamespace = myTag.getAttributeValue("targetNamespace");
}
final THashSet<PsiFile> dependenciesSet = new THashSet<PsiFile>();
final Set<PsiFile> redefineProcessingSet = myRedefinedDescriptorsInProcessing.get();
if (redefineProcessingSet != null) {
dependenciesSet.addAll(redefineProcessingSet);
}
collectDependencies(myTag, myFile, dependenciesSet);
dependencies = ArrayUtil.toObjectArray(dependenciesSet);
}
@Override
public Object[] getDependences() {
if (dependencies == null) dependencies = myFile == null ? ArrayUtil.EMPTY_OBJECT_ARRAY : new Object[] {myFile}; // init was not called
return dependencies;
}
static {
STD_TYPES.add("string");
STD_TYPES.add("normalizedString");
STD_TYPES.add("token");
STD_TYPES.add("byte");
STD_TYPES.add("unsignedByte");
STD_TYPES.add("base64Binary");
STD_TYPES.add("hexBinary");
STD_TYPES.add("integer");
STD_TYPES.add("positiveInteger");
STD_TYPES.add("negativeInteger");
STD_TYPES.add("nonNegativeInteger");
STD_TYPES.add("nonPositiveInteger");
STD_TYPES.add("int");
STD_TYPES.add("unsignedInt");
STD_TYPES.add("long");
STD_TYPES.add("unsignedLong");
STD_TYPES.add("short");
STD_TYPES.add("unsignedShort");
STD_TYPES.add("decimal");
STD_TYPES.add("float");
STD_TYPES.add("double");
STD_TYPES.add("boolean");
STD_TYPES.add("time");
STD_TYPES.add("dateTime");
STD_TYPES.add("duration");
STD_TYPES.add("date");
STD_TYPES.add("gMonth");
STD_TYPES.add("gYear");
STD_TYPES.add("gYearMonth");
STD_TYPES.add("gDay");
STD_TYPES.add("gMonthDay");
STD_TYPES.add("Name");
STD_TYPES.add("QName");
STD_TYPES.add("NCName");
STD_TYPES.add("anyURI");
STD_TYPES.add("language");
STD_TYPES.add("ID");
STD_TYPES.add("IDREF");
STD_TYPES.add("IDREFS");
STD_TYPES.add("ENTITY");
STD_TYPES.add("ENTITIES");
STD_TYPES.add("NOTATION");
STD_TYPES.add("NMTOKEN");
STD_TYPES.add("NMTOKENS");
STD_TYPES.add("anySimpleType");
UNDECLARED_STD_TYPES.add("anySimpleType");
}
@Override
public void validate(@NotNull XmlDocument context, @NotNull Validator.ValidationHost host) {
ExternalDocumentValidator.doValidation(context,host);
}
public XmlTag getTag() {
return myTag;
}
public boolean hasSubstitutions() {
initSubstitutes();
return mySubstitutions != null && mySubstitutions.size() > 0;
}
public boolean isValid() {
return myFile != null && getDeclaration().isValid();
}
static class QNameKey extends Pair<String, String>{
QNameKey(String name, String namespace) {
super(name, namespace);
}
}
}