EA-32857 - NPE: XmlUtil.findPrefixByQualifiedName
[idea/community.git] / xml / impl / src / com / intellij / xml / impl / schema / XmlElementDescriptorImpl.java
1 /*
2  * Copyright 2000-2011 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.xml.impl.schema;
17
18 import com.intellij.codeInsight.daemon.Validator;
19 import com.intellij.psi.PsiElement;
20 import com.intellij.psi.PsiFile;
21 import com.intellij.psi.meta.PsiWritableMetaData;
22 import com.intellij.psi.util.PsiTreeUtil;
23 import com.intellij.psi.xml.*;
24 import com.intellij.util.ArrayUtil;
25 import com.intellij.util.IncorrectOperationException;
26 import com.intellij.xml.*;
27 import com.intellij.xml.util.XmlUtil;
28 import org.jetbrains.annotations.NonNls;
29 import org.jetbrains.annotations.Nullable;
30 import org.jetbrains.annotations.NotNull;
31
32 /**
33  * @author Mike
34  */
35 public class XmlElementDescriptorImpl implements XmlElementDescriptor, PsiWritableMetaData, Validator<XmlTag>,
36                                                  XmlElementDescriptorAwareAboutChildren {
37   protected XmlTag myDescriptorTag;
38   protected volatile XmlNSDescriptor NSDescriptor;
39   private volatile @Nullable Validator<XmlTag> myValidator;
40
41   @NonNls
42   public static final String QUALIFIED_ATTR_VALUE = "qualified";
43   @NonNls
44   public static final String NONQUALIFIED_ATTR_VALUE = "unqualified";
45   @NonNls
46   private static final String ELEMENT_FORM_DEFAULT = "elementFormDefault";
47
48   public XmlElementDescriptorImpl(@Nullable XmlTag descriptorTag) {
49     myDescriptorTag = descriptorTag;
50   }
51
52   public XmlElementDescriptorImpl() {}
53
54   public PsiElement getDeclaration(){
55     return myDescriptorTag;
56   }
57
58   public String getName(PsiElement context){
59     String value = myDescriptorTag.getAttributeValue("name");
60
61     if(context instanceof XmlElement){
62       final String namespace = getNamespaceByContext(context);
63       final XmlTag tag = PsiTreeUtil.getParentOfType(context, XmlTag.class, false);
64
65       if(tag != null){
66         final String namespacePrefix = tag.getPrefixByNamespace(namespace);
67
68         if (namespacePrefix != null && namespacePrefix.length() > 0) {
69           final XmlTag rootTag = ((XmlFile)myDescriptorTag.getContainingFile()).getRootTag();
70           String elementFormDefault;
71
72           if (rootTag != null && 
73               ( NONQUALIFIED_ATTR_VALUE.equals(elementFormDefault = rootTag.getAttributeValue(ELEMENT_FORM_DEFAULT)) || elementFormDefault == null /*unqualified is default*/) &&
74               tag.getNamespaceByPrefix("").length() == 0
75              ) {
76             value = XmlUtil.findLocalNameByQualifiedName(value);
77           } else {
78             value = namespacePrefix + ":" + XmlUtil.findLocalNameByQualifiedName(value);
79           }
80         }
81       }
82     }
83     return value;
84   }
85
86   /** getter for _local_ name */
87   public String getName() {
88     return XmlUtil.findLocalNameByQualifiedName(getName(null));
89   }
90
91   public String getNamespaceByContext(PsiElement context){
92     //while(context != null){
93     //  if(context instanceof XmlTag){
94     //    final XmlTag contextTag = ((XmlTag)context);
95     //    final XmlNSDescriptorImpl schemaDescriptor = XmlUtil.findXmlNSDescriptorByType(contextTag);
96     //    if (schemaDescriptor != null) {
97     //      return schemaDescriptor.getDefaultNamespace();
98     //    }
99     //  }
100     //  context = context.getContext();
101     //}
102     return getNamespace();
103   }
104
105   public String getNamespace(){
106     String name = getName();
107     if (name == null) return XmlUtil.EMPTY_URI;
108     final String namespacePrefix = XmlUtil.findPrefixByQualifiedName(name);
109     final XmlNSDescriptorImpl xmlNSDescriptor = (XmlNSDescriptorImpl)getNSDescriptor();
110     if(xmlNSDescriptor == null || myDescriptorTag == null) return XmlUtil.EMPTY_URI;
111     return "".equals(namespacePrefix) ?
112            xmlNSDescriptor.getDefaultNamespace() :
113            myDescriptorTag.getNamespaceByPrefix(namespacePrefix);
114   }
115
116   public void init(PsiElement element){
117     if (myDescriptorTag!=element && myDescriptorTag!=null) {
118       NSDescriptor = null;
119     }
120     myDescriptorTag = (XmlTag) element;
121   }
122
123   public Object[] getDependences(){
124     return new Object[]{myDescriptorTag};
125   }
126
127   private XmlNSDescriptor getNSDescriptor(XmlElement context) {
128     XmlNSDescriptor nsDescriptor = getNSDescriptor();
129     if (context instanceof XmlTag && nsDescriptor instanceof XmlNSDescriptorImpl) {
130       final String defaultNamespace = ((XmlNSDescriptorImpl)nsDescriptor).getDefaultNamespace();
131       if (XmlUtil.XML_SCHEMA_URI.equals(defaultNamespace)) return nsDescriptor; // do not check for overriden for efficiency
132
133       final XmlTag tag = (XmlTag)context;
134       final String tagNs = tag.getNamespace();
135       if (tagNs.equals(defaultNamespace)) {
136         XmlNSDescriptor previousDescriptor = nsDescriptor;
137         nsDescriptor = tag.getNSDescriptor(tagNs, true);
138         if (nsDescriptor == null) nsDescriptor = previousDescriptor;
139       }
140     }
141     
142     return nsDescriptor;
143   }
144
145   public XmlNSDescriptor getNSDescriptor() {
146     XmlNSDescriptor nsDescriptor = NSDescriptor;
147     if (nsDescriptor == null || !NSDescriptor.getDeclaration().isValid()) {
148       final XmlFile file = XmlUtil.getContainingFile(getDeclaration());
149       if(file == null) return null;
150       final XmlDocument document = file.getDocument();
151       if(document == null) return null;
152       NSDescriptor = nsDescriptor = (XmlNSDescriptor)document.getMetaData();
153     }
154
155     return nsDescriptor;
156   }
157
158   @Override
159   public XmlElementsGroup getTopGroup() {
160     TypeDescriptor type = getType();
161     return type instanceof ComplexTypeDescriptor ? ((ComplexTypeDescriptor)type).getTopGroup() : null;
162   }
163
164   @Nullable
165   public TypeDescriptor getType() {
166     return getType(null);
167   }
168
169   @Nullable
170   public TypeDescriptor getType(XmlElement context) {
171     final XmlNSDescriptor nsDescriptor = getNSDescriptor(context);
172     if (!(nsDescriptor instanceof XmlNSTypeDescriptorProvider)) return null;
173
174     TypeDescriptor type = ((XmlNSTypeDescriptorProvider) nsDescriptor).getTypeDescriptor(myDescriptorTag);
175     if (type == null) {
176       String substAttr = myDescriptorTag.getAttributeValue("substitutionGroup");
177       if (substAttr != null) {
178         final String namespacePrefix = XmlUtil.findPrefixByQualifiedName(substAttr);
179         final String namespace = "".equals(namespacePrefix) ?
180                                  ((XmlNSDescriptorImpl)getNSDescriptor()).getDefaultNamespace() :
181                                  myDescriptorTag.getNamespaceByPrefix(namespacePrefix);
182         final String local = XmlUtil.findLocalNameByQualifiedName(substAttr);
183         final XmlElementDescriptorImpl originalElement = (XmlElementDescriptorImpl)((XmlNSDescriptorImpl)getNSDescriptor()).getElementDescriptor(local, namespace);
184         if (originalElement != null) {
185           type = originalElement.getType(context);
186         }
187       }
188     }
189     return type;
190   }
191
192   public XmlElementDescriptor[] getElementsDescriptors(XmlTag context) {
193     if (context != null) {
194       final XmlElementDescriptor parentDescriptorByType = XmlUtil.findXmlDescriptorByType(context);
195       if (parentDescriptorByType != null && !parentDescriptorByType.equals(this)) {
196         return parentDescriptorByType.getElementsDescriptors(context);
197       }
198     }
199
200     XmlElementDescriptor[] elementsDescriptors = getElementsDescriptorsImpl(context);
201
202     final TypeDescriptor type = getType(context);
203
204     if (type instanceof ComplexTypeDescriptor) {
205       final ComplexTypeDescriptor descriptor = (ComplexTypeDescriptor)type;
206       String contextNs;
207       PsiFile containingFile = context != null ? context.getContainingFile():null;
208
209       if (context != null && !containingFile.isPhysical()) {
210         containingFile = containingFile.getOriginalFile();
211         //context = context.getParentTag();
212       }
213
214       if (context != null &&
215           ( descriptor.canContainTag(context.getLocalName(), contextNs = context.getNamespace(), context ) &&
216             (!contextNs.equals(getNamespace()) || descriptor.hasAnyInContentModel())
217           ) ) {
218         final XmlNSDescriptor nsDescriptor = getNSDescriptor();
219
220         if (nsDescriptor != null) {
221           elementsDescriptors = ArrayUtil.mergeArrays(
222             elementsDescriptors,
223             nsDescriptor.getRootElementsDescriptors(((XmlFile)containingFile).getDocument())
224           );
225         }
226       }
227     }
228
229     return elementsDescriptors;
230   }
231
232   private XmlElementDescriptor[] getElementsDescriptorsImpl(XmlElement context) {
233     TypeDescriptor type = getType(context);
234
235     if (type instanceof ComplexTypeDescriptor) {
236       ComplexTypeDescriptor typeDescriptor = (ComplexTypeDescriptor)type;
237
238       return typeDescriptor.getElements(context);
239     }
240
241     return EMPTY_ARRAY;
242   }
243
244   public XmlAttributeDescriptor[] getAttributesDescriptors(final XmlTag context) {
245     TypeDescriptor type = getType(context);
246
247     if (type instanceof ComplexTypeDescriptor) {
248       ComplexTypeDescriptor typeDescriptor = (ComplexTypeDescriptor)type;
249       XmlAttributeDescriptor[] attributeDescriptors = typeDescriptor.getAttributes(context);
250
251       if (context != null) {
252         final String contextNs = context.getNamespace();
253
254         boolean seenXmlNs = false;
255         for(String ns:context.knownNamespaces()) {
256           if (!contextNs.equals(ns) && ns.length() > 0) {
257             seenXmlNs |= XmlUtil.XML_NAMESPACE_URI.equals(ns);
258             attributeDescriptors = updateAttributeDescriptorsFromAny(context, typeDescriptor, attributeDescriptors, ns);
259           }
260         }
261
262         if (!seenXmlNs) {
263           attributeDescriptors = updateAttributeDescriptorsFromAny(context, typeDescriptor, attributeDescriptors, XmlUtil.XML_NAMESPACE_URI);
264         }
265       }
266       return attributeDescriptors;
267     }
268
269     return XmlAttributeDescriptor.EMPTY;
270   }
271
272   private static XmlAttributeDescriptor[] updateAttributeDescriptorsFromAny(final XmlTag context,
273                                                                             final ComplexTypeDescriptor typeDescriptor,
274                                                                             XmlAttributeDescriptor[] attributeDescriptors,
275                                                                             final String ns) {
276     if (typeDescriptor.canContainAttribute(ns, null) != ComplexTypeDescriptor.CanContainAttributeType.CanNotContain) {
277       final XmlNSDescriptor descriptor = context.getNSDescriptor(ns, true);
278
279       if (descriptor instanceof XmlNSDescriptorImpl) {
280         attributeDescriptors = ArrayUtil.mergeArrays(
281           attributeDescriptors,
282           ((XmlNSDescriptorImpl)descriptor).getRootAttributeDescriptors(context)
283         );
284       }
285     }
286     return attributeDescriptors;
287   }
288
289   public XmlAttributeDescriptor getAttributeDescriptor(String attributeName, final XmlTag context){
290     return getAttributeDescriptorImpl(attributeName,context);
291   }
292
293   @Nullable
294   private XmlAttributeDescriptor getAttributeDescriptorImpl(final String attributeName, XmlTag context) {
295     final String localName = XmlUtil.findLocalNameByQualifiedName(attributeName);
296     final String namespacePrefix = XmlUtil.findPrefixByQualifiedName(attributeName);
297     final XmlNSDescriptorImpl xmlNSDescriptor = (XmlNSDescriptorImpl)getNSDescriptor();
298     final String namespace = "".equals(namespacePrefix) ?
299                              ((xmlNSDescriptor != null)?xmlNSDescriptor.getDefaultNamespace():"") :
300                              context.getNamespaceByPrefix(namespacePrefix);
301
302     XmlAttributeDescriptor attribute = getAttribute(localName, namespace, context, attributeName);
303     
304     if (attribute instanceof AnyXmlAttributeDescriptor && namespace.length() > 0) {
305       final XmlNSDescriptor candidateNSDescriptor = context.getNSDescriptor(namespace, true);
306
307       if (candidateNSDescriptor instanceof XmlNSDescriptorImpl) {
308         final XmlNSDescriptorImpl nsDescriptor = (XmlNSDescriptorImpl)candidateNSDescriptor;
309
310         final XmlAttributeDescriptor xmlAttributeDescriptor = nsDescriptor.getAttribute(localName, namespace, context);
311         if (xmlAttributeDescriptor != null) return xmlAttributeDescriptor;
312         else {
313           final ComplexTypeDescriptor.CanContainAttributeType containAttributeType =
314             ((AnyXmlAttributeDescriptor)attribute).getCanContainAttributeType();
315           if (containAttributeType == ComplexTypeDescriptor.CanContainAttributeType.CanContainButDoNotSkip) {
316             attribute = null;
317           }
318         }
319       }
320     }
321     return attribute;
322   }
323
324   public XmlAttributeDescriptor getAttributeDescriptor(XmlAttribute attribute){
325     return getAttributeDescriptorImpl(attribute.getName(),attribute.getParent());
326   }
327
328   @Nullable
329   private XmlAttributeDescriptor getAttribute(String attributeName, String namespace, XmlTag context, String qName) {
330     XmlAttributeDescriptor[] descriptors = getAttributesDescriptors(context);
331
332     for (XmlAttributeDescriptor descriptor : descriptors) {
333       if (descriptor.getName().equals(attributeName) &&
334           descriptor.getName(context).equals(qName)
335          ) {
336         return descriptor;
337       }
338     }
339
340     TypeDescriptor type = getType(context);
341     if (type instanceof ComplexTypeDescriptor) {
342       ComplexTypeDescriptor descriptor = (ComplexTypeDescriptor)type;
343       final ComplexTypeDescriptor.CanContainAttributeType containAttributeType = descriptor.canContainAttribute(namespace, qName);
344
345       if (containAttributeType != ComplexTypeDescriptor.CanContainAttributeType.CanNotContain) {
346         return new AnyXmlAttributeDescriptor(attributeName, containAttributeType);
347       }
348     }
349
350     return null;
351   }
352
353   public int getContentType() {
354     TypeDescriptor type = getType();
355
356     if (type instanceof ComplexTypeDescriptor) {
357       return ((ComplexTypeDescriptor)type).getContentType();
358     }
359
360     return CONTENT_TYPE_MIXED;
361   }
362
363   @Override
364   public String getDefaultValue() {
365     return myDescriptorTag.getAttributeValue("default");
366   }
367
368   @Nullable
369   public XmlElementDescriptor getElementDescriptor(final String name) {
370       final String localName = XmlUtil.findLocalNameByQualifiedName(name);
371       final String namespacePrefix = XmlUtil.findPrefixByQualifiedName(name);
372       final String namespace = "".equals(namespacePrefix) ?
373                                ((XmlNSDescriptorImpl)getNSDescriptor()).getDefaultNamespace() :
374                                myDescriptorTag.getNamespaceByPrefix(namespacePrefix);
375     return getElementDescriptor(localName, namespace, null, name);
376   }
377
378   @Nullable
379   protected XmlElementDescriptor getElementDescriptor(final String localName, final String namespace, XmlElement context, String fullName) {
380     XmlElementDescriptor[] elements = getElementsDescriptorsImpl(context);
381
382     for (XmlElementDescriptor element1 : elements) {
383       final XmlElementDescriptorImpl element = (XmlElementDescriptorImpl)element1;
384       final String namespaceByContext = element.getNamespaceByContext(context);
385
386       if (element.getName().equals(localName)) {
387         if (namespace == null ||
388             namespace.equals(namespaceByContext) ||
389             namespaceByContext.equals(XmlUtil.EMPTY_URI) ||
390             element.getName(context).equals(fullName) || (namespace.length() == 0) &&
391                                                          element.getDefaultName().equals(fullName)
392            ) {
393           return element;
394         }
395         else {
396           final XmlNSDescriptor descriptor = context instanceof XmlTag? ((XmlTag)context).getNSDescriptor(namespace, true) : null;
397
398           // schema's targetNamespace could be different from file systemId used as NS
399           if (descriptor instanceof XmlNSDescriptorImpl &&
400               ((XmlNSDescriptorImpl)descriptor).getDefaultNamespace().equals(namespaceByContext)
401              ) {
402             return element;
403           }
404         }
405       }
406     }
407
408     TypeDescriptor type = getType(context);
409     if (type instanceof ComplexTypeDescriptor) {
410       ComplexTypeDescriptor descriptor = (ComplexTypeDescriptor)type;
411       if (descriptor.canContainTag(localName, namespace, context)) {
412         return new AnyXmlElementDescriptor(this, getNSDescriptor());
413       }
414     }
415
416     return null;
417   }
418
419   public XmlElementDescriptor getElementDescriptor(XmlTag element, XmlTag contextTag){
420     final XmlElement context = (XmlElement)element.getParent();
421
422     XmlElementDescriptor elementDescriptor = getElementDescriptor(
423       element.getLocalName(),
424       element.getNamespace(), context,
425       element.getName()
426     );
427
428     if(elementDescriptor == null || element.getAttributeValue("xsi:type") != null){
429       final XmlElementDescriptor xmlDescriptorByType = XmlUtil.findXmlDescriptorByType(element);
430
431       if (xmlDescriptorByType != null) elementDescriptor = xmlDescriptorByType;
432       else if (context instanceof XmlTag && ((XmlTag)context).getAttributeValue("xsi:type") != null && askParentDescriptorViaXsi()) {
433         final XmlElementDescriptor parentXmlDescriptorByType = XmlUtil.findXmlDescriptorByType(((XmlTag)context));
434         if (parentXmlDescriptorByType != null) {
435           elementDescriptor = parentXmlDescriptorByType.getElementDescriptor(element, contextTag);
436         }
437       }
438     }
439     return elementDescriptor;
440   }
441
442   protected boolean askParentDescriptorViaXsi() {
443     return true;
444   }
445
446   public String getQualifiedName() {
447     if (!"".equals(getNS())) {
448       return getNS() + ":" + getName();
449     }
450
451     return getName();
452   }
453
454   @Nullable
455   private String getNS(){
456     return XmlUtil.findNamespacePrefixByURI((XmlFile) myDescriptorTag.getContainingFile(), getNamespace());
457   }
458
459   public String getDefaultName() {
460     final PsiFile psiFile = myDescriptorTag.getContainingFile();
461     XmlTag rootTag = psiFile instanceof XmlFile ?((XmlFile)psiFile).getRootTag():null;
462
463     if (rootTag != null && QUALIFIED_ATTR_VALUE.equals(rootTag.getAttributeValue(ELEMENT_FORM_DEFAULT))) {
464       return getQualifiedName();
465     }
466
467     return getName();
468   }
469
470   public boolean isAbstract() {
471     return isAbstractDeclaration(myDescriptorTag);
472   }
473
474   public static Boolean isAbstractDeclaration(final XmlTag descriptorTag) {
475     return Boolean.valueOf(descriptorTag.getAttributeValue("abstract"));
476   }
477
478   public void setName(String name) throws IncorrectOperationException {
479     NamedObjectDescriptor.setName(myDescriptorTag, name);
480   }
481
482   public void setValidator(final Validator<XmlTag> validator) {
483     myValidator = validator;
484   }
485
486   public void validate(@NotNull XmlTag context, @NotNull ValidationHost host) {
487     Validator<XmlTag> validator = myValidator;
488     if (validator != null) {
489       validator.validate(context, host);
490     }
491   }
492
493   public boolean allowElementsFromNamespace(final String namespace, final XmlTag context) {
494     final TypeDescriptor type = getType(context);
495     
496     if (type instanceof ComplexTypeDescriptor) {
497       final ComplexTypeDescriptor typeDescriptor = (ComplexTypeDescriptor)type;
498       return typeDescriptor.canContainTag("a", namespace, context) ||
499              typeDescriptor.getNsDescriptors().hasSubstitutions() ||
500              XmlUtil.nsFromTemplateFramework(namespace)
501         ;
502     }
503     return false;
504   }
505
506   @Override
507   public String toString() {
508     return getName() + " (" + getNamespace() + ")";
509   }
510
511   @Override
512   public boolean equals(Object o) {
513     if (this == o) return true;
514     if (o == null || getClass() != o.getClass()) return false;
515
516     XmlElementDescriptorImpl that = (XmlElementDescriptorImpl)o;
517
518     if (myDescriptorTag != null ? !myDescriptorTag.equals(that.myDescriptorTag) : that.myDescriptorTag != null) return false;
519
520     return true;
521   }
522
523   @Override
524   public int hashCode() {
525     return myDescriptorTag != null ? myDescriptorTag.hashCode() : 0;
526   }
527 }