7cfd7e5dc966f0a4c4268e0eee0c4469e3df0152
[idea/community.git] / plugins / ant / src / com / intellij / lang / ant / dom / AntDomExtender.java
1 /*
2  * Copyright 2000-2016 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.lang.ant.dom;
17
18 import com.intellij.lang.ant.AntIntrospector;
19 import com.intellij.lang.ant.ReflectedProject;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.util.Key;
22 import com.intellij.openapi.util.Pair;
23 import com.intellij.pom.PomTarget;
24 import com.intellij.psi.CommonClassNames;
25 import com.intellij.psi.PsiFileSystemItem;
26 import com.intellij.psi.xml.XmlAttribute;
27 import com.intellij.psi.xml.XmlElement;
28 import com.intellij.psi.xml.XmlTag;
29 import com.intellij.util.xml.*;
30 import com.intellij.util.xml.reflect.*;
31 import org.apache.tools.ant.Task;
32 import org.apache.tools.ant.types.Reference;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
35
36 import java.io.File;
37 import java.lang.reflect.ParameterizedType;
38 import java.lang.reflect.Type;
39 import java.util.*;
40
41 /**
42  * @author Eugene Zhuravlev
43  */
44 public class AntDomExtender extends DomExtender<AntDomElement>{
45   private static final Logger LOG = Logger.getInstance("#com.intellij.lang.ant.dom.AntDomExtender");
46
47   private static final Key<Class> ELEMENT_IMPL_CLASS_KEY = Key.create("_element_impl_class_");
48   private static final Key<Boolean> IS_TASK_CONTAINER = Key.create("_task_container_");
49   private static final Map<String, Class<? extends AntDomElement>> TAG_MAPPING = new HashMap<>();
50   static {
51     TAG_MAPPING.put("property", AntDomProperty.class);
52     TAG_MAPPING.put("dirname", AntDomDirname.class);
53     TAG_MAPPING.put("fileset", AntDomFileSet.class);
54     TAG_MAPPING.put("dirset", AntDomDirSet.class);
55     TAG_MAPPING.put("filelist", AntDomFileList.class);
56     TAG_MAPPING.put("pathelement", AntDomPathElement.class);
57     TAG_MAPPING.put("path", AntDomPath.class);
58     TAG_MAPPING.put("classpath", AntDomClasspath.class);
59     TAG_MAPPING.put("typedef", AntDomTypeDef.class);
60     TAG_MAPPING.put("taskdef", AntDomTaskdef.class);
61     TAG_MAPPING.put("presetdef", AntDomPresetDef.class);
62     TAG_MAPPING.put("macrodef", AntDomMacroDef.class);
63     TAG_MAPPING.put("scriptdef", AntDomScriptDef.class);
64     TAG_MAPPING.put("antlib", AntDomAntlib.class);
65     TAG_MAPPING.put("ant", AntDomAnt.class);
66     TAG_MAPPING.put("antcall", AntDomAntCall.class);
67     TAG_MAPPING.put("available", AntDomPropertyDefiningTaskWithDefaultValue.class);
68     TAG_MAPPING.put("condition", AntDomPropertyDefiningTaskWithDefaultValue.class);
69     TAG_MAPPING.put("uptodate", AntDomPropertyDefiningTaskWithDefaultValue.class);
70     TAG_MAPPING.put("checksum", AntDomChecksumTask.class);
71     TAG_MAPPING.put("loadfile", AntDomLoadFileTask.class);
72     TAG_MAPPING.put("whichresource", AntDomWhichResourceTask.class);
73     TAG_MAPPING.put("jarlib-resolve", AntDomPropertyDefiningTask.class);
74     TAG_MAPPING.put("p4counter", AntDomPropertyDefiningTask.class);
75     TAG_MAPPING.put("pathconvert", AntDomPropertyDefiningTask.class);
76     TAG_MAPPING.put("basename", AntDomBasenameTask.class);
77     TAG_MAPPING.put("length", AntDomLengthTask.class);
78     TAG_MAPPING.put("tempfile", AntDomTempFile.class);
79     TAG_MAPPING.put("exec", AntDomExecTask.class);
80     TAG_MAPPING.put("buildnumber", AntDomBuildnumberTask.class);
81     TAG_MAPPING.put("tstamp", AntDomTimestampTask.class);
82     TAG_MAPPING.put("format", AntDomTimestampTaskFormat.class);
83     TAG_MAPPING.put("input", AntDomInputTask.class);
84   }
85
86   @Override
87   public void registerExtensions(@NotNull final AntDomElement antDomElement, @NotNull DomExtensionsRegistrar registrar) {
88     final XmlElement xmlElement = antDomElement.getXmlElement();
89     if (xmlElement instanceof XmlTag) {
90       final XmlTag xmlTag = (XmlTag)xmlElement;
91       final String tagName = xmlTag.getName();
92
93       final AntDomProject antProject = antDomElement.getAntProject();
94       if (antProject == null) {
95         return;
96       }
97       final ReflectedProject reflected = ReflectedProject.getProject(antProject.getClassLoader());
98       if (reflected.getProject() == null) {
99         return;
100       }
101
102       final DomGenericInfo genericInfo = antDomElement.getGenericInfo();
103       AntIntrospector classBasedIntrospector = null;
104       final Hashtable<String,Class> coreTaskDefs = reflected.getTaskDefinitions();
105       final Hashtable<String, Class> coreTypeDefs = reflected.getDataTypeDefinitions();
106       final boolean isCustom = antDomElement instanceof AntDomCustomElement;
107       if ("project".equals(tagName)) {
108         classBasedIntrospector = getIntrospector(reflected.getProject().getClass());
109       }
110       else if ("target".equals(tagName)) {
111         classBasedIntrospector = getIntrospector(reflected.getTargetClass());
112       }
113       else {
114         if (isCustom) {
115           final AntDomCustomElement custom = (AntDomCustomElement)antDomElement;
116           final Class definitionClass = custom.getDefinitionClass();
117           if (definitionClass != null) {
118             classBasedIntrospector = getIntrospector(definitionClass);
119           }
120         }
121         else {
122           Class elemType = antDomElement.getChildDescription().getUserData(ELEMENT_IMPL_CLASS_KEY);
123
124           if (elemType == null) {
125             if (coreTaskDefs != null){
126               elemType = coreTaskDefs.get(tagName);
127             }
128           }
129
130           if (elemType == null) {
131             if (coreTypeDefs != null){
132               elemType = coreTypeDefs.get(tagName);
133             }
134           }
135
136           if (elemType != null) {
137             classBasedIntrospector = getIntrospector(elemType);
138           }
139         }
140       }
141
142       AbstractIntrospector parentIntrospector = null;
143       if (classBasedIntrospector != null) {
144         parentIntrospector = new ClassIntrospectorAdapter(classBasedIntrospector, coreTaskDefs, coreTypeDefs);
145       }
146       else {
147         if (isCustom) {
148           final AntDomNamedElement declaringElement = ((AntDomCustomElement)antDomElement).getDeclaringElement();
149           if (declaringElement instanceof AntDomMacroDef) {
150             parentIntrospector = new MacrodefIntrospectorAdapter((AntDomMacroDef)declaringElement);
151           }
152           else if (declaringElement instanceof AntDomMacrodefElement){
153             parentIntrospector = new MacrodefElementOccurrenceIntrospectorAdapter((AntDomMacrodefElement)declaringElement)/*ContainerElementIntrospector.INSTANCE*/;
154           }
155           else if (declaringElement instanceof AntDomScriptDef) {
156             parentIntrospector = new ScriptdefIntrospectorAdapter((AntDomScriptDef)declaringElement);
157           }
158         }
159       }
160
161       if (parentIntrospector != null) {
162
163         defineAttributes(xmlTag, registrar, genericInfo, parentIntrospector);
164
165         if ("project".equals(tagName) || parentIntrospector.isContainer()) { // can contain any task or/and type definition
166           if (coreTaskDefs != null) {
167             for (Map.Entry<String, Class> entry : coreTaskDefs.entrySet()) {
168               final DomExtension extension = registerChild(registrar, genericInfo, entry.getKey());
169               if (extension != null) {
170                 final Class type = entry.getValue();
171                 if (type != null) {
172                   extension.putUserData(ELEMENT_IMPL_CLASS_KEY, type);
173                 }
174                 extension.putUserData(AntDomElement.ROLE, AntDomElement.Role.TASK);
175               }
176             }
177           }
178           if (coreTypeDefs != null) {
179             for (Map.Entry<String, Class> entry : coreTypeDefs.entrySet()) {
180               final DomExtension extension = registerChild(registrar, genericInfo, entry.getKey());
181               if (extension != null) {
182                 final Class type = entry.getValue();
183                 if (type != null) {
184                   extension.putUserData(ELEMENT_IMPL_CLASS_KEY, type);
185                 }
186                 extension.putUserData(AntDomElement.ROLE, AntDomElement.Role.DATA_TYPE);
187               }
188             }
189           }
190           registrar.registerCustomChildrenExtension(AntDomCustomElement.class, new AntCustomTagNameDescriptor());
191         }
192         else {
193           final Iterator<String> nested = parentIntrospector.getNestedElementsIterator();
194           while (nested.hasNext()) {
195             final String nestedElementName = nested.next();
196             final DomExtension extension = registerChild(registrar, genericInfo, nestedElementName);
197             if (extension != null) {
198               Class type = parentIntrospector.getNestedElementType(nestedElementName);
199               if (type != null && CommonClassNames.JAVA_LANG_OBJECT.equals(type.getName())) {
200                 type = null; // hack to support badly written tasks
201               }
202               if (type == null) {
203                 if (coreTypeDefs != null){
204                   type = coreTypeDefs.get(nestedElementName);
205                 }
206               }
207               if (type != null) {
208                 extension.putUserData(ELEMENT_IMPL_CLASS_KEY, type);
209               }
210               AntDomElement.Role role = AntDomElement.Role.DATA_TYPE;
211               if (coreTaskDefs != null && coreTaskDefs.containsKey(nestedElementName)) {
212                 role = AntDomElement.Role.TASK;
213               }
214               else if (type != null && isAssignableFrom(Task.class.getName(), type)) {
215                 role = AntDomElement.Role.TASK;
216               }
217               if (role != null) {
218                 extension.putUserData(AntDomElement.ROLE, role);
219               }
220             }
221           }
222           registrar.registerCustomChildrenExtension(AntDomCustomElement.class, new AntCustomTagNameDescriptor());
223         }
224       }
225     }
226   }
227
228   private static void defineAttributes(XmlTag xmlTag, DomExtensionsRegistrar registrar, DomGenericInfo genericInfo, AbstractIntrospector parentIntrospector) {
229     final Map<String, Pair<Type, Class>> registeredAttribs = getStaticallyRegisteredAttributes(genericInfo);
230     // define attributes discovered by introspector and not yet defined statically
231     final Iterator<String> introspectedAttributes = parentIntrospector.getAttributesIterator();
232     while (introspectedAttributes.hasNext()) {
233       final String attribName = introspectedAttributes.next();
234       if (genericInfo.getAttributeChildDescription(attribName) == null) { // if not defined yet
235         final String _attribName = attribName.toLowerCase(Locale.US);
236         final Pair<Type, Class> types = registeredAttribs.get(_attribName);
237         Type type = Pair.getFirst(types);
238         Class converterClass = Pair.getSecond(types);
239         if (type == null) {
240           type = String.class; // use String by default
241           final Class attributeType = parentIntrospector.getAttributeType(attribName);
242           if (attributeType != null) {
243             // handle well-known types
244             if (File.class.isAssignableFrom(attributeType)) {
245               type = PsiFileSystemItem.class;
246               converterClass = AntPathConverter.class;
247             }
248             else if (Boolean.class.isAssignableFrom(attributeType)){
249               type = Boolean.class;
250               converterClass = AntBooleanConverter.class;
251             }
252             else if (isAssignableFrom(Reference.class.getName(), attributeType)) {
253               converterClass = AntDomRefIdConverter.class;
254             }
255           }
256         }
257
258         LOG.assertTrue(type != null);
259
260         registerAttribute(registrar, attribName, type, converterClass);
261         if (types == null) { // augment the map if this was a newly added attribute
262           registeredAttribs.put(_attribName, Pair.create(type, converterClass));
263         }
264       }
265     }
266     // handle attribute case problems:
267     // additionaly register all attributes that exist in XML but differ from the registered ones only in case
268     for (XmlAttribute xmlAttribute : xmlTag.getAttributes()) {
269       final String existingAttribName = xmlAttribute.getName();
270       if (genericInfo.getAttributeChildDescription(existingAttribName) == null) {
271         final Pair<Type, Class> pair = registeredAttribs.get(existingAttribName.toLowerCase(Locale.US));
272         if (pair != null) { // if such attribute should actually be here
273           registerAttribute(registrar, existingAttribName, pair.getFirst(), pair.getSecond());
274         }
275       }
276     }
277   }
278
279   private static void registerAttribute(DomExtensionsRegistrar registrar, String attribName, final @NotNull Type attributeType, final @Nullable Class converterType) {
280     final DomExtension extension = registrar.registerGenericAttributeValueChildExtension(new XmlName(attribName), attributeType);
281     if (converterType != null) {
282       try {
283         extension.setConverter((Converter)converterType.newInstance());
284       }
285       catch (InstantiationException | IllegalAccessException e) {
286         LOG.info(e);
287       }
288     }
289   }
290
291   private static Map<String, Pair<Type, Class>> getStaticallyRegisteredAttributes(final DomGenericInfo genericInfo) {
292     final Map<String, Pair<Type, Class>> map = new HashMap<>();
293     for (DomAttributeChildDescription description : genericInfo.getAttributeChildrenDescriptions()) {
294       final Type type = description.getType();
295       if (type instanceof ParameterizedType) {
296         final Type[] typeArguments = ((ParameterizedType)type).getActualTypeArguments();
297         if (typeArguments.length == 1) {
298           String name = description.getXmlElementName();
299           final Type attribType = typeArguments[0];
300           Class<? extends Converter> converterType = null;
301           final Convert converterAnnotation = description.getAnnotation(Convert.class);
302           if (converterAnnotation != null) {
303             converterType = converterAnnotation.value();
304           }
305           map.put(name.toLowerCase(Locale.US), new Pair<>(attribType, converterType));
306         }
307       }
308     }
309     return map;
310   }
311
312   @Nullable
313   private static DomExtension registerChild(DomExtensionsRegistrar registrar, DomGenericInfo elementInfo, String childName) {
314     if (elementInfo.getCollectionChildDescription(childName) == null) { // register if not yet defined statically
315       Class<? extends AntDomElement> modelClass = getModelClass(childName);
316       if (modelClass == null) {
317         modelClass = AntDomElement.class;
318       }
319       return registrar.registerCollectionChildrenExtension(new XmlName(childName), modelClass);
320     }
321     return null;
322   }
323
324   @Nullable
325   public static AntIntrospector getIntrospector(Class c) {
326     try {
327       return AntIntrospector.getInstance(c);
328     }
329     catch (Throwable ignored) {
330     }
331     return null;
332   }
333
334   @Nullable
335   private static Class<? extends AntDomElement> getModelClass(@NotNull String tagName) {
336     return TAG_MAPPING.get(tagName.toLowerCase(Locale.US));
337   }
338
339   private static boolean isAssignableFrom(final String baseClassName, final Class clazz) {
340     try {
341       final ClassLoader loader = clazz.getClassLoader();
342       if (loader != null) {
343         final Class baseClass = loader.loadClass(baseClassName);
344         return baseClass.isAssignableFrom(clazz);
345       }
346     }
347     catch (ClassNotFoundException ignored) {
348     }
349     return false;
350   }
351
352   private static class AntCustomTagNameDescriptor extends CustomDomChildrenDescription.TagNameDescriptor {
353
354     @Override
355     public Set<EvaluatedXmlName> getCompletionVariants(@NotNull DomElement parent) {
356       if (!(parent instanceof AntDomElement)) {
357         return Collections.emptySet();
358       }
359       final AntDomElement element = (AntDomElement)parent;
360       final AntDomProject antDomProject = element.getAntProject();
361       if (antDomProject == null) {
362         return Collections.emptySet();
363       }
364       final CustomAntElementsRegistry registry = CustomAntElementsRegistry.getInstance(antDomProject);
365       final Set<EvaluatedXmlName> result = new HashSet<>();
366       for (XmlName variant : registry.getCompletionVariants(element)) {
367         final String ns = variant.getNamespaceKey();
368         result.add(new DummyEvaluatedXmlName(variant, ns != null? ns : ""));
369       }
370       return result;
371     }
372
373     @Override
374     @Nullable
375     public PomTarget findDeclaration(DomElement parent, @NotNull EvaluatedXmlName name) {
376       final XmlName xmlName = name.getXmlName();
377       return doFindDeclaration(parent, xmlName);
378     }
379
380     @Override
381     @Nullable
382     public PomTarget findDeclaration(@NotNull DomElement child) {
383       XmlName name = new XmlName(child.getXmlElementName(), child.getXmlElementNamespace());
384       return doFindDeclaration(child.getParent(), name);
385     }
386
387     @Nullable
388     private static PomTarget doFindDeclaration(DomElement parent, XmlName xmlName) {
389       if (!(parent instanceof AntDomElement)) {
390         return null;
391       }
392       final AntDomElement parentElement = (AntDomElement)parent;
393       final AntDomProject antDomProject = parentElement.getAntProject();
394       if (antDomProject == null) {
395         return null;
396       }
397       final CustomAntElementsRegistry registry = CustomAntElementsRegistry.getInstance(antDomProject);
398       final AntDomElement declaringElement = registry.findDeclaringElement(parentElement, xmlName);
399       if (declaringElement == null) {
400         return null;
401       }
402       DomTarget target = DomTarget.getTarget(declaringElement);
403       if (target == null && declaringElement instanceof AntDomTypeDef) {
404         final AntDomTypeDef typedef = (AntDomTypeDef)declaringElement;
405         final GenericAttributeValue<PsiFileSystemItem> resource = typedef.getResource();
406         if (resource != null) {
407           target = DomTarget.getTarget(declaringElement, resource);
408         }
409         if (target == null) {
410           final GenericAttributeValue<PsiFileSystemItem> file = typedef.getFile();
411           if (file != null) {
412             target = DomTarget.getTarget(declaringElement, file);
413           }
414         }
415       }
416       return target;
417     }
418   }
419
420   private static abstract class AbstractIntrospector {
421     @NotNull
422     public Iterator<String> getAttributesIterator() {
423       return Collections.<String>emptyList().iterator();
424     }
425
426     @NotNull
427     public Iterator<String> getNestedElementsIterator(){
428       return Collections.<String>emptyList().iterator();
429     }
430
431     public abstract boolean isContainer();
432
433     @Nullable
434     public Class getAttributeType(String attribName) {
435       return null;
436     }
437
438     @Nullable
439     public Class getNestedElementType(String elementName) {
440       return null;
441     }
442   }
443
444   private static class ClassIntrospectorAdapter extends AbstractIntrospector {
445
446     private final AntIntrospector myIntrospector;
447     private final Map<String, Class> myCoreTaskDefs;
448     private final Map<String, Class> myCoreTypeDefs;
449     private List<String> myNestedElements;
450     private Map<String, Class> myNestedElementTypes;
451
452     private ClassIntrospectorAdapter(AntIntrospector introspector) {
453       this(introspector, null, null);
454     }
455
456     ClassIntrospectorAdapter(AntIntrospector introspector, Map<String, Class> coreTaskDefs, Map<String, Class> coreTypeDefs) {
457       myIntrospector = introspector;
458       myCoreTaskDefs = coreTaskDefs != null? coreTaskDefs : Collections.emptyMap();
459       myCoreTypeDefs = coreTypeDefs != null? coreTypeDefs : Collections.emptyMap();
460     }
461
462     @Override
463     @NotNull
464     public Iterator<String> getAttributesIterator() {
465       return new EnumerationToIteratorAdapter<>(myIntrospector.getAttributes());
466     }
467
468     @Override
469     public Class getAttributeType(String attribName) {
470       return myIntrospector.getAttributeType(attribName);
471     }
472
473     @Override
474     public boolean isContainer() {
475       return myIntrospector.isContainer();
476     }
477
478     @Override
479     @NotNull
480     public Iterator<String> getNestedElementsIterator() {
481       initNestedElements();
482       return myNestedElements.iterator();
483     }
484
485     @Override
486     public Class getNestedElementType(String attribName) {
487       initNestedElements();
488       return myNestedElementTypes.get(attribName);
489     }
490
491     private void initNestedElements() {
492       if (myNestedElements != null) {
493         return;
494       }
495       myNestedElements = new ArrayList<>();
496       myNestedElementTypes = new HashMap<>();
497       final Enumeration<String> nestedElements = myIntrospector.getNestedElements();
498       while (nestedElements.hasMoreElements()) {
499         final String elemName = nestedElements.nextElement();
500         myNestedElements.add(elemName);
501         myNestedElementTypes.put(elemName, myIntrospector.getElementType(elemName));
502       }
503
504       final Set<String> extensionPointTypes = myIntrospector.getExtensionPointTypes();
505       for (String extPoint : extensionPointTypes) {
506         processEntries(extPoint, myCoreTaskDefs);
507         processEntries(extPoint, myCoreTypeDefs);
508       }
509     }
510
511     private void processEntries(String extPoint, final Map<String, Class> definitions) {
512       for (Map.Entry<String, Class> entry : definitions.entrySet()) {
513         final String elementName = entry.getKey();
514         final Class taskClass = entry.getValue();
515         if (isAssignableFrom(extPoint, taskClass)) {
516           myNestedElements.add(elementName);
517           myNestedElementTypes.put(elementName, taskClass);
518         }
519       }
520     }
521   }
522
523   private static class MacrodefIntrospectorAdapter extends AbstractIntrospector {
524     private final AntDomMacroDef myMacrodef;
525
526     private MacrodefIntrospectorAdapter(AntDomMacroDef macrodef) {
527       myMacrodef = macrodef;
528     }
529
530     @Override
531     @NotNull
532     public Iterator<String> getAttributesIterator() {
533       final List<AntDomMacrodefAttribute> macrodefAttributes = myMacrodef.getMacroAttributes();
534       if (macrodefAttributes.size() == 0) {
535         return Collections.<String>emptyList().iterator();
536       }
537       final List<String> attribs = new ArrayList<>(macrodefAttributes.size());
538       for (AntDomMacrodefAttribute attribute : macrodefAttributes) {
539         final String attribName = attribute.getName().getRawText();
540         if (attribName != null) {
541           attribs.add(attribName);
542         }
543       }
544       return attribs.iterator();
545     }
546
547     @Override
548     public boolean isContainer() {
549       return myMacrodef.getMacroElements().stream()
550         .map(AntDomMacrodefElement::isImplicit)
551         .anyMatch(implicit -> implicit != null && Boolean.TRUE.equals(implicit.getValue()));
552     }
553   }
554
555   private static class MacrodefElementOccurrenceIntrospectorAdapter extends AbstractIntrospector {
556     private final AntDomMacrodefElement myElement;
557     private volatile List<AbstractIntrospector> myContexts;
558     private volatile Map<String, Class> myChildrenMap;
559
560     private MacrodefElementOccurrenceIntrospectorAdapter(AntDomMacrodefElement element) {
561       myElement = element;
562     }
563
564     @Override
565     public boolean isContainer() {
566       return getContexts().stream().allMatch(AbstractIntrospector::isContainer);
567     }
568
569     @Override
570     @NotNull
571     public Iterator<String> getNestedElementsIterator() {
572       return getNestedElementsMap().keySet().iterator();
573     }
574
575     @Override
576     public Class getNestedElementType(String elementName) {
577       return getNestedElementsMap().get(elementName);
578     }
579
580     private Map<String, Class> getNestedElementsMap() {
581       if (myChildrenMap != null) {
582         return myChildrenMap;
583       }
584       final List<AbstractIntrospector> contexts = getContexts();
585       Map<String, Class> names = null;
586       for (AbstractIntrospector context : contexts) {
587         if (context.isContainer()) {
588           continue;
589         }
590         final Set<String> set = new HashSet<>();
591         for (Iterator<String> it = context.getNestedElementsIterator();it.hasNext();) {
592           final String name = it.next();
593           set.add(name);
594         }
595         if (names == null) {
596           names = new HashMap<>();
597           for (String s : set) {
598             names.put(s, context.getNestedElementType(s));
599           }
600         }
601         else {
602           names.keySet().retainAll(set);
603         }
604       }
605       final Map<String, Class> result = names == null ? Collections.emptyMap() : names;
606       return myChildrenMap = result;
607     }
608
609     private List<AbstractIntrospector> getContexts() {
610       if (myContexts != null) {
611         return myContexts;
612       }
613       final List<AbstractIntrospector> parents = new ArrayList<>();
614       final AntDomMacroDef macroDef = myElement.getParentOfType(AntDomMacroDef.class, true);
615       if (macroDef != null) {
616         final AntDomSequentialTask body = macroDef.getMacroBody();
617         if (body != null) {
618           body.accept(new AntDomRecursiveVisitor() {
619             @Override
620             public void visitAntDomCustomElement(AntDomCustomElement custom) {
621               if (myElement.equals(custom.getDeclaringElement())) {
622                 final AntDomElement parent = custom.getParentOfType(AntDomElement.class, true);
623                 if (parent != null) {
624                   final Class type = parent.getChildDescription().getUserData(ELEMENT_IMPL_CLASS_KEY);
625                   if (type != null) {
626                     final AntIntrospector antIntrospector = AntIntrospector.getInstance(type);
627                     if (antIntrospector != null) {
628                       parents.add(new ClassIntrospectorAdapter(antIntrospector));
629                     }
630                   }
631                 }
632               }
633             }
634           });
635         }
636       }
637       return myContexts = parents;
638     }
639   }
640
641   private static class ScriptdefIntrospectorAdapter extends AbstractIntrospector {
642     private final AntDomScriptDef myScriptDef;
643
644     private ScriptdefIntrospectorAdapter(AntDomScriptDef scriptDef) {
645       myScriptDef = scriptDef;
646     }
647
648     @Override
649     @NotNull
650     public Iterator<String> getAttributesIterator() {
651       final List<AntDomScriptdefAttribute> macrodefAttributes = myScriptDef.getScriptdefAttributes();
652       final List<String> attribs = new ArrayList<>(macrodefAttributes.size());
653       for (AntDomScriptdefAttribute attribute : macrodefAttributes) {
654         final String nameAttrib = attribute.getName().getRawText();
655         if (nameAttrib != null) {
656           attribs.add(nameAttrib);
657         }
658       }
659       return attribs.iterator();
660     }
661
662     @Override
663     public boolean isContainer() {
664       return false;
665     }
666   }
667
668   private static class ContainerElementIntrospector extends AbstractIntrospector{
669     public static final ContainerElementIntrospector INSTANCE = new ContainerElementIntrospector();
670     @Override
671     public boolean isContainer() {
672       return true;
673     }
674   }
675
676   private static class EnumerationToIteratorAdapter<T> implements Iterator<T> {
677
678     private final Enumeration<T> myEnum;
679
680     EnumerationToIteratorAdapter(Enumeration<T> enumeration) {
681       myEnum = enumeration;
682     }
683
684     @Override
685     public boolean hasNext() {
686       return myEnum.hasMoreElements();
687     }
688
689     @Override
690     public T next() {
691       return myEnum.nextElement();
692     }
693
694     @Override
695     public void remove() {
696       throw new UnsupportedOperationException("remove is not supported");
697     }
698   }
699
700 }