001    package edu.rice.cs.cunit.threadCheck;
002    
003    import edu.rice.cs.cunit.classFile.ClassFile;
004    import edu.rice.cs.cunit.classFile.attributes.AAnnotationsAttributeInfo;
005    import edu.rice.cs.cunit.instrumentors.threadCheck.*;
006    import static edu.rice.cs.cunit.instrumentors.threadCheck.AThreadCheckStrategy.*;
007    import edu.rice.cs.cunit.util.Debug;
008    import edu.rice.cs.cunit.util.ILambda;
009    import edu.rice.cs.cunit.util.IPredicate;
010    import edu.rice.cs.cunit.util.XMLConfig;
011    import org.w3c.dom.Node;
012    
013    import java.io.StringReader;
014    import java.util.ArrayList;
015    import java.util.HashMap;
016    import java.util.List;
017    
018    /**
019     * Utilities to write annotations to an XML configuration.
020     *
021     * @author Mathias Ricken
022     */
023    public class XMLAnnotUtils {
024        /**
025         * Utilities for putting annotations into an XML configuration.
026         */
027        public static class AnnotationToXML {
028            /**
029             * Put the annotations from the lists into the XML document.
030             *
031             * @param xc   XML configuration
032             * @param root node under which they are to appear
033             * @param tcAR records of annotations
034             */
035            public static void processAnnotations(XMLConfig xc,
036                                                  Node root,
037                                                  ThreadCheckAnnotationRecord tcAR) {
038                for(String s : tcAR.denyThreadNames) {
039                    Node node = xc.set("name.type", "not", root, false);
040                    xc.set(".value", s, node, false);
041                }
042                for(String s : tcAR.denyThreadGroups) {
043                    Node node = xc.set("group.type", "not", root, false);
044                    xc.set(".value", s, node, false);
045                }
046                for(Long id : tcAR.denyThreadIds) {
047                    Node node = xc.set("id.type", "not", root, false);
048                    xc.set(".value", String.valueOf(id), node, false);
049                }
050                for(String s : tcAR.allowThreadNames) {
051                    Node node = xc.set("name.type", "only", root, false);
052                    xc.set(".value", s, node, false);
053                }
054                for(String s : tcAR.allowThreadGroups) {
055                    Node node = xc.set("group.type", "only", root, false);
056                    xc.set(".value", s, node, false);
057                }
058                for(Long id : tcAR.allowThreadIds) {
059                    Node node = xc.set("id.type", "only", root, false);
060                    xc.set(".value", String.valueOf(id), node, false);
061                }
062                if (tcAR.allowEventThread != OnlyRunBy.EVENT_THREAD.NO) {
063                    xc.set("eventThread.type", tcAR.allowEventThread.toString().toLowerCase(), root, false);
064                }
065                HashMap<String, ArrayList<PredicateAnnotationRecord>> rootSets
066                    = new HashMap<String, ArrayList<PredicateAnnotationRecord>>();
067                rootSets.put("", tcAR.predicateAnnotations);
068                processPredicateAnnotations(xc, root, rootSets);
069            }
070    
071            /**
072             * Process a list of predicate annotations and add &lt;predicate&gt; or &lt;combine&gt; tags underneath the specified node.
073             *
074             * @param xc   XML configuration
075             * @param node XML node that is going to be the parent of the &lt;predicate&gt; or &lt;combine&gt; tags
076             * @param pars predicate annotation records
077             */
078            private static void processPredicateAnnotations(XMLConfig xc,
079                                                            Node node,
080                                                            HashMap<String, ArrayList<PredicateAnnotationRecord>> pars) {
081                for(final String key : pars.keySet()) {
082                    for(final PredicateAnnotationRecord par : pars.get(key)) {
083                        Node predNode;
084                        if (par.predicateClass != null) {
085                            // @PredicateLink specified
086                            predNode = xc.set("predicate.type", par.annotation.getType(), node, false);
087                            Node membersNode = predNode.getOwnerDocument().createElement("values");
088                            predNode.appendChild(membersNode);
089                            processPredicateMembers(xc, membersNode, par.paramNames, par.paramTypes, par.valueList);
090                        }
091                        else {
092                            // @Combine specified
093                            predNode = xc.set("combine.type", par.annotation.getType(), node, false);
094                            Node predicatesNode = xc.set("values.mode", par.combineMode.toString(), predNode, false);
095                            processPredicateAnnotations(xc, predicatesNode, par.combinedPredicates);
096                        }
097                        if (par.passArguments) {
098                            // method arguments should be passed
099                            xc.set(".arguments", "true", predNode, true);
100                        }
101                        processAnnotation(xc, predNode, par.annotation.getPairs(), par.paramTypes);
102                    }
103                }
104            }
105    
106            /**
107             * Process an annotation and add the &lt;arg&gt; tags underneath the specified node.
108             *
109             * @param xc         XML configuration
110             * @param node       XML node that is going to be the parent of the &lt;arg&gt; tags
111             * @param nvpList    list of name-value pairs
112             * @param paramTypes list of parameter types, or null if this information is not available
113             */
114            private static void processAnnotation(final XMLConfig xc,
115                                                  Node node,
116                                                  List<AAnnotationsAttributeInfo.Annotation.NameValuePair> nvpList,
117                                                  final HashMap<String, String> paramTypes) {
118                for(final AAnnotationsAttributeInfo.Annotation.NameValuePair nvp : nvpList) {
119                    Node argNode = xc.set("arg.name", nvp.getName().toString(), node, false);
120                    nvp.getValue()
121                        .execute(new AAnnotationsAttributeInfo.Annotation.ADefaultMemberValueVisitor<Object, Node>() {
122                            public Object defaultCase(AAnnotationsAttributeInfo.Annotation.AMemberValue host,
123                                                      Node argNode) {
124                                xc.set(".type", String.valueOf(host.getTag()), argNode, false);
125                                xc.set(".value", host.toString(), argNode, false);
126                                return null;
127                            }
128    
129                            public Object arrayMemberCase(AAnnotationsAttributeInfo.Annotation.ArrayMemberValue host,
130                                                          Node argNode) {
131                                xc.set(".type", String.valueOf(host.getTag()), argNode, false);
132                                if ((paramTypes != null) && (paramTypes.size() > 0)) {
133                                    final String type = paramTypes.get(nvp.getName().toString());
134                                    xc.set(".desc", type, argNode, false); // in case the array is empty, we need a type
135                                }
136                                xc.set(".value", String.valueOf(host.getEntries().length), argNode, false);
137                                for(AAnnotationsAttributeInfo.Annotation.AMemberValue mv : host.getEntries()) {
138                                    Node elNode = argNode.getOwnerDocument().createElement("element");
139                                    argNode.appendChild(elNode);
140                                    mv.execute(this, elNode);
141                                }
142                                return null;
143                            }
144    
145                            public Object annotationMemberCase(AAnnotationsAttributeInfo.Annotation.AnnotationMemberValue host,
146                                                               Node argNode) {
147                                xc.set(".type", String.valueOf(host.getTag()), argNode, false);
148                                xc.set(".value", String.valueOf(host.getAnnotation().getType()), argNode, false);
149    
150                                processAnnotation(xc, argNode, host.getAnnotation().getPairs(), null);
151                                return null;
152                            }
153                        }, argNode);
154                }
155            }
156    
157            /**
158             * Process a list of members and add the &lt;arg&gt; tags underneath the specified node.
159             *
160             * @param xc         XML configuration
161             * @param node       XML node that is going to be the parent of the &lt;arg&gt; tags
162             * @param paramNames list of parameter names
163             * @param paramTypes list of parameter types, or null if that information is not available
164             * @param valueList  list of values
165             */
166            private static void processPredicateMembers(final XMLConfig xc,
167                                                        Node node,
168                                                        List<String> paramNames,
169                                                        final HashMap<String, String> paramTypes,
170                                                        List<AAnnotationsAttributeInfo.Annotation.AMemberValue> valueList) {
171                for(int i = 0; i < paramNames.size(); ++i) {
172                    final String name = paramNames.get(i);
173                    AAnnotationsAttributeInfo.Annotation.AMemberValue value = valueList.get(i);
174                    Node argNode = xc.set("arg.name", name, node, false);
175                    value.execute(new AAnnotationsAttributeInfo.Annotation.ADefaultMemberValueVisitor<Object, Node>() {
176                        public Object defaultCase(AAnnotationsAttributeInfo.Annotation.AMemberValue host, Node argNode) {
177                            xc.set(".type", String.valueOf(host.getTag()), argNode, false);
178                            xc.set(".value", host.toString(), argNode, false);
179                            return null;
180                        }
181    
182                        public Object arrayMemberCase(AAnnotationsAttributeInfo.Annotation.ArrayMemberValue host,
183                                                      Node argNode) {
184                            xc.set(".type", String.valueOf(host.getTag()), argNode, false);
185                            if ((paramTypes != null) && (paramTypes.size() > 0)) {
186                                final String type = paramTypes.get(name);
187                                xc.set(".desc", type, argNode, false); // in case the array is empty, we need a type
188                            }
189                            xc.set(".value", String.valueOf(host.getEntries().length), argNode, false);
190                            for(AAnnotationsAttributeInfo.Annotation.AMemberValue mv : host.getEntries()) {
191                                Node elNode = argNode.getOwnerDocument().createElement("element");
192                                argNode.appendChild(elNode);
193                                mv.execute(this, elNode);
194                            }
195                            return null;
196                        }
197    
198                        public Object annotationMemberCase(AAnnotationsAttributeInfo.Annotation.AnnotationMemberValue host,
199                                                           Node argNode) {
200                            xc.set(".type", String.valueOf(host.getTag()), argNode, false);
201                            xc.set(".value", String.valueOf(host.getAnnotation().getType()), argNode, false);
202    
203                            processAnnotation(xc, argNode, host.getAnnotation().getPairs(), null);
204                            return null;
205                        }
206                    }, argNode);
207                }
208            }
209        }
210    
211        /**
212         * Class that maintains a map between invariants and definitions.
213         */
214        public static class ConcurrentyDefinitions extends HashMap<ThreadCheckAnnotationRecord, ThreadCheckDefinitionRecord> {
215            // nothing additional required, just to shorten the writing
216        }
217    
218        /**
219         * Definition of DEFALT_XML_CONC_DEF_PATH_PREFIX without the trailing '/'
220         */
221        private static final String CD_PREFIX_NOSLASH =
222            AThreadCheckStrategy.DEFAULT_XML_CONC_DEF_PATH_PREFIX.substring(0, AThreadCheckStrategy.DEFAULT_XML_CONC_DEF_PATH_PREFIX.length() - 1);
223    
224        /**
225         * Convert XML concurrency definitions into a map between invariants and definitions.
226         * This works for both <threadcheck> and <threadcheck:def>.
227         * @param xc new XML concurrency definitions
228         * @return the map between invariants and definitions
229         */
230        public static ConcurrentyDefinitions convertXMLToConcDefs(XMLConfig xc) {
231            return joinXMLConcDefs(xc, new ConcurrentyDefinitions());
232        }
233    
234        /**
235         * Join the two XML concurrency definitions
236         * This works for both <threadcheck> and <threadcheck:def>.
237         * @param concDef1 first XML file
238         * @param concDef2 second XML file
239         * @return joined XML concurrency definitions
240         */
241        public static XMLConfig joinXMLConcDefs(XMLConfig concDef1, XMLConfig concDef2) {
242            ConcurrentyDefinitions defsAccum = convertXMLToConcDefs(concDef1);
243            defsAccum = joinXMLConcDefs(concDef2, defsAccum);
244            return convertConcDefsToConcDefBasedXML(defsAccum);
245        }
246    
247        /**
248         * Convert the map between invariants and definitions back to XML concurrency definitions (<threadcheck:def>).
249         * @param defsAccum accumulated map
250         * @return XML concurrency definition configuration (<threadcheck:def>)
251         */
252        public static XMLConfig convertConcDefsToConcDefBasedXML(ConcurrentyDefinitions defsAccum) {
253            XMLConfig xc = createEmptyXMLConcDefs();
254            for(ThreadCheckDefinitionRecord cd: defsAccum.values()) {
255                Node defNode = xc.createNode(CD_PREFIX_NOSLASH, null, false);
256                Node invNode = xc.createNode("invariant", defNode);
257                AnnotationToXML.processAnnotations(xc, invNode, cd.getInvariant());
258                if (cd.classNames.size()>0) {
259                    for(String s: cd.classNames) {
260                        Node classNode = xc.createNode("class", defNode, false);
261                        xc.set(".name", s, classNode, true);
262                    }
263                }
264                if (cd.methodClassAndSigs.size()>0) {
265                    for(String s: cd.methodClassAndSigs.keySet()) {
266                        int sepPos = s.indexOf(AThreadCheckStrategy.CLASS_SIG_SEPARATOR_STRING);
267                        if (sepPos<0) {
268                            throw new ThreadCheckException("Method description of <class>::<signature> was incorrect");
269                        }
270                        String className = s.substring(0, sepPos);
271                        String sig = s.substring(sepPos + AThreadCheckStrategy.CLASS_SIG_SEPARATOR_STRING.length());
272                        Node methodNode = xc.createNode("method", defNode, false);
273                        xc.set(".name", className, methodNode, true);
274                        xc.set(".sig", sig, methodNode, true);
275                        if (cd.methodClassAndSigs.get(s)) {
276                            xc.set(".suppress", "true", methodNode, true);
277                        }
278                    }
279                }
280            }
281            return xc;
282        }
283    
284        /**
285         * Convert the map between invariants and definitions back to a class-based XML configuration (<threadcheck>).
286         * @param defsAccum accumulated map
287         * @return class-based XML configuration (<threadcheck>)
288         */
289        public static XMLConfig convertConcDefsToClassBasedXML(ConcurrentyDefinitions defsAccum) {
290            XMLConfig xc = new XMLConfig(new StringReader("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
291                                                          "<concutest>\n"+
292                                                          "  <threadcheck>"+
293                                                          "  </threadcheck>"+
294                                                          "</concutest>\n"));
295    
296            List<Node> roots = xc.getNodes(AThreadCheckStrategy.DEFAULT_XML_PATH_PREFIX);
297            if (roots.size()!=1) {
298                throw new ThreadCheckException("The output XML file must contain exactly one node with the path "+
299                                               AThreadCheckStrategy.DEFAULT_XML_PATH_PREFIX+", found "+roots.size());
300            }
301            Node tcRoot = roots.get(0);
302            for(ThreadCheckDefinitionRecord cd: defsAccum.values()) {
303                if (cd.classNames.size()>0) {
304                    for(String s: cd.classNames) {
305                        // convert '$' from inner classes to '-' because XML does not allow '$' inside node names
306                        String classPath = s.replace('.', '/').replace('$', '-')+"/class";
307                        List<Node> cNodes = xc.getNodes(classPath, tcRoot);
308                        if (cNodes.size()>1) {
309                            throw new ThreadCheckException("The output XML file must contain exactly one node with the path "+
310                                                           AThreadCheckStrategy.DEFAULT_XML_PATH_PREFIX+classPath+
311                                                           ", found "+cNodes.size());
312                        }
313                        Node classNode;
314                        if (cNodes.size()==1) {
315                            // already exists, reuse it
316                            classNode = cNodes.get(0);
317                        }
318                        else {
319                            // create a new one
320                            classNode = xc.createNode(classPath, tcRoot);
321                        }
322                        // add the class invariant
323                        XMLAnnotUtils.AnnotationToXML.processAnnotations(xc, classNode, cd.getInvariant());
324                    }
325                }
326                if (cd.methodClassAndSigs.size()>0) {
327                    for(String s: cd.methodClassAndSigs.keySet()) {
328                        int sepPos = s.indexOf(AThreadCheckStrategy.CLASS_SIG_SEPARATOR_STRING);
329                        if (sepPos<0) {
330                            throw new ThreadCheckException("Method description of <class>::<signature> was incorrect");
331                        }
332                        // convert '$' from inner classes to '-' because XML does not allow '$' inside node names
333                        String classPath = s.substring(0, sepPos).replace('.', '/').replace('$', '-')+"/class";
334                        String sig = s.substring(sepPos + AThreadCheckStrategy.CLASS_SIG_SEPARATOR_STRING.length());
335                        // find or create the class node
336                        List<Node> cNodes = xc.getNodes(classPath, tcRoot);
337                        if (cNodes.size()>1) {
338                            throw new ThreadCheckException("The output XML file must contain exactly one node with the path "+
339                                                           AThreadCheckStrategy.DEFAULT_XML_PATH_PREFIX+classPath+
340                                                           ", found "+cNodes.size());
341                        }
342                        Node classNode;
343                        if (cNodes.size()==1) {
344                            // already exists, reuse it
345                            classNode = cNodes.get(0);
346                        }
347                        else {
348                            // create a new one
349                            classNode = xc.createNode(classPath, tcRoot);
350                        }
351                        // find or create the method node
352                        List<Node> mNodes = xc.getNodes("method", classNode);
353                        Node methodNode = null;
354                        for(Node mn: mNodes) {
355                            try {
356                                List<String> sigAttrs = xc.getMultiple(".sig", mn);
357                                if (sigAttrs.size()!=1) {
358                                    throw new ThreadCheckException("The output XML file must contain a sig attribute for the path "+
359                                                                   AThreadCheckStrategy.DEFAULT_XML_PATH_PREFIX+classPath+"/method");
360                                }
361                                if (sig.equals(sigAttrs.get(0))) {
362                                    methodNode = mn;
363                                    break;
364                                }
365                            }
366                            catch(XMLConfig.XMLConfigException e) {
367                                if (!e.equals(new XMLConfig.XMLConfigException("Node method has no attribute with name sig"))) {
368                                    throw new ThreadCheckException("The output XML file must contain a sig attribute for the path "+
369                                                                   AThreadCheckStrategy.DEFAULT_XML_PATH_PREFIX+classPath+"/method");
370                                }
371                            }
372                        }
373                        if (methodNode==null) {
374                            // create a new one
375                            methodNode = xc.createNode("method", classNode, false);
376                            xc.set(".sig", sig, methodNode, true);
377                            if (cd.methodClassAndSigs.get(s)) {
378                                xc.set(".suppress", "true", methodNode, true);
379                            }
380                        }
381    
382                        // add the method invariant
383                        XMLAnnotUtils.AnnotationToXML.processAnnotations(xc, methodNode, cd.getInvariant());
384                    }
385                }
386            }
387            return xc;
388        }
389    
390        /**
391         * Add the XML concurrency definitions in xc to the ones already in the accumulator.
392         * This works for both <threadcheck> and <threadcheck:def>.
393         * @param xc new XML concurrency definitions
394         * @param defsAccum accumulator of all the invariants and definitions
395         * @return the accumulator, i.e. defsAccum
396         */
397        public static ConcurrentyDefinitions joinXMLConcDefs(XMLConfig xc, ConcurrentyDefinitions defsAccum) {
398            AAddThreadCheckStrategy dummy = createDummy();
399            // go through the concurrency definitions
400            List<ThreadCheckDefinitionRecord> cds = dummy.extractXMLConcDef(xc);
401            for(ThreadCheckDefinitionRecord cd : cds) {
402                // if we should join definitions with equal invariants, we need to check the map first
403                ThreadCheckDefinitionRecord old = defsAccum.get(cd.getInvariant());
404                if (old != null) {
405                    // invariant already exists, join
406                    for(String s : old.classNames) {
407                        cd.addClass(s);
408                    }
409                    for(String s : old.methodClassAndSigs.keySet()) {
410                        int sepPos = s.indexOf(AThreadCheckStrategy.CLASS_SIG_SEPARATOR_STRING);
411                        if (sepPos < 0) {
412                            throw new ThreadCheckException("Method description of <class>::<signature> was incorrect");
413                        }
414                        cd.addMethod(s.substring(0, sepPos),
415                                     s.substring(sepPos + AThreadCheckStrategy.CLASS_SIG_SEPARATOR_STRING.length()),
416                                     old.methodClassAndSigs.get(s));
417                    }
418                }
419                defsAccum.put(cd.getInvariant(), cd);
420            }
421    
422            return defsAccum;
423        }
424    
425        /**
426         * Convert an XML thread checker configuration to a class-based representation (<threadcheck>).
427         * The input XML configuration can be mixed between <threadcheck> and <threadcheck:def>.
428         * @param xc input XML thread checker configuration
429         * @return class-based XML thread checker configuration (<threadcheck>)
430         */
431        public static XMLConfig convertXMLToClassBasedXML(XMLConfig xc) {
432            ConcurrentyDefinitions defsAccum = convertXMLToConcDefs(xc);
433            return convertConcDefsToClassBasedXML(defsAccum);
434        }
435    
436        /**
437         * Convert an XML thread checker configuration to an XML concurrency definition configuration (<threadcheck:def>).
438         * The input XML configuration can be mixed between <threadcheck> and <threadcheck:def>.
439         * @param xc input XML thread checker configuration
440         * @return XML concurrency definition configuration (<threadcheck:def>) 
441         */
442        public static XMLConfig convertXMLToConcDefXML(XMLConfig xc) {
443            ConcurrentyDefinitions defsAccum = convertXMLToConcDefs(xc);
444            return convertConcDefsToConcDefBasedXML(defsAccum);
445        }
446    
447        /**
448         * Creates enough of a dummy AAddThreadCheckStrategy  to let us do our work.
449         * @return dummy AAddThreadCheckStrategy with limited capabilities
450         */
451        public static AAddThreadCheckStrategy createDummy() {
452            boolean debug = Debug.out.isDebug();
453            Debug.out.setDebug(false);
454            List<String> parameters = new ArrayList<String>();
455            SharedData sharedData = new SharedData(parameters);
456            Debug.out.setDebug(debug);
457            return new AAddThreadCheckStrategy(sharedData,
458                                               new AAddThreadCheckStrategy.SharedAddData(parameters, sharedData)) {
459                public void instrument(ClassFile cf) {
460                    // no, I'm not doing that here
461                }
462                public void done() {
463                    // nothing to do
464                }
465            };
466        }
467    
468        /**
469         * Create an empty set of XML concurrency definitions.
470         * @return empty set of XML concurrency definitions
471         */
472        public static XMLConfig createEmptyXMLConcDefs() {
473            return new XMLConfig(new StringReader("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
474                                                  "<concutest>\n"+
475                                                  "</concutest>\n"));
476        }
477    
478        /**
479         * Create an empty set of XML annotations.
480         * @return empty set of XML annotations
481         */
482        public static XMLConfig createEmptyXMLAnnotaations() {
483            return new XMLConfig(new StringReader("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
484                                                  "<concutest>\n"+
485                                                  "  <threadcheck>"+
486                                                  "  </threadcheck>"+
487                                                  "</concutest>\n"));
488        }
489    
490        /**
491         * Utilities for working with class-based thread checker definitions (<threadcheck>).
492         */
493        public static class ClassBased {
494            /**
495             * Definition of DEFALT_XML_PATH_PREFIX without the trailing '/'
496             */
497            private static final String PATH_PREFIX =
498                AThreadCheckStrategy.DEFAULT_XML_PATH_PREFIX.substring(0, AThreadCheckStrategy.DEFAULT_XML_PATH_PREFIX.length() - 1);
499    
500            /**
501             * Process a node.
502             * @param xc the XML configuration
503             * @param classLambda the lambda to be executed when a class has been processed completely
504             * @param processClassPred a predicate that determines whether a class should be processed, based on the class name
505             * @param exList a list of exceptions generated while processing the node
506             */
507            public static void processNode(XMLConfig xc,
508                                           ILambda.Ternary<
509                                               Object,
510                                               String,
511                                               ThreadCheckAnnotationRecord,
512                                               HashMap<String,ThreadCheckAnnotationRecord>> classLambda,
513                                           IPredicate<String> processClassPred,
514                                           List<ThreadCheckException> exList) {
515                List<Node> nodes = xc.getNodes(PATH_PREFIX+"/*");
516                if (nodes!=null) {
517                    for(Node n: nodes) {
518                        processNode(xc, classLambda, n, "", processClassPred, exList);
519                    }
520                }
521            }
522    
523            /**
524             * Process a node.
525             * @param xc the XML configuration
526             * @param classLambda the lambda to be executed when a class has been processed completely
527             * @param n the node
528             * @param path the path, not including the node itself
529             * @param processClassPred a predicate that determines whether a class should be processed, based on the class name
530             * @param exList a list of exceptions generated while processing the node
531             */
532            private static void processNode(XMLConfig xc,
533                                           ILambda.Ternary<
534                                               Object,
535                                               String,
536                                               ThreadCheckAnnotationRecord,
537                                               HashMap<String,ThreadCheckAnnotationRecord>> classLambda,
538                                           Node n,
539                                           String path,
540                                           IPredicate<String> processClassPred,
541                                           List<ThreadCheckException> exList) {
542                final AAddThreadCheckStrategy dummy = createDummy();
543                final String className = (path+"/"+n.getNodeName()).replace('/','.').replace('-','$').substring(1);
544                final List<Node> allNodes = xc.getNodes(PATH_PREFIX+path+"/"+n.getNodeName()+"/*");
545                for(Node an: allNodes) {
546                    if (!an.getNodeName().equals("class")) {
547                        // recur
548                        processNode(xc, classLambda, an, path+"/"+n.getNodeName(), processClassPred, exList);
549                    }
550                    else {
551                        final List<Node> classNodes = xc.getNodes(PATH_PREFIX+path+"/"+n.getNodeName()+"/class");
552                        if (classNodes.size()>1) {
553                            exList.add(new ThreadCheckException("Improper format, "+path+"/"+n.getNodeName()+" has multiple <class> tags"));
554                            continue;
555                        }
556                        for(Node cn: classNodes) {
557                            processClassNode(xc, classLambda, cn, path+"/"+n.getNodeName(), className, processClassPred, exList, dummy);
558                        }
559                    }
560                }
561            }
562    
563            /**
564             * Process a &lt;class&gt; node
565             * @param xc the XML configuration
566             * @param classLambda the lambda to be executed when a class has been processed completely
567             * @param n the class node
568             * @param path the path, not including the class node itself
569             * @param className name of the class
570             * @param processClassPred a predicate that determines whether a class should be processed, based on the class name
571             * @param exList a list of exceptions generated while processing the node
572             * @param dummy dummy AAddThreadCheckStrategy
573             */
574            private static void processClassNode(XMLConfig xc,
575                                                 ILambda.Ternary<
576                                                     Object,
577                                                     String,
578                                                     ThreadCheckAnnotationRecord,
579                                                     HashMap<String,ThreadCheckAnnotationRecord>> classLambda,
580                                                 Node n,
581                                                 String path,
582                                                 String className,
583                                                 IPredicate<String> processClassPred,
584                                                 List<ThreadCheckException> exList,
585                                                 AAddThreadCheckStrategy dummy) {
586                if (!processClassPred.apply(className)) {
587                    return;
588                }
589                final ThreadCheckAnnotationRecord classAnnots = dummy.extractXMLAnnotations(n);
590                // key = method signature
591                final HashMap<String,ThreadCheckAnnotationRecord> methodAnnots = new HashMap<String,ThreadCheckAnnotationRecord>();
592                final List<Node> methodNodes = xc.getNodes(PATH_PREFIX+path+"/"+n.getNodeName()+"/method");
593                for(Node mn: methodNodes) {
594                    final Node sigAttr = mn.getAttributes().getNamedItem("sig");
595                    if (sigAttr==null) {
596                        exList.add(new ThreadCheckException("Improper format, "+path+"/"+n.getNodeName()+"/class has a <method> tag without sig attribute"));
597                        continue;
598                    }
599                    final String sig = sigAttr.getNodeValue();
600                    ThreadCheckAnnotationRecord tcar = dummy.extractXMLAnnotations(mn);
601                    final Node suppressAttr = mn.getAttributes().getNamedItem("suppress");
602                    if ((suppressAttr!=null) && (suppressAttr.getNodeValue().equalsIgnoreCase("true"))) {
603                        tcar.suppressSubtypingWarning = true;
604                    }
605                    methodAnnots.put(sig, tcar);
606                }
607                if ((!classAnnots.empty()) || (methodAnnots.size()>0)) {
608                    classLambda.apply(className,classAnnots,methodAnnots);
609                }
610            }
611        }
612    }