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 <predicate> or <combine> 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 <predicate> or <combine> 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 <arg> 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 <arg> 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 <arg> 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 <arg> 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 <class> 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 }