KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tools > ant > module > api > IntrospectedInfo


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.apache.tools.ant.module.api;
21
22 import java.io.IOException JavaDoc;
23 import java.io.InputStream JavaDoc;
24 import java.util.Arrays JavaDoc;
25 import java.util.Collections JavaDoc;
26 import java.util.Enumeration JavaDoc;
27 import java.util.HashSet JavaDoc;
28 import java.util.Map JavaDoc;
29 import java.util.Properties JavaDoc;
30 import java.util.Set JavaDoc;
31 import java.util.TreeMap JavaDoc;
32 import java.util.logging.Level JavaDoc;
33 import java.util.logging.Logger JavaDoc;
34 import java.util.prefs.BackingStoreException JavaDoc;
35 import java.util.prefs.Preferences JavaDoc;
36 import java.util.regex.Matcher JavaDoc;
37 import java.util.regex.Pattern JavaDoc;
38 import javax.swing.event.ChangeEvent JavaDoc;
39 import javax.swing.event.ChangeListener JavaDoc;
40 import org.apache.tools.ant.module.AntModule;
41 import org.apache.tools.ant.module.AntSettings;
42 import org.apache.tools.ant.module.bridge.AntBridge;
43 import org.apache.tools.ant.module.bridge.IntrospectionHelperProxy;
44 import org.openide.ErrorManager;
45 import org.openide.util.NbCollections;
46 import org.openide.util.RequestProcessor;
47 import org.openide.util.Utilities;
48 import org.openide.util.WeakListeners;
49
50 // XXX in order to support Ant 1.6 interface addition types, need to keep
51
// track of which classes implement a given interface
52

53 /** Represents Ant-style introspection info for a set of classes.
54  * There should be one instance which is loaded automatically
55  * from defaults.properties files, i.e. standard tasks/datatypes.
56  * A second is loaded from settings and represents custom tasks/datatypes.
57  * Uses Ant's IntrospectionHelper for the actual work, but manages the results
58  * and makes them safely serializable (stores only classnames, etc.).
59  * <p>
60  * All task and type names may be namespace-qualified for use
61  * in Ant 1.6: a name of the form <samp>nsuri:localname</samp> refers to
62  * an XML element with namespace <samp>nsuri</samp> and local name <samp>localname</samp>.
63  * Attribute names could also be similarly qualified, but in practice attributes
64  * used in Ant never have a defined namespace. The prefix <samp>antlib:org.apache.tools.ant:</samp>
65  * is implied, not expressed, on Ant core element names (for backwards compatibility).
66  * Subelement names are *not* namespace-qualified here, even though in the script
67  * they would be - because the namespace used in the script will actually vary
68  * according to how an antlib is imported and used. An unqualified subelement name
69  * should be understood to inherit a namespace from its parent element.
70  * <em>(Namespace support since <code>org.apache.tools.ant.module/3 3.6</code>)</em>
71  */

72 public final class IntrospectedInfo {
73     
74     private static final Logger JavaDoc LOG = Logger.getLogger(IntrospectedInfo.class.getName());
75     
76     private static IntrospectedInfo defaults = null;
77     private static boolean defaultsInited = false;
78     private static boolean defaultsEverInited = false;
79     
80     /** Get default definitions specified by Ant's defaults.properties.
81      * @return the singleton defaults
82      */

83     public static synchronized IntrospectedInfo getDefaults() {
84         if (defaults == null) {
85             defaults = new IntrospectedInfo();
86         }
87         return defaults;
88     }
89     
90     private Map JavaDoc<String JavaDoc,IntrospectedClass> clazzes = Collections.synchronizedMap(new TreeMap JavaDoc<String JavaDoc,IntrospectedClass>());
91     /** definitions first by kind then by name to class name */
92     private Map JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,String JavaDoc>> namedefs = new TreeMap JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,String JavaDoc>>();
93     
94     private Set JavaDoc<ChangeListener JavaDoc> listeners = new HashSet JavaDoc<ChangeListener JavaDoc>(5);
95     private Set JavaDoc<ChangeListener JavaDoc> tonotify = new HashSet JavaDoc<ChangeListener JavaDoc>(5);
96     
97     private ChangeListener JavaDoc antBridgeListener = new ChangeListener JavaDoc() {
98         public void stateChanged(ChangeEvent JavaDoc ev) {
99             clearDefs();
100             fireStateChanged();
101         }
102     };
103     
104     /** Make new empty set of info.
105      */

106     public IntrospectedInfo () {
107     }
108     
109     private void init() {
110         synchronized (IntrospectedInfo.class) {
111             if (!defaultsInited && this == defaults) {
112                 AntModule.err.log("IntrospectedInfo.getDefaults: loading...");
113                 defaultsInited = true;
114                 loadDefaults(!defaultsEverInited);
115                 defaultsEverInited = true;
116             }
117         }
118     }
119     
120     private void clearDefs() {
121         clazzes.clear();
122         namedefs.clear();
123         defaultsInited = false;
124     }
125     
126     private void loadDefaults(boolean listen) {
127         ClassLoader JavaDoc cl = AntBridge.getMainClassLoader();
128         InputStream JavaDoc taskDefaults = cl.getResourceAsStream("org/apache/tools/ant/taskdefs/defaults.properties");
129         if (taskDefaults != null) {
130             try {
131                 defaults.load(taskDefaults, "task", cl); // NOI18N
132
} catch (IOException JavaDoc ioe) {
133                 AntModule.err.log("Could not load default taskdefs");
134                 AntModule.err.notify(ioe);
135             }
136         } else {
137             AntModule.err.log("Could not open default taskdefs");
138         }
139         InputStream JavaDoc typeDefaults = cl.getResourceAsStream("org/apache/tools/ant/types/defaults.properties");
140         if (typeDefaults != null) {
141             try {
142                 defaults.load(typeDefaults, "type", cl); // NOI18N
143
} catch (IOException JavaDoc ioe) {
144                 AntModule.err.log("Could not load default typedefs");
145                 AntModule.err.notify(ioe);
146             }
147         } else {
148             AntModule.err.log("Could not open default typedefs");
149         }
150         defaults.loadNetBeansSpecificDefinitions();
151         if (listen) {
152             AntBridge.addChangeListener(WeakListeners.change(antBridgeListener, AntBridge.class));
153         }
154         if (AntModule.err.isLoggable(ErrorManager.INFORMATIONAL)) {
155             AntModule.err.log("IntrospectedInfo.defaults=" + defaults);
156         }
157     }
158
159     /** Add a listener to changes in the definition set.
160      * @param l the listener to add
161      * @since 2.6
162      */

163     public void addChangeListener(ChangeListener JavaDoc l) {
164         synchronized (listeners) {
165             listeners.add(l);
166         }
167     }
168     
169     /** Remove a listener to changes in the definition set.
170      * @param l the listener to remove
171      * @since 2.6
172      */

173     public void removeChangeListener(ChangeListener JavaDoc l) {
174         synchronized (listeners) {
175             listeners.remove(l);
176         }
177     }
178     
179     private class ChangeTask implements Runnable JavaDoc {
180         public void run() {
181             ChangeListener JavaDoc[] listeners2;
182             synchronized (listeners) {
183                 if (tonotify.isEmpty()) return;
184                 listeners2 = tonotify.toArray(new ChangeListener JavaDoc[tonotify.size()]);
185                 tonotify.clear();
186             }
187             ChangeEvent JavaDoc ev = new ChangeEvent JavaDoc(IntrospectedInfo.this);
188             for (ChangeListener JavaDoc l : listeners2) {
189                 l.stateChanged(ev);
190             }
191         }
192     }
193     private void fireStateChanged() {
194         if (AntModule.err.isLoggable(ErrorManager.INFORMATIONAL)) {
195             AntModule.err.log("IntrospectedInfo.fireStateChanged");
196         }
197         synchronized (listeners) {
198             if (listeners.isEmpty()) return;
199             if (tonotify.isEmpty()) {
200                 RequestProcessor.getDefault().post(new ChangeTask());
201             }
202             tonotify.addAll(listeners);
203         }
204     }
205     
206     /** Get definitions.
207      * @param kind the kind of definition, e.g. <code>task</code>
208      * @return an immutable map from definition names to class names
209      */

210     public Map JavaDoc<String JavaDoc,String JavaDoc> getDefs(String JavaDoc kind) {
211         init();
212         synchronized (namedefs) {
213             Map JavaDoc<String JavaDoc,String JavaDoc> m = namedefs.get(kind);
214             if (m != null) {
215                 return Collections.unmodifiableMap(m);
216             } else {
217                 return Collections.emptyMap();
218             }
219         }
220     }
221     
222     private IntrospectedClass getData (String JavaDoc clazz) throws IllegalArgumentException JavaDoc {
223         IntrospectedClass data = clazzes.get(clazz);
224         if (data == null) {
225             throw new IllegalArgumentException JavaDoc("Unknown class: " + clazz); // NOI18N
226
}
227         return data;
228     }
229     
230     /** Is anything known about this class?
231      * @param clazz the class name
232      * @return true if it is known, false if never encountered
233      */

234     public boolean isKnown (String JavaDoc clazz) {
235         init();
236         return clazzes.get (clazz) != null;
237     }
238     
239     /** Does this class support inserting text data?
240      * @param clazz the class name
241      * @return true if so
242      * @throws IllegalArgumentException if the class is unknown
243      */

244     public boolean supportsText (String JavaDoc clazz) throws IllegalArgumentException JavaDoc {
245         init();
246         return getData (clazz).supportsText;
247     }
248     
249     /** Get all attributes supported by this class.
250      * @param clazz the class name
251      * @return an immutable map from attribute name to type (class name)
252      * @throws IllegalArgumentException if the class is unknown
253      */

254     public Map JavaDoc<String JavaDoc,String JavaDoc> getAttributes(String JavaDoc clazz) throws IllegalArgumentException JavaDoc {
255         init();
256         Map JavaDoc<String JavaDoc,String JavaDoc> map = getData(clazz).attrs;
257         if (map == null) {
258             return Collections.emptyMap();
259         } else {
260             return Collections.unmodifiableMap (map);
261         }
262     }
263     
264     /** Get all subelements supported by this class.
265      * @param clazz the class name
266      * @return an immutable map from element name to type (class name)
267      * @throws IllegalArgumentException if the class is unknown
268      */

269     public Map JavaDoc<String JavaDoc,String JavaDoc> getElements(String JavaDoc clazz) throws IllegalArgumentException JavaDoc {
270         init();
271         Map JavaDoc<String JavaDoc,String JavaDoc> map = getData(clazz).subs;
272         if (map == null) {
273             return Collections.emptyMap();
274         } else {
275             return Collections.unmodifiableMap (map);
276         }
277     }
278     
279     /**
280      * Get tags represented by this class if it is an <code>EnumeratedAttribute</code>.
281      * @param clazz the class name
282      * @return a list of tag names, or null if the class is not a subclass of <code>EnumeratedAttribute</code>
283      * @throws IllegalArgumentException if the class is unknown
284      * @since org.apache.tools.ant.module/3 3.3
285      */

286     public String JavaDoc[] getTags(String JavaDoc clazz) throws IllegalArgumentException JavaDoc {
287         init();
288         return getData(clazz).enumTags;
289     }
290     
291     /** Load defs from a properties file. */
292     private void load (InputStream JavaDoc is, String JavaDoc kind, ClassLoader JavaDoc cl) throws IOException JavaDoc {
293         Properties JavaDoc p = new Properties JavaDoc ();
294         try {
295             p.load (is);
296         } finally {
297             is.close ();
298         }
299         for (Map.Entry JavaDoc<String JavaDoc,String JavaDoc> entry : NbCollections.checkedMapByFilter(p, String JavaDoc.class, String JavaDoc.class, true).entrySet()) {
300             String JavaDoc name = entry.getKey();
301             if (kind.equals("type") && name.equals("description")) { // NOI18N
302
// Not a real data type; handled specially.
303
AntModule.err.log("Skipping pseudodef of <description>");
304                 continue;
305             }
306             String JavaDoc clazzname = entry.getValue();
307             try {
308                 Class JavaDoc clazz = cl.loadClass (clazzname);
309                 register(name, clazz, kind, false);
310             } catch (ClassNotFoundException JavaDoc cnfe) {
311                 // This is normal, e.g. Ant's taskdefs include optional tasks we don't have.
312
AntModule.err.log ("IntrospectedInfo: skipping " + clazzname + ": " + cnfe);
313             } catch (NoClassDefFoundError JavaDoc ncdfe) {
314                 // Normal for e.g. optional tasks which we cannot resolve against.
315
AntModule.err.log ("IntrospectedInfo: skipping " + clazzname + ": " + ncdfe);
316             } catch (LinkageError JavaDoc e) {
317                 // Not normal; if it is there it ought to be resolvable etc.
318
throw (IOException JavaDoc) new IOException JavaDoc("Could not load class " + clazzname + ": " + e).initCause(e); // NOI18N
319
} catch (RuntimeException JavaDoc e) {
320                 // SecurityException etc. Not normal.
321
throw (IOException JavaDoc) new IOException JavaDoc("Could not load class " + clazzname + ": " + e).initCause(e); // NOI18N
322
}
323         }
324     }
325     
326     private void loadNetBeansSpecificDefinitions() {
327         loadNetBeansSpecificDefinitions0(AntBridge.getCustomDefsNoNamespace());
328         if (AntBridge.getInterface().isAnt16()) {
329             // Define both.
330
loadNetBeansSpecificDefinitions0(AntBridge.getCustomDefsWithNamespace());
331         }
332     }
333     
334     private void loadNetBeansSpecificDefinitions0(Map JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,Class JavaDoc>> defsByKind) {
335         for (Map.Entry JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,Class JavaDoc>> kindE : defsByKind.entrySet()) {
336             for (Map.Entry JavaDoc<String JavaDoc,Class JavaDoc> defsE : kindE.getValue().entrySet()) {
337                 register(defsE.getKey(), defsE.getValue(), kindE.getKey());
338             }
339         }
340     }
341     
342     /** Register a new definition.
343      * May change the defined task/type for a given name, but
344      * will not redefine structure if classes are modified.
345      * Also any class definitions contained in the default map (if not this one)
346      * are just ignored; you should refer to the default map for info on them.
347      * Throws various errors if the class could not be resolved, e.g. NoClassDefFoundError.
348      * @param name name of the task or type as it appears in scripts
349      * @param clazz the implementing class
350      * @param kind the kind of definition to register (<code>task</code> or <code>type</code> currently)
351      * @since 2.4
352      */

353     public synchronized void register(String JavaDoc name, Class JavaDoc clazz, String JavaDoc kind) {
354         register(name, clazz, kind, true);
355     }
356     
357     private void register(String JavaDoc name, Class JavaDoc clazz, String JavaDoc kind, boolean fire) {
358         init();
359         synchronized (namedefs) {
360             Map JavaDoc<String JavaDoc,String JavaDoc> m = namedefs.get(kind);
361             if (m == null) {
362                 m = new TreeMap JavaDoc<String JavaDoc,String JavaDoc>();
363                 namedefs.put(kind, m);
364             }
365             m.put(name, clazz.getName());
366         }
367         boolean changed = analyze(clazz, null, false);
368         if (changed && fire) {
369             fireStateChanged();
370         }
371     }
372     
373     /** Unregister a definition.
374      * Removes it from the definition mapping, though structural
375      * information about the implementing class (and classes referenced
376      * by that class) will not be removed.
377      * If the definition was not registered before, does nothing.
378      * @param name the definition name
379      * @param kind the kind of definition (<code>task</code> etc.)
380      * @since 2.4
381      */

382     public synchronized void unregister(String JavaDoc name, String JavaDoc kind) {
383         init();
384         synchronized (namedefs) {
385             Map JavaDoc<String JavaDoc,String JavaDoc> m = namedefs.get(kind);
386             if (m != null) {
387                 m.remove(name);
388             }
389         }
390         fireStateChanged();
391     }
392     
393     /**
394      * Analyze a particular class and other classes recursively.
395      * Will never try to redefine anything in the default IntrospectedInfo.
396      * For custom IntrospectedInfo's, will never try to redefine anything
397      * if skipReanalysis is null. If not null, will not redefine anything
398      * in that set - so start recursion by passing an empty set, if you wish
399      * to redefine anything you come across recursively that is not in the
400      * default IntrospectedInfo, without causing loops.
401      * Attribute classes are examined just in case they are EnumeratedAttribute
402      * subclasses; they are not checked for subelements etc.
403      * Does not itself fire changes - you should do this if the return value is true.
404      * @param clazz the class to look at
405      * @param skipReanalysis null to do not redefs, or a set of already redef'd classes
406      * @param isAttrType false for an element class, true for an attribute class
407      * @return true if something changed
408      */

409     private boolean analyze(Class JavaDoc clazz, Set JavaDoc<Class JavaDoc> skipReanalysis, boolean isAttrType) {
410         String JavaDoc n = clazz.getName();
411         /*
412         if (AntModule.err.isLoggable(ErrorManager.INFORMATIONAL)) {
413             AntModule.err.log("IntrospectedInfo.analyze: " + n + " skipping=" + skipReanalysis + " attrType=" + isAttrType);
414         }
415          */

416         if (getDefaults().isKnown(n)) {
417             // Never try to redefine anything in the default IntrospectedInfo.
418
return false;
419         }
420         if ((skipReanalysis == null || !skipReanalysis.add(clazz)) && /* #23630 */isKnown(n)) {
421             // Either we are not redefining anything; or we are, but this class
422
// has already been in the list. Skip it. If we are continuing, make
423
// sure to add this class to the skip list so we do not loop.
424
return false;
425         }
426         //AntModule.err.log ("IntrospectedInfo.analyze: clazz=" + clazz.getName ());
427
//boolean dbg = (clazz == org.apache.tools.ant.taskdefs.Taskdef.class);
428
//if (! dbg && clazz.getName ().equals ("org.apache.tools.ant.taskdefs.Taskdef")) { // NOI18N
429
// AntModule.err.log ("Classloader mismatch: cl1=" + clazz.getClassLoader () + " cl2=" + org.apache.tools.ant.taskdefs.Taskdef.class.getClassLoader ());
430
//}
431
//if (dbg) AntModule.err.log ("Analyzing <taskdef> attrs...");
432
IntrospectedClass info = new IntrospectedClass ();
433         if (isAttrType) {
434             String JavaDoc[] enumTags = AntBridge.getInterface().getEnumeratedValues(clazz);
435             if (enumTags != null) {
436                 info.enumTags = enumTags;
437                 return !info.equals(clazzes.put(clazz.getName(), info));
438             } else {
439                 // Do not store attr clazzes unless they are interesting: EnumAttr.
440
return clazzes.remove(clazz.getName()) != null;
441             }
442             // That's all we do - no subelements etc.
443
}
444         IntrospectionHelperProxy helper = AntBridge.getInterface().getIntrospectionHelper(clazz);
445         info.supportsText = helper.supportsCharacters ();
446         Enumeration JavaDoc<String JavaDoc> e = helper.getAttributes();
447         Set JavaDoc<Class JavaDoc> nueAttrTypeClazzes = new HashSet JavaDoc<Class JavaDoc>();
448         //if (dbg) AntModule.err.log ("Analyzing <taskdef> attrs...");
449
if (e.hasMoreElements ()) {
450             while (e.hasMoreElements ()) {
451                 String JavaDoc name = e.nextElement();
452                 //if (dbg) AntModule.err.log ("\tname=" + name);
453
try {
454                     Class JavaDoc attrType = helper.getAttributeType(name);
455                     String JavaDoc type = attrType.getName();
456                     //if (dbg) AntModule.err.log ("\ttype=" + type);
457
if (hasSuperclass(clazz, "org.apache.tools.ant.Task") && // NOI18N
458
((name.equals ("location") && type.equals ("org.apache.tools.ant.Location")) || // NOI18N
459
(name.equals ("taskname") && type.equals ("java.lang.String")) || // NOI18N
460
(name.equals ("description") && type.equals ("java.lang.String")))) { // NOI18N
461
// IntrospectionHelper is supposed to exclude such things, but I guess not.
462
// Or it excludes location & taskType.
463
// description may be OK to actually show on nodes, but since it is common
464
// to all tasks it should not be stored as such. Ditto taskname.
465
continue;
466                     }
467                     // XXX also handle subclasses of DataType and its standard attrs
468
// incl. creating nicely-named node props for description, refid, etc.
469
if (info.attrs == null) {
470                         info.attrs = new TreeMap JavaDoc<String JavaDoc,String JavaDoc>();
471                     }
472                     info.attrs.put (name, type);
473                     nueAttrTypeClazzes.add(attrType);
474                 } catch (RuntimeException JavaDoc re) { // i.e. BuildException; but avoid loading this class
475
AntModule.err.notify (ErrorManager.INFORMATIONAL, re);
476                 }
477             }
478         } else {
479             info.attrs = null;
480         }
481         Set JavaDoc<Class JavaDoc> nueClazzes = new HashSet JavaDoc<Class JavaDoc>();
482         e = helper.getNestedElements ();
483         //if (dbg) AntModule.err.log ("Analyzing <taskdef> subels...");
484
if (e.hasMoreElements ()) {
485             while (e.hasMoreElements ()) {
486                 String JavaDoc name = e.nextElement();
487                 //if (dbg) AntModule.err.log ("\tname=" + name);
488
try {
489                     Class JavaDoc subclazz = helper.getElementType (name);
490                     //if (dbg) AntModule.err.log ("\ttype=" + subclazz.getName ());
491
if (info.subs == null) {
492                         info.subs = new TreeMap JavaDoc<String JavaDoc,String JavaDoc>();
493                     }
494                     info.subs.put (name, subclazz.getName ());
495                     nueClazzes.add (subclazz);
496                 } catch (RuntimeException JavaDoc re) { // i.e. BuildException; but avoid loading this class
497
AntModule.err.notify (ErrorManager.INFORMATIONAL, re);
498                 }
499             }
500         } else {
501             info.subs = null;
502         }
503         boolean changed = !info.equals(clazzes.put(clazz.getName(), info));
504         // And recursively analyze reachable classes for subelements...
505
// (usually these will already be known, and analyze will return at once)
506
for (Class JavaDoc nueClazz : nueClazzes) {
507             changed |= analyze(nueClazz, skipReanalysis, false);
508         }
509         for (Class JavaDoc nueClazz : nueAttrTypeClazzes) {
510             changed |= analyze(nueClazz, skipReanalysis, true);
511         }
512         return changed;
513     }
514     
515     private static boolean hasSuperclass(Class JavaDoc subclass, String JavaDoc superclass) {
516         for (Class JavaDoc c = subclass; c != null; c = c.getSuperclass()) {
517             if (c.getName().equals(superclass)) {
518                 return true;
519             }
520         }
521         return false;
522     }
523     
524     /**
525      * Scan an existing (already-run) project to see if it has any new tasks/types.
526      * Any new definitions found will automatically be added to the known list.
527      * This will try to change existing definitions in the custom set, i.e.
528      * if a task is defined to be implemented with a different class, or if a
529      * class changes structure.
530      * Will not try to define anything contained in the defaults list.
531      * @param defs map from kinds to maps from names to classes
532      */

533     public void scanProject(Map JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,Class JavaDoc>> defs) {
534         init();
535         Set JavaDoc<Class JavaDoc> skipReanalysis = new HashSet JavaDoc<Class JavaDoc>();
536         boolean changed = false;
537         for (Map.Entry JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,Class JavaDoc>> e : defs.entrySet()) {
538             changed |= scanMap(e.getValue(), e.getKey(), skipReanalysis);
539         }
540         if (AntModule.err.isLoggable(ErrorManager.INFORMATIONAL)) {
541             AntModule.err.log("IntrospectedInfo.scanProject: " + this);
542         }
543         if (changed) {
544             fireStateChanged();
545         }
546     }
547     
548     private boolean scanMap(Map JavaDoc<String JavaDoc,Class JavaDoc> m, String JavaDoc kind, Set JavaDoc<Class JavaDoc> skipReanalysis) {
549         if (kind == null) throw new IllegalArgumentException JavaDoc();
550         boolean changed = false;
551         for (Map.Entry JavaDoc<String JavaDoc,Class JavaDoc> entry : m.entrySet()) {
552             String JavaDoc name = entry.getKey();
553             if (kind.equals("type") && name.equals("description")) { // NOI18N
554
// Not a real data type; handled specially.
555
AntModule.err.log("Skipping pseudodef of <description>");
556                 continue;
557             }
558             Class JavaDoc clazz = entry.getValue();
559             if (clazz.getName().equals("org.apache.tools.ant.taskdefs.MacroInstance")) { // NOI18N
560
continue;
561             }
562             Map JavaDoc<String JavaDoc,String JavaDoc> registry = namedefs.get(kind);
563             if (registry == null) {
564                 registry = new TreeMap JavaDoc<String JavaDoc,String JavaDoc>();
565                 namedefs.put(kind, registry);
566             }
567             synchronized (this) {
568                 Map JavaDoc<String JavaDoc,String JavaDoc> defaults = getDefaults().getDefs(kind);
569                 if (defaults.get(name) == null) {
570                     changed |= !clazz.getName().equals(registry.put(name, clazz.getName()));
571                 }
572                 if (! getDefaults ().isKnown (clazz.getName ())) {
573                     try {
574                         changed |= analyze(clazz, skipReanalysis, false);
575                     } catch (ThreadDeath JavaDoc td) {
576                         throw td;
577                     } catch (NoClassDefFoundError JavaDoc ncdfe) {
578                         // Reasonably normal.
579
AntModule.err.log ("Skipping " + clazz.getName () + ": " + ncdfe);
580                     } catch (LinkageError JavaDoc e) {
581                         // Not so normal.
582
AntModule.err.annotate (e, ErrorManager.INFORMATIONAL, "Cannot scan class " + clazz.getName (), null, null, null); // NOI18N
583
AntModule.err.notify (ErrorManager.INFORMATIONAL, e);
584                     }
585                 }
586             }
587         }
588         return changed;
589     }
590     
591     @Override JavaDoc
592     public String JavaDoc toString () {
593         return "IntrospectedInfo[namedefs=" + namedefs + ",clazzes=" + clazzes + "]"; // NOI18N
594
}
595     
596     private static final class IntrospectedClass {
597         
598         public boolean supportsText;
599         public Map JavaDoc<String JavaDoc,String JavaDoc> attrs; // null or name -> class
600
public Map JavaDoc<String JavaDoc,String JavaDoc> subs; // null or name -> class
601
public String JavaDoc[] enumTags; // null or list of tags
602

603         @Override JavaDoc
604         public String JavaDoc toString () {
605             return "IntrospectedClass[text=" + supportsText + ",attrs=" + attrs + ",subs=" + subs + ",enumTags=" + Arrays.toString(enumTags) + "]"; // NOI18N
606
}
607         
608         @Override JavaDoc
609         public int hashCode() {
610             // XXX
611
return 0;
612         }
613         
614         @Override JavaDoc
615         public boolean equals(Object JavaDoc o) {
616             if (!(o instanceof IntrospectedClass)) {
617                 return false;
618             }
619             IntrospectedClass other = (IntrospectedClass)o;
620             return supportsText == other.supportsText &&
621                 Utilities.compareObjects(attrs, other.attrs) &&
622                 Utilities.compareObjects(subs, other.subs) &&
623                 Utilities.compareObjects(enumTags, other.enumTags);
624         }
625         
626     }
627     
628     // merging and including custom defs:
629

630     /** only used to permit use of WeakListener */
631     private ChangeListener JavaDoc holder;
632     
633     /**
634      * Merge several IntrospectedInfo instances together.
635      * Responds live to updates.
636      */

637     private static IntrospectedInfo merge(IntrospectedInfo[] proxied) {
638         final IntrospectedInfo ii = new IntrospectedInfo();
639         ChangeListener JavaDoc l = new ChangeListener JavaDoc() {
640             public void stateChanged(ChangeEvent JavaDoc ev) {
641                 IntrospectedInfo ii2 = (IntrospectedInfo)ev.getSource();
642                 ii2.init();
643                 ii.clazzes.putAll(ii2.clazzes);
644                 for (Map.Entry JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,String JavaDoc>> e : ii2.namedefs.entrySet()) {
645                     String JavaDoc kind = e.getKey();
646                     Map JavaDoc<String JavaDoc,String JavaDoc> entries = e.getValue();
647                     if (ii.namedefs.containsKey(kind)) {
648                         ii.namedefs.get(kind).putAll(entries);
649                     } else {
650                         ii.namedefs.put(kind, new TreeMap JavaDoc<String JavaDoc,String JavaDoc>(entries));
651                     }
652                 }
653                 ii.fireStateChanged();
654             }
655         };
656         ii.holder = l;
657         for (IntrospectedInfo info : proxied) {
658             info.addChangeListener(WeakListeners.change(l, info));
659             l.stateChanged(new ChangeEvent JavaDoc(info));
660         }
661         return ii;
662     }
663     
664     /** defaults + custom defs */
665     private static IntrospectedInfo merged;
666     
667     /**
668      * Get all known introspected definitions.
669      * Includes all those in {@link #getDefaults} plus custom definitions
670      * encountered in actual build scripts (details unspecified).
671      * @return a set of all known definitions, e.g. of tasks and types
672      * @since 2.14
673      */

674     public static synchronized IntrospectedInfo getKnownInfo() {
675         if (merged == null) {
676             merged = merge(new IntrospectedInfo[] {
677                 getDefaults(),
678                 AntSettings.getCustomDefs(),
679             });
680         }
681         return merged;
682     }
683
684     static {
685         AntSettings.IntrospectedInfoSerializer.instance = new AntSettings.IntrospectedInfoSerializer() {
686             /*
687             Format quick key:
688             Map<String,Map<String,String>> namedefs: task.echo=org.apache.tools.ant.taskdefs.Echo
689             Map<String,IntrospectedClass> clazzes: class.org.apache.tools.ant.taskdefs.Echo.<...>
690             boolean supportsText: .supportsText=true
691             null | Map<String,String> attrs: .attrs.message=java.lang.String
692             null | Map<String,String> subs: .subs.file=java.io.File
693             null | String[] enumTags: .enumTags=whenempty,always,never
694              */

695             Pattern JavaDoc p = Pattern.compile("(.+)\\.(supportsText|attrs\\.(.+)|subs\\.(.+)|enumTags)");
696             public IntrospectedInfo load(Preferences JavaDoc node) {
697                 IntrospectedInfo ii = new IntrospectedInfo();
698                 try {
699                     for (String JavaDoc k : node.keys()) {
700                         String JavaDoc v = node.get(k, null);
701                         assert v != null : k;
702                         String JavaDoc[] ss = k.split("\\.", 2);
703                         if (ss[0].equals("class")) {
704                             Matcher JavaDoc m = p.matcher(ss[1]);
705                             boolean match = m.matches();
706                             if (!match) {
707                                 LOG.log(Level.WARNING, "malformed key: {0}", k);
708                                 continue;
709                             }
710                             String JavaDoc c = m.group(1);
711                             IntrospectedClass ic = assureDefined(ii, c);
712                             String JavaDoc tail = m.group(2);
713                             if (tail.equals("supportsText")) {
714                                 assert v.equals("true") : k;
715                                 ic.supportsText = true;
716                             } else if (tail.equals("enumTags")) {
717                                 ic.enumTags = v.split(",");
718                             } else if (m.group(3) != null) {
719                                 if (ic.attrs == null) {
720                                     ic.attrs = new TreeMap JavaDoc<String JavaDoc,String JavaDoc>();
721                                 }
722                                 ic.attrs.put(m.group(3), v);
723                                 //assureDefined(ii, v);
724
} else {
725                                 assert m.group(4) != null : k;
726                                 if (ic.subs == null) {
727                                     ic.subs = new TreeMap JavaDoc<String JavaDoc,String JavaDoc>();
728                                 }
729                                 ic.subs.put(m.group(4), v);
730                                 //assureDefined(ii, v);
731
}
732                         } else {
733                             Map JavaDoc<String JavaDoc,String JavaDoc> m = ii.namedefs.get(ss[0]);
734                             if (m == null) {
735                                 m = new TreeMap JavaDoc<String JavaDoc,String JavaDoc>();
736                                 ii.namedefs.put(ss[0], m);
737                             }
738                             m.put(ss[1], v);
739                             //assureDefined(ii, v);
740
}
741                     }
742                 } catch (BackingStoreException JavaDoc x) {
743                     LOG.log(Level.WARNING, null, x);
744                 }
745                 for (String JavaDoc kind : new String JavaDoc[] {"task", "type"}) {
746                     if (!ii.namedefs.containsKey(kind)) {
747                         ii.namedefs.put(kind, new TreeMap JavaDoc<String JavaDoc,String JavaDoc>());
748                     }
749                 }
750                 return ii;
751             }
752             private IntrospectedClass assureDefined(IntrospectedInfo ii, String JavaDoc clazz) {
753                 IntrospectedClass ic = ii.clazzes.get(clazz);
754                 if (ic == null) {
755                     ic = new IntrospectedClass();
756                     ii.clazzes.put(clazz, ic);
757                 }
758                 return ic;
759             }
760             public void store(Preferences JavaDoc node, IntrospectedInfo info) {
761                 try {
762                     node.clear();
763                 } catch (BackingStoreException JavaDoc x) {
764                     LOG.log(Level.WARNING, null, x);
765                     return;
766                 }
767                 for (Map.Entry JavaDoc<String JavaDoc,Map JavaDoc<String JavaDoc,String JavaDoc>> kindEntries : info.namedefs.entrySet()) {
768                     for (Map.Entry JavaDoc<String JavaDoc,String JavaDoc> namedef : kindEntries.getValue().entrySet()) {
769                         node.put(kindEntries.getKey() + "." + namedef.getKey(), namedef.getValue());
770                     }
771                 }
772                 for (Map.Entry JavaDoc<String JavaDoc,IntrospectedClass> clazzPair : info.clazzes.entrySet()) {
773                     String JavaDoc c = "class." + clazzPair.getKey();
774                     IntrospectedClass ic = clazzPair.getValue();
775                     if (ic.supportsText) {
776                         node.putBoolean(c + ".supportsText", true);
777                     }
778                     if (ic.attrs != null) {
779                         for (Map.Entry JavaDoc<String JavaDoc,String JavaDoc> attr : ic.attrs.entrySet()) {
780                             node.put(c + ".attrs." + attr.getKey(), attr.getValue());
781                         }
782                     }
783                     if (ic.subs != null) {
784                         for (Map.Entry JavaDoc<String JavaDoc,String JavaDoc> sub : ic.subs.entrySet()) {
785                             node.put(c + ".subs." + sub.getKey(), sub.getValue());
786                         }
787                     }
788                     if (ic.enumTags != null) {
789                         StringBuilder JavaDoc b = new StringBuilder JavaDoc();
790                         for (String JavaDoc s : ic.enumTags) {
791                             if (b.length() > 0) {
792                                 b.append(',');
793                             }
794                             b.append(s);
795                         }
796                         node.put(c + ".enumTags", b.toString());
797                     }
798                 }
799                 // Exact equivalence is unlikely to happen; there may be unanalyzed Java classes, etc.
800
//assert equiv(info, load(node)) : info + " vs. " + load(node);
801
}
802             private boolean equiv(IntrospectedInfo ii1, IntrospectedInfo ii2) {
803                 return ii1.clazzes.equals(ii2.clazzes) && ii1.namedefs.equals(ii2.namedefs);
804             }
805         };
806     }
807     
808 }
809
Popular Tags