emptyIterator() used; misc warning fixes
[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 Map<String,Class> coreTaskDefs = reflected.getTaskDefinitions();
105       final Map<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         }
191         else {
192           final Iterator<String> nested = parentIntrospector.getNestedElementsIterator();
193           while (nested.hasNext()) {
194             final String nestedElementName = nested.next();
195             final DomExtension extension = registerChild(registrar, genericInfo, nestedElementName);
196             if (extension != null) {
197               Class type = parentIntrospector.getNestedElementType(nestedElementName);
198               if (type != null && CommonClassNames.JAVA_LANG_OBJECT.equals(type.getName())) {
199                 type = null; // hack to support badly written tasks
200               }
201               if (type == null) {
202                 if (coreTypeDefs != null){
203                   type = coreTypeDefs.get(nestedElementName);
204                 }
205               }
206               if (type != null) {
207                 extension.putUserData(ELEMENT_IMPL_CLASS_KEY, type);
208               }
209               AntDomElement.Role role = AntDomElement.Role.DATA_TYPE;
210               if (coreTaskDefs != null && coreTaskDefs.containsKey(nestedElementName) ||
211                   type != null && isAssignableFrom(Task.class.getName(), type)) {
212                 role = AntDomElement.Role.TASK;
213               }
214               extension.putUserData(AntDomElement.ROLE, role);
215             }
216           }
217         }
218         registrar.registerCustomChildrenExtension(AntDomCustomElement.class, new AntCustomTagNameDescriptor());
219       }
220     }
221   }
222
223   private static void defineAttributes(XmlTag xmlTag, DomExtensionsRegistrar registrar, DomGenericInfo genericInfo, AbstractIntrospector parentIntrospector) {
224     final Map<String, Pair<Type, Class>> registeredAttribs = getStaticallyRegisteredAttributes(genericInfo);
225     // define attributes discovered by introspector and not yet defined statically
226     final Iterator<String> introspectedAttributes = parentIntrospector.getAttributesIterator();
227     while (introspectedAttributes.hasNext()) {
228       final String attribName = introspectedAttributes.next();
229       if (genericInfo.getAttributeChildDescription(attribName) == null) { // if not defined yet
230         final String _attribName = attribName.toLowerCase(Locale.US);
231         final Pair<Type, Class> types = registeredAttribs.get(_attribName);
232         Type type = Pair.getFirst(types);
233         Class converterClass = Pair.getSecond(types);
234         if (type == null) {
235           type = String.class; // use String by default
236           final Class attributeType = parentIntrospector.getAttributeType(attribName);
237           if (attributeType != null) {
238             // handle well-known types
239             if (File.class.isAssignableFrom(attributeType)) {
240               type = PsiFileSystemItem.class;
241               converterClass = AntPathConverter.class;
242             }
243             else if (Boolean.class.isAssignableFrom(attributeType)){
244               type = Boolean.class;
245               converterClass = AntBooleanConverter.class;
246             }
247             else if (isAssignableFrom(Reference.class.getName(), attributeType)) {
248               converterClass = AntDomRefIdConverter.class;
249             }
250           }
251         }
252
253         LOG.assertTrue(type != null);
254
255         registerAttribute(registrar, attribName, type, converterClass);
256         if (types == null) { // augment the map if this was a newly added attribute
257           registeredAttribs.put(_attribName, Pair.create(type, converterClass));
258         }
259       }
260     }
261     // handle attribute case problems:
262     // additionaly register all attributes that exist in XML but differ from the registered ones only in case
263     for (XmlAttribute xmlAttribute : xmlTag.getAttributes()) {
264       final String existingAttribName = xmlAttribute.getName();
265       if (genericInfo.getAttributeChildDescription(existingAttribName) == null) {
266         final Pair<Type, Class> pair = registeredAttribs.get(existingAttribName.toLowerCase(Locale.US));
267         if (pair != null) { // if such attribute should actually be here
268           registerAttribute(registrar, existingAttribName, pair.getFirst(), pair.getSecond());
269         }
270       }
271     }
272   }
273
274   private static void registerAttribute(DomExtensionsRegistrar registrar, String attribName, final @NotNull Type attributeType, final @Nullable Class converterType) {
275     final DomExtension extension = registrar.registerGenericAttributeValueChildExtension(new XmlName(attribName), attributeType);
276     if (converterType != null) {
277       try {
278         extension.setConverter((Converter)converterType.newInstance());
279       }
280       catch (InstantiationException | IllegalAccessException e) {
281         LOG.info(e);
282       }
283     }
284   }
285
286   private static Map<String, Pair<Type, Class>> getStaticallyRegisteredAttributes(final DomGenericInfo genericInfo) {
287     final Map<String, Pair<Type, Class>> map = new HashMap<>();
288     for (DomAttributeChildDescription description : genericInfo.getAttributeChildrenDescriptions()) {
289       final Type type = description.getType();
290       if (type instanceof ParameterizedType) {
291         final Type[] typeArguments = ((ParameterizedType)type).getActualTypeArguments();
292         if (typeArguments.length == 1) {
293           String name = description.getXmlElementName();
294           final Type attribType = typeArguments[0];
295           Class<? extends Converter> converterType = null;
296           final Convert converterAnnotation = description.getAnnotation(Convert.class);
297           if (converterAnnotation != null) {
298             converterType = converterAnnotation.value();
299           }
300           map.put(name.toLowerCase(Locale.US), new Pair<>(attribType, converterType));
301         }
302       }
303     }
304     return map;
305   }
306
307   @Nullable
308   private static DomExtension registerChild(DomExtensionsRegistrar registrar, DomGenericInfo elementInfo, String childName) {
309     if (elementInfo.getCollectionChildDescription(childName) == null) { // register if not yet defined statically
310       Class<? extends AntDomElement> modelClass = getModelClass(childName);
311       if (modelClass == null) {
312         modelClass = AntDomElement.class;
313       }
314       return registrar.registerCollectionChildrenExtension(new XmlName(childName), modelClass);
315     }
316     return null;
317   }
318
319   @Nullable
320   public static AntIntrospector getIntrospector(Class c) {
321     try {
322       return AntIntrospector.getInstance(c);
323     }
324     catch (Throwable ignored) {
325     }
326     return null;
327   }
328
329   @Nullable
330   private static Class<? extends AntDomElement> getModelClass(@NotNull String tagName) {
331     return TAG_MAPPING.get(tagName.toLowerCase(Locale.US));
332   }
333
334   private static boolean isAssignableFrom(final String baseClassName, final Class clazz) {
335     try {
336       final ClassLoader loader = clazz.getClassLoader();
337       if (loader != null) {
338         final Class<?> baseClass = loader.loadClass(baseClassName);
339         return baseClass.isAssignableFrom(clazz);
340       }
341     }
342     catch (ClassNotFoundException ignored) {
343     }
344     return false;
345   }
346
347   private static class AntCustomTagNameDescriptor extends CustomDomChildrenDescription.TagNameDescriptor {
348
349     @Override
350     public Set<EvaluatedXmlName> getCompletionVariants(@NotNull DomElement parent) {
351       if (!(parent instanceof AntDomElement)) {
352         return Collections.emptySet();
353       }
354       final AntDomElement element = (AntDomElement)parent;
355       final AntDomProject antDomProject = element.getAntProject();
356       if (antDomProject == null) {
357         return Collections.emptySet();
358       }
359       final CustomAntElementsRegistry registry = CustomAntElementsRegistry.getInstance(antDomProject);
360       final Set<EvaluatedXmlName> result = new HashSet<>();
361       for (XmlName variant : registry.getCompletionVariants(element)) {
362         final String ns = variant.getNamespaceKey();
363         result.add(new DummyEvaluatedXmlName(variant, ns != null? ns : ""));
364       }
365       return result;
366     }
367
368     @Override
369     @Nullable
370     public PomTarget findDeclaration(DomElement parent, @NotNull EvaluatedXmlName name) {
371       final XmlName xmlName = name.getXmlName();
372       return doFindDeclaration(parent, xmlName);
373     }
374
375     @Override
376     @Nullable
377     public PomTarget findDeclaration(@NotNull DomElement child) {
378       XmlName name = new XmlName(child.getXmlElementName(), child.getXmlElementNamespace());
379       return doFindDeclaration(child.getParent(), name);
380     }
381
382     @Nullable
383     private static PomTarget doFindDeclaration(DomElement parent, XmlName xmlName) {
384       if (!(parent instanceof AntDomElement)) {
385         return null;
386       }
387       final AntDomElement parentElement = (AntDomElement)parent;
388       final AntDomProject antDomProject = parentElement.getAntProject();
389       if (antDomProject == null) {
390         return null;
391       }
392       final CustomAntElementsRegistry registry = CustomAntElementsRegistry.getInstance(antDomProject);
393       final AntDomElement declaringElement = registry.findDeclaringElement(parentElement, xmlName);
394       if (declaringElement == null) {
395         return null;
396       }
397       DomTarget target = DomTarget.getTarget(declaringElement);
398       if (target == null && declaringElement instanceof AntDomTypeDef) {
399         final AntDomTypeDef typedef = (AntDomTypeDef)declaringElement;
400         final GenericAttributeValue<PsiFileSystemItem> resource = typedef.getResource();
401         if (resource != null) {
402           target = DomTarget.getTarget(declaringElement, resource);
403         }
404         if (target == null) {
405           final GenericAttributeValue<PsiFileSystemItem> file = typedef.getFile();
406           if (file != null) {
407             target = DomTarget.getTarget(declaringElement, file);
408           }
409         }
410       }
411       return target;
412     }
413   }
414
415   private static abstract class AbstractIntrospector {
416     @NotNull
417     public Iterator<String> getAttributesIterator() {
418       return Collections.emptyIterator();
419     }
420
421     @NotNull
422     public Iterator<String> getNestedElementsIterator(){
423       return Collections.emptyIterator();
424     }
425
426     public abstract boolean isContainer();
427
428     @Nullable
429     public Class getAttributeType(String attribName) {
430       return null;
431     }
432
433     @Nullable
434     public Class getNestedElementType(String elementName) {
435       return null;
436     }
437   }
438
439   private static class ClassIntrospectorAdapter extends AbstractIntrospector {
440
441     private final AntIntrospector myIntrospector;
442     private final Map<String, Class> myCoreTaskDefs;
443     private final Map<String, Class> myCoreTypeDefs;
444     private List<String> myNestedElements;
445     private Map<String, Class> myNestedElementTypes;
446
447     private ClassIntrospectorAdapter(AntIntrospector introspector) {
448       this(introspector, null, null);
449     }
450
451     ClassIntrospectorAdapter(AntIntrospector introspector, Map<String, Class> coreTaskDefs, Map<String, Class> coreTypeDefs) {
452       myIntrospector = introspector;
453       myCoreTaskDefs = coreTaskDefs != null? coreTaskDefs : Collections.emptyMap();
454       myCoreTypeDefs = coreTypeDefs != null? coreTypeDefs : Collections.emptyMap();
455     }
456
457     @Override
458     @NotNull
459     public Iterator<String> getAttributesIterator() {
460       return new EnumerationToIteratorAdapter<>(myIntrospector.getAttributes());
461     }
462
463     @Override
464     public Class getAttributeType(String attribName) {
465       return myIntrospector.getAttributeType(attribName);
466     }
467
468     @Override
469     public boolean isContainer() {
470       return myIntrospector.isContainer();
471     }
472
473     @Override
474     @NotNull
475     public Iterator<String> getNestedElementsIterator() {
476       initNestedElements();
477       return myNestedElements.iterator();
478     }
479
480     @Override
481     public Class getNestedElementType(String attribName) {
482       initNestedElements();
483       return myNestedElementTypes.get(attribName);
484     }
485
486     private void initNestedElements() {
487       if (myNestedElements != null) {
488         return;
489       }
490       myNestedElements = new ArrayList<>();
491       myNestedElementTypes = new HashMap<>();
492       final Enumeration<String> nestedElements = myIntrospector.getNestedElements();
493       while (nestedElements.hasMoreElements()) {
494         final String elemName = nestedElements.nextElement();
495         myNestedElements.add(elemName);
496         myNestedElementTypes.put(elemName, myIntrospector.getElementType(elemName));
497       }
498
499       final Set<String> extensionPointTypes = myIntrospector.getExtensionPointTypes();
500       for (String extPoint : extensionPointTypes) {
501         processEntries(extPoint, myCoreTaskDefs);
502         processEntries(extPoint, myCoreTypeDefs);
503       }
504     }
505
506     private void processEntries(String extPoint, final Map<String, Class> definitions) {
507       for (Map.Entry<String, Class> entry : definitions.entrySet()) {
508         final String elementName = entry.getKey();
509         final Class taskClass = entry.getValue();
510         if (isAssignableFrom(extPoint, taskClass)) {
511           myNestedElements.add(elementName);
512           myNestedElementTypes.put(elementName, taskClass);
513         }
514       }
515     }
516   }
517
518   private static class MacrodefIntrospectorAdapter extends AbstractIntrospector {
519     private final AntDomMacroDef myMacrodef;
520
521     private MacrodefIntrospectorAdapter(AntDomMacroDef macrodef) {
522       myMacrodef = macrodef;
523     }
524
525     @Override
526     @NotNull
527     public Iterator<String> getAttributesIterator() {
528       final List<AntDomMacrodefAttribute> macrodefAttributes = myMacrodef.getMacroAttributes();
529       if (macrodefAttributes.isEmpty()) {
530         return Collections.emptyIterator();
531       }
532       final List<String> attribs = new ArrayList<>(macrodefAttributes.size());
533       for (AntDomMacrodefAttribute attribute : macrodefAttributes) {
534         final String attribName = attribute.getName().getRawText();
535         if (attribName != null) {
536           attribs.add(attribName);
537         }
538       }
539       return attribs.iterator();
540     }
541
542     @Override
543     public boolean isContainer() {
544       return myMacrodef.getMacroElements().stream()
545         .map(AntDomMacrodefElement::isImplicit)
546         .anyMatch(implicit -> implicit != null && Boolean.TRUE.equals(implicit.getValue()));
547     }
548   }
549
550   private static class MacrodefElementOccurrenceIntrospectorAdapter extends AbstractIntrospector {
551     private final AntDomMacrodefElement myElement;
552     private volatile List<AbstractIntrospector> myContexts;
553     private volatile Map<String, Class> myChildrenMap;
554
555     private MacrodefElementOccurrenceIntrospectorAdapter(AntDomMacrodefElement element) {
556       myElement = element;
557     }
558
559     @Override
560     public boolean isContainer() {
561       return getContexts().stream().allMatch(AbstractIntrospector::isContainer);
562     }
563
564     @Override
565     @NotNull
566     public Iterator<String> getNestedElementsIterator() {
567       return getNestedElementsMap().keySet().iterator();
568     }
569
570     @Override
571     public Class getNestedElementType(String elementName) {
572       return getNestedElementsMap().get(elementName);
573     }
574
575     private Map<String, Class> getNestedElementsMap() {
576       if (myChildrenMap != null) {
577         return myChildrenMap;
578       }
579       final List<AbstractIntrospector> contexts = getContexts();
580       Map<String, Class> names = null;
581       for (AbstractIntrospector context : contexts) {
582         if (context.isContainer()) {
583           continue;
584         }
585         final Set<String> set = new HashSet<>();
586         for (Iterator<String> it = context.getNestedElementsIterator();it.hasNext();) {
587           final String name = it.next();
588           set.add(name);
589         }
590         if (names == null) {
591           names = new HashMap<>();
592           for (String s : set) {
593             names.put(s, context.getNestedElementType(s));
594           }
595         }
596         else {
597           names.keySet().retainAll(set);
598         }
599       }
600       final Map<String, Class> result = names == null ? Collections.emptyMap() : names;
601       return myChildrenMap = result;
602     }
603
604     private List<AbstractIntrospector> getContexts() {
605       if (myContexts != null) {
606         return myContexts;
607       }
608       final List<AbstractIntrospector> parents = new ArrayList<>();
609       final AntDomMacroDef macroDef = myElement.getParentOfType(AntDomMacroDef.class, true);
610       if (macroDef != null) {
611         final AntDomSequentialTask body = macroDef.getMacroBody();
612         if (body != null) {
613           body.accept(new AntDomRecursiveVisitor() {
614             @Override
615             public void visitAntDomCustomElement(AntDomCustomElement custom) {
616               if (myElement.equals(custom.getDeclaringElement())) {
617                 final AntDomElement parent = custom.getParentOfType(AntDomElement.class, true);
618                 if (parent != null) {
619                   final Class type = parent.getChildDescription().getUserData(ELEMENT_IMPL_CLASS_KEY);
620                   if (type != null) {
621                     final AntIntrospector antIntrospector = AntIntrospector.getInstance(type);
622                     if (antIntrospector != null) {
623                       parents.add(new ClassIntrospectorAdapter(antIntrospector));
624                     }
625                   }
626                 }
627               }
628             }
629           });
630         }
631       }
632       return myContexts = parents;
633     }
634   }
635
636   private static class ScriptdefIntrospectorAdapter extends AbstractIntrospector {
637     private final AntDomScriptDef myScriptDef;
638
639     private ScriptdefIntrospectorAdapter(AntDomScriptDef scriptDef) {
640       myScriptDef = scriptDef;
641     }
642
643     @Override
644     @NotNull
645     public Iterator<String> getAttributesIterator() {
646       final List<AntDomScriptdefAttribute> macrodefAttributes = myScriptDef.getScriptdefAttributes();
647       final List<String> attribs = new ArrayList<>(macrodefAttributes.size());
648       for (AntDomScriptdefAttribute attribute : macrodefAttributes) {
649         final String nameAttrib = attribute.getName().getRawText();
650         if (nameAttrib != null) {
651           attribs.add(nameAttrib);
652         }
653       }
654       return attribs.iterator();
655     }
656
657     @Override
658     public boolean isContainer() {
659       return false;
660     }
661   }
662
663   private static class ContainerElementIntrospector extends AbstractIntrospector{
664     public static final ContainerElementIntrospector INSTANCE = new ContainerElementIntrospector();
665     @Override
666     public boolean isContainer() {
667       return true;
668     }
669   }
670
671   private static class EnumerationToIteratorAdapter<T> implements Iterator<T> {
672
673     private final Enumeration<T> myEnum;
674
675     EnumerationToIteratorAdapter(Enumeration<T> enumeration) {
676       myEnum = enumeration;
677     }
678
679     @Override
680     public boolean hasNext() {
681       return myEnum.hasMoreElements();
682     }
683
684     @Override
685     public T next() {
686       return myEnum.nextElement();
687     }
688
689     @Override
690     public void remove() {
691       throw new UnsupportedOperationException("remove is not supported");
692     }
693   }
694
695 }