cda043cd5d9bc3410899d50088260af8ae7c7753
[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     final String namespacePrefix = XmlUtil.findPrefixByQualifiedName(getName(null));
107     final XmlNSDescriptorImpl xmlNSDescriptor = (XmlNSDescriptorImpl)getNSDescriptor();
108     if(xmlNSDescriptor == null || myDescriptorTag == null) return XmlUtil.EMPTY_URI;
109     return "".equals(namespacePrefix) ?
110            xmlNSDescriptor.getDefaultNamespace() :
111            myDescriptorTag.getNamespaceByPrefix(namespacePrefix);
112   }
113
114   public void init(PsiElement element){
115     if (myDescriptorTag!=element && myDescriptorTag!=null) {
116       NSDescriptor = null;
117     }
118     myDescriptorTag = (XmlTag) element;
119   }
120
121   public Object[] getDependences(){
122     return new Object[]{myDescriptorTag};
123   }
124
125   private XmlNSDescriptor getNSDescriptor(XmlElement context) {
126     XmlNSDescriptor nsDescriptor = getNSDescriptor();
127     if (context instanceof XmlTag && nsDescriptor instanceof XmlNSDescriptorImpl) {
128       final String defaultNamespace = ((XmlNSDescriptorImpl)nsDescriptor).getDefaultNamespace();
129       if (XmlUtil.XML_SCHEMA_URI.equals(defaultNamespace)) return nsDescriptor; // do not check for overriden for efficiency
130
131       final XmlTag tag = (XmlTag)context;
132       final String tagNs = tag.getNamespace();
133       if (tagNs.equals(defaultNamespace)) {
134         XmlNSDescriptor previousDescriptor = nsDescriptor;
135         nsDescriptor = tag.getNSDescriptor(tagNs, true);
136         if (nsDescriptor == null) nsDescriptor = previousDescriptor;
137       }
138     }
139     
140     return nsDescriptor;
141   }
142
143   public XmlNSDescriptor getNSDescriptor() {
144     XmlNSDescriptor nsDescriptor = NSDescriptor;
145     if (nsDescriptor == null || !NSDescriptor.getDeclaration().isValid()) {
146       final XmlFile file = XmlUtil.getContainingFile(getDeclaration());
147       if(file == null) return null;
148       final XmlDocument document = file.getDocument();
149       if(document == null) return null;
150       NSDescriptor = nsDescriptor = (XmlNSDescriptor)document.getMetaData();
151     }
152
153     return nsDescriptor;
154   }
155
156   @Override
157   public XmlElementsGroup getTopGroup() {
158     TypeDescriptor type = getType();
159     return type instanceof ComplexTypeDescriptor ? ((ComplexTypeDescriptor)type).getTopGroup() : null;
160   }
161
162   @Nullable
163   public TypeDescriptor getType() {
164     return getType(null);
165   }
166
167   @Nullable
168   public TypeDescriptor getType(XmlElement context) {
169     final XmlNSDescriptor nsDescriptor = getNSDescriptor(context);
170     if (!(nsDescriptor instanceof XmlNSTypeDescriptorProvider)) return null;
171
172     TypeDescriptor type = ((XmlNSTypeDescriptorProvider) nsDescriptor).getTypeDescriptor(myDescriptorTag);
173     if (type == null) {
174       String substAttr = myDescriptorTag.getAttributeValue("substitutionGroup");
175       if (substAttr != null) {
176         final String namespacePrefix = XmlUtil.findPrefixByQualifiedName(substAttr);
177         final String namespace = "".equals(namespacePrefix) ?
178                                  ((XmlNSDescriptorImpl)getNSDescriptor()).getDefaultNamespace() :
179                                  myDescriptorTag.getNamespaceByPrefix(namespacePrefix);
180         final String local = XmlUtil.findLocalNameByQualifiedName(substAttr);
181         final XmlElementDescriptorImpl originalElement = (XmlElementDescriptorImpl)((XmlNSDescriptorImpl)getNSDescriptor()).getElementDescriptor(local, namespace);
182         if (originalElement != null) {
183           type = originalElement.getType(context);
184         }
185       }
186     }
187     return type;
188   }
189
190   public XmlElementDescriptor[] getElementsDescriptors(XmlTag context) {
191     if (context != null) {
192       final XmlElementDescriptor parentDescriptorByType = XmlUtil.findXmlDescriptorByType(context);
193       if (parentDescriptorByType != null && !parentDescriptorByType.equals(this)) {
194         return parentDescriptorByType.getElementsDescriptors(context);
195       }
196     }
197
198     XmlElementDescriptor[] elementsDescriptors = getElementsDescriptorsImpl(context);
199
200     final TypeDescriptor type = getType(context);
201
202     if (type instanceof ComplexTypeDescriptor) {
203       final ComplexTypeDescriptor descriptor = (ComplexTypeDescriptor)type;
204       String contextNs;
205       PsiFile containingFile = context != null ? context.getContainingFile():null;
206
207       if (context != null && !containingFile.isPhysical()) {
208         containingFile = containingFile.getOriginalFile();
209         //context = context.getParentTag();
210       }
211
212       if (context != null &&
213           ( descriptor.canContainTag(context.getLocalName(), contextNs = context.getNamespace(), context ) &&
214             (!contextNs.equals(getNamespace()) || descriptor.hasAnyInContentModel())
215           ) ) {
216         final XmlNSDescriptor nsDescriptor = getNSDescriptor();
217
218         if (nsDescriptor != null) {
219           elementsDescriptors = ArrayUtil.mergeArrays(
220             elementsDescriptors,
221             nsDescriptor.getRootElementsDescriptors(((XmlFile)containingFile).getDocument())
222           );
223         }
224       }
225     }
226
227     return elementsDescriptors;
228   }
229
230   private XmlElementDescriptor[] getElementsDescriptorsImpl(XmlElement context) {
231     TypeDescriptor type = getType(context);
232
233     if (type instanceof ComplexTypeDescriptor) {
234       ComplexTypeDescriptor typeDescriptor = (ComplexTypeDescriptor)type;
235
236       return typeDescriptor.getElements(context);
237     }
238
239     return EMPTY_ARRAY;
240   }
241
242   public XmlAttributeDescriptor[] getAttributesDescriptors(final XmlTag context) {
243     TypeDescriptor type = getType(context);
244
245     if (type instanceof ComplexTypeDescriptor) {
246       ComplexTypeDescriptor typeDescriptor = (ComplexTypeDescriptor)type;
247       XmlAttributeDescriptor[] attributeDescriptors = typeDescriptor.getAttributes(context);
248
249       if (context != null) {
250         final String contextNs = context.getNamespace();
251
252         boolean seenXmlNs = false;
253         for(String ns:context.knownNamespaces()) {
254           if (!contextNs.equals(ns) && ns.length() > 0) {
255             seenXmlNs |= XmlUtil.XML_NAMESPACE_URI.equals(ns);
256             attributeDescriptors = updateAttributeDescriptorsFromAny(context, typeDescriptor, attributeDescriptors, ns);
257           }
258         }
259
260         if (!seenXmlNs) {
261           attributeDescriptors = updateAttributeDescriptorsFromAny(context, typeDescriptor, attributeDescriptors, XmlUtil.XML_NAMESPACE_URI);
262         }
263       }
264       return attributeDescriptors;
265     }
266
267     return XmlAttributeDescriptor.EMPTY;
268   }
269
270   private static XmlAttributeDescriptor[] updateAttributeDescriptorsFromAny(final XmlTag context,
271                                                                             final ComplexTypeDescriptor typeDescriptor,
272                                                                             XmlAttributeDescriptor[] attributeDescriptors,
273                                                                             final String ns) {
274     if (typeDescriptor.canContainAttribute(ns, null) != ComplexTypeDescriptor.CanContainAttributeType.CanNotContain) {
275       final XmlNSDescriptor descriptor = context.getNSDescriptor(ns, true);
276
277       if (descriptor instanceof XmlNSDescriptorImpl) {
278         attributeDescriptors = ArrayUtil.mergeArrays(
279           attributeDescriptors,
280           ((XmlNSDescriptorImpl)descriptor).getRootAttributeDescriptors(context)
281         );
282       }
283     }
284     return attributeDescriptors;
285   }
286
287   public XmlAttributeDescriptor getAttributeDescriptor(String attributeName, final XmlTag context){
288     return getAttributeDescriptorImpl(attributeName,context);
289   }
290
291   @Nullable
292   private XmlAttributeDescriptor getAttributeDescriptorImpl(final String attributeName, XmlTag context) {
293     final String localName = XmlUtil.findLocalNameByQualifiedName(attributeName);
294     final String namespacePrefix = XmlUtil.findPrefixByQualifiedName(attributeName);
295     final XmlNSDescriptorImpl xmlNSDescriptor = (XmlNSDescriptorImpl)getNSDescriptor();
296     final String namespace = "".equals(namespacePrefix) ?
297                              ((xmlNSDescriptor != null)?xmlNSDescriptor.getDefaultNamespace():"") :
298                              context.getNamespaceByPrefix(namespacePrefix);
299
300     XmlAttributeDescriptor attribute = getAttribute(localName, namespace, context, attributeName);
301     
302     if (attribute instanceof AnyXmlAttributeDescriptor && namespace.length() > 0) {
303       final XmlNSDescriptor candidateNSDescriptor = context.getNSDescriptor(namespace, true);
304
305       if (candidateNSDescriptor instanceof XmlNSDescriptorImpl) {
306         final XmlNSDescriptorImpl nsDescriptor = (XmlNSDescriptorImpl)candidateNSDescriptor;
307
308         final XmlAttributeDescriptor xmlAttributeDescriptor = nsDescriptor.getAttribute(localName, namespace, context);
309         if (xmlAttributeDescriptor != null) return xmlAttributeDescriptor;
310         else {
311           final ComplexTypeDescriptor.CanContainAttributeType containAttributeType =
312             ((AnyXmlAttributeDescriptor)attribute).getCanContainAttributeType();
313           if (containAttributeType == ComplexTypeDescriptor.CanContainAttributeType.CanContainButDoNotSkip) {
314             attribute = null;
315           }
316         }
317       }
318     }
319     return attribute;
320   }
321
322   public XmlAttributeDescriptor getAttributeDescriptor(XmlAttribute attribute){
323     return getAttributeDescriptorImpl(attribute.getName(),attribute.getParent());
324   }
325
326   @Nullable
327   private XmlAttributeDescriptor getAttribute(String attributeName, String namespace, XmlTag context, String qName) {
328     XmlAttributeDescriptor[] descriptors = getAttributesDescriptors(context);
329
330     for (XmlAttributeDescriptor descriptor : descriptors) {
331       if (descriptor.getName().equals(attributeName) &&
332           descriptor.getName(context).equals(qName)
333          ) {
334         return descriptor;
335       }
336     }
337
338     TypeDescriptor type = getType(context);
339     if (type instanceof ComplexTypeDescriptor) {
340       ComplexTypeDescriptor descriptor = (ComplexTypeDescriptor)type;
341       final ComplexTypeDescriptor.CanContainAttributeType containAttributeType = descriptor.canContainAttribute(namespace, qName);
342
343       if (containAttributeType != ComplexTypeDescriptor.CanContainAttributeType.CanNotContain) {
344         return new AnyXmlAttributeDescriptor(attributeName, containAttributeType);
345       }
346     }
347
348     return null;
349   }
350
351   public int getContentType() {
352     TypeDescriptor type = getType();
353
354     if (type instanceof ComplexTypeDescriptor) {
355       return ((ComplexTypeDescriptor)type).getContentType();
356     }
357
358     return CONTENT_TYPE_MIXED;
359   }
360
361   @Override
362   public String getDefaultValue() {
363     return myDescriptorTag.getAttributeValue("default");
364   }
365
366   @Nullable
367   public XmlElementDescriptor getElementDescriptor(final String name) {
368       final String localName = XmlUtil.findLocalNameByQualifiedName(name);
369       final String namespacePrefix = XmlUtil.findPrefixByQualifiedName(name);
370       final String namespace = "".equals(namespacePrefix) ?
371                                ((XmlNSDescriptorImpl)getNSDescriptor()).getDefaultNamespace() :
372                                myDescriptorTag.getNamespaceByPrefix(namespacePrefix);
373     return getElementDescriptor(localName, namespace, null, name);
374   }
375
376   @Nullable
377   protected XmlElementDescriptor getElementDescriptor(final String localName, final String namespace, XmlElement context, String fullName) {
378     XmlElementDescriptor[] elements = getElementsDescriptorsImpl(context);
379
380     for (XmlElementDescriptor element1 : elements) {
381       final XmlElementDescriptorImpl element = (XmlElementDescriptorImpl)element1;
382       final String namespaceByContext = element.getNamespaceByContext(context);
383
384       if (element.getName().equals(localName)) {
385         if (namespace == null ||
386             namespace.equals(namespaceByContext) ||
387             namespaceByContext.equals(XmlUtil.EMPTY_URI) ||
388             element.getName(context).equals(fullName) || (namespace.length() == 0) &&
389                                                          element.getDefaultName().equals(fullName)
390            ) {
391           return element;
392         }
393         else {
394           final XmlNSDescriptor descriptor = context instanceof XmlTag? ((XmlTag)context).getNSDescriptor(namespace, true) : null;
395
396           // schema's targetNamespace could be different from file systemId used as NS
397           if (descriptor instanceof XmlNSDescriptorImpl &&
398               ((XmlNSDescriptorImpl)descriptor).getDefaultNamespace().equals(namespaceByContext)
399              ) {
400             return element;
401           }
402         }
403       }
404     }
405
406     TypeDescriptor type = getType(context);
407     if (type instanceof ComplexTypeDescriptor) {
408       ComplexTypeDescriptor descriptor = (ComplexTypeDescriptor)type;
409       if (descriptor.canContainTag(localName, namespace, context)) {
410         return new AnyXmlElementDescriptor(this, getNSDescriptor());
411       }
412     }
413
414     return null;
415   }
416
417   public XmlElementDescriptor getElementDescriptor(XmlTag element, XmlTag contextTag){
418     final XmlElement context = (XmlElement)element.getParent();
419
420     XmlElementDescriptor elementDescriptor = getElementDescriptor(
421       element.getLocalName(),
422       element.getNamespace(), context,
423       element.getName()
424     );
425
426     if(elementDescriptor == null || element.getAttributeValue("xsi:type") != null){
427       final XmlElementDescriptor xmlDescriptorByType = XmlUtil.findXmlDescriptorByType(element);
428
429       if (xmlDescriptorByType != null) elementDescriptor = xmlDescriptorByType;
430       else if (context instanceof XmlTag && ((XmlTag)context).getAttributeValue("xsi:type") != null && askParentDescriptorViaXsi()) {
431         final XmlElementDescriptor parentXmlDescriptorByType = XmlUtil.findXmlDescriptorByType(((XmlTag)context));
432         if (parentXmlDescriptorByType != null) {
433           elementDescriptor = parentXmlDescriptorByType.getElementDescriptor(element, contextTag);
434         }
435       }
436     }
437     return elementDescriptor;
438   }
439
440   protected boolean askParentDescriptorViaXsi() {
441     return true;
442   }
443
444   public String getQualifiedName() {
445     if (!"".equals(getNS())) {
446       return getNS() + ":" + getName();
447     }
448
449     return getName();
450   }
451
452   @Nullable
453   private String getNS(){
454     return XmlUtil.findNamespacePrefixByURI((XmlFile) myDescriptorTag.getContainingFile(), getNamespace());
455   }
456
457   public String getDefaultName() {
458     final PsiFile psiFile = myDescriptorTag.getContainingFile();
459     XmlTag rootTag = psiFile instanceof XmlFile ?((XmlFile)psiFile).getRootTag():null;
460
461     if (rootTag != null && QUALIFIED_ATTR_VALUE.equals(rootTag.getAttributeValue(ELEMENT_FORM_DEFAULT))) {
462       return getQualifiedName();
463     }
464
465     return getName();
466   }
467
468   public boolean isAbstract() {
469     return isAbstractDeclaration(myDescriptorTag);
470   }
471
472   public static Boolean isAbstractDeclaration(final XmlTag descriptorTag) {
473     return Boolean.valueOf(descriptorTag.getAttributeValue("abstract"));
474   }
475
476   public void setName(String name) throws IncorrectOperationException {
477     NamedObjectDescriptor.setName(myDescriptorTag, name);
478   }
479
480   public void setValidator(final Validator<XmlTag> validator) {
481     myValidator = validator;
482   }
483
484   public void validate(@NotNull XmlTag context, @NotNull ValidationHost host) {
485     Validator<XmlTag> validator = myValidator;
486     if (validator != null) {
487       validator.validate(context, host);
488     }
489   }
490
491   public boolean allowElementsFromNamespace(final String namespace, final XmlTag context) {
492     final TypeDescriptor type = getType(context);
493     
494     if (type instanceof ComplexTypeDescriptor) {
495       final ComplexTypeDescriptor typeDescriptor = (ComplexTypeDescriptor)type;
496       return typeDescriptor.canContainTag("a", namespace, context) ||
497              typeDescriptor.getNsDescriptors().hasSubstitutions() ||
498              XmlUtil.nsFromTemplateFramework(namespace)
499         ;
500     }
501     return false;
502   }
503
504   @Override
505   public String toString() {
506     return getName() + " (" + getNamespace() + ")";
507   }
508
509   @Override
510   public boolean equals(Object o) {
511     if (this == o) return true;
512     if (o == null || getClass() != o.getClass()) return false;
513
514     XmlElementDescriptorImpl that = (XmlElementDescriptorImpl)o;
515
516     if (myDescriptorTag != null ? !myDescriptorTag.equals(that.myDescriptorTag) : that.myDescriptorTag != null) return false;
517
518     return true;
519   }
520
521   @Override
522   public int hashCode() {
523     return myDescriptorTag != null ? myDescriptorTag.hashCode() : 0;
524   }
525 }