001    package edu.rice.cs.cunit.threadCheck;
002    
003    import edu.rice.cs.cunit.classFile.ClassFileTools;
004    import edu.rice.cs.cunit.instrumentors.DoNotInstrument;
005    import edu.rice.cs.cunit.util.Pair;
006    
007    import java.awt.*;
008    import java.io.*;
009    import java.lang.annotation.Annotation;
010    import java.lang.reflect.InvocationTargetException;
011    import java.lang.reflect.Method;
012    import java.lang.reflect.Modifier;
013    import java.util.*;
014    import java.util.regex.PatternSyntaxException;
015    
016    /**
017     * Class that checks whether threads may execute certain classes at runtime..
018     *
019     * @author Mathias Ricken
020     */
021    @DoNotInstrument
022    public class ThreadCheck {
023        public static class Violation implements Serializable {
024            public static final String LF = System.getProperty("line.separator");
025    
026            public static enum TYPE {
027                NOTRUNBY_NAME,
028                NOTRUNBY_GROUP,
029                NOTRUNBY_ID,
030                ONLYRUNBY,
031                PREDICATE,
032                REFLECTION_PREDICATE
033            }
034    
035            // offending thread information
036            public final TYPE type;
037    
038            public final String threadName;
039    
040            public final String threadGroupName;
041    
042            public final long threadId;
043    
044            public final StackTraceElement[] stackTrace;
045    
046            public final long checkCount;
047    
048            public final long violationCount;
049    
050            public Violation(Violation.TYPE type,
051                             String threadName,
052                             String threadGroupName,
053                             long threadId,
054                             StackTraceElement[] ste) {
055                this.type = type;
056                this.threadName = threadName;
057                this.threadGroupName = threadGroupName;
058                this.threadId = threadId;
059                if (ste.length>2) {
060                    this.stackTrace = new StackTraceElement[ste.length-2];
061                    System.arraycopy(ste,2,this.stackTrace,0,ste.length-2);
062                }
063                else {
064                    this.stackTrace = new StackTraceElement[0];
065                }
066                this.checkCount = _checkCount;
067                this.violationCount = _violationCount;
068            }
069    
070            public boolean equals(Object o) {
071                if (this == o) {
072                    return true;
073                }
074                if (o == null || getClass() != o.getClass()) {
075                    return false;
076                }
077    
078                Violation violation = (Violation)o;
079    
080                if (threadId != violation.threadId) {
081                    return false;
082                }
083                if (!Arrays.equals(stackTrace, violation.stackTrace)) {
084                    return false;
085                }
086                if (threadGroupName != null ? !threadGroupName.equals(violation.threadGroupName) :
087                    violation.threadGroupName != null) {
088                    return false;
089                }
090                if (threadName != null ? !threadName.equals(violation.threadName) : violation.threadName != null) {
091                    return false;
092                }
093                if (type != violation.type) {
094                    return false;
095                }
096    
097                return true;
098            }
099    
100            public int hashCode() {
101                int result;
102                result = type.hashCode();
103                result = 31 * result + (threadName != null ? threadName.hashCode() : 0);
104                result = 31 * result + (threadGroupName != null ? threadGroupName.hashCode() : 0);
105                result = 31 * result + (int)(threadId ^ (threadId >>> 32));
106                result = 31 * result + Arrays.hashCode(stackTrace);
107                return result;
108            }
109        }
110    
111        public static class NotRunByThreadNameViolation extends Violation {
112            public final String disallowedThreadName;
113    
114            public NotRunByThreadNameViolation(String threadName,
115                                               String threadGroupName,
116                                               long threadId,
117                                               String disallowedThreadName,
118                                               StackTraceElement[] ste) {
119                super(TYPE.NOTRUNBY_NAME, threadName, threadGroupName, threadId, ste);
120                this.disallowedThreadName = disallowedThreadName;
121            }
122    
123            /**
124             * Returns a string representation of the object.
125             *
126             * @return string representation
127             */
128            public String toString() {
129                StringBuilder sb = new StringBuilder();
130                sb.append("Thread Name Violation: NotRunBy (");
131                sb.append(checkCount);
132                sb.append(" ");
133                sb.append(form("check", checkCount));
134                sb.append(", ");
135                sb.append(violationCount);
136                sb.append(" ");
137                sb.append(form("violation", violationCount));
138                sb.append(")");
139                sb.append(LF);
140                sb.append("\tCurrent thread '");
141                sb.append(threadName);
142                sb.append("', id ");
143                sb.append(threadId);
144                sb.append(", group '");
145                sb.append(threadGroupName);
146                sb.append("'");
147                sb.append(LF);
148                sb.append("\tMatched disallowed name pattern '");
149                sb.append(disallowedThreadName);
150                sb.append("'");
151                for(int i = 0; i < stackTrace.length; ++i) {
152                    sb.append(LF);
153                    sb.append("\tat ");
154                    sb.append(stackTrace[i].getClassName());
155                    sb.append(".");
156                    sb.append(stackTrace[i].getMethodName());
157                    sb.append(" (");
158                    sb.append(stackTrace[i].getFileName());
159                    sb.append(":");
160                    sb.append(stackTrace[i].getLineNumber());
161                    sb.append(")");
162                }
163    
164                return sb.toString();
165            }
166    
167    
168            public boolean equals(Object o) {
169                if (this == o) {
170                    return true;
171                }
172                if (o == null || getClass() != o.getClass()) {
173                    return false;
174                }
175                if (!super.equals(o)) {
176                    return false;
177                }
178    
179                NotRunByThreadNameViolation that = (NotRunByThreadNameViolation)o;
180    
181                if (!disallowedThreadName.equals(that.disallowedThreadName)) {
182                    return false;
183                }
184    
185                return true;
186            }
187    
188            public int hashCode() {
189                int result = super.hashCode();
190                result = 31 * result + disallowedThreadName.hashCode();
191                return result;
192            }
193        }
194    
195        public static class NotRunByThreadGroupViolation extends Violation {
196            public final String disallowedThreadGroupName;
197    
198            public NotRunByThreadGroupViolation(String threadName,
199                                                String threadGroupName,
200                                                long threadId,
201                                                StackTraceElement[] ste,
202                                                String disallowedThreadGroupName) {
203                super(TYPE.NOTRUNBY_GROUP, threadName, threadGroupName, threadId, ste);
204                this.disallowedThreadGroupName = disallowedThreadGroupName;
205            }
206    
207            /**
208             * Returns a string representation of the object.
209             *
210             * @return string representation
211             */
212            public String toString() {
213                StringBuilder sb = new StringBuilder();
214                sb.append("Thread Group Name Violation: NotRunBy (");
215                sb.append(checkCount);
216                sb.append(" ");
217                sb.append(form("check", checkCount));
218                sb.append(", ");
219                sb.append(violationCount);
220                sb.append(" ");
221                sb.append(form("violation", violationCount));
222                sb.append(")");
223                sb.append(LF);
224                sb.append("\tCurrent thread '");
225                sb.append(threadName);
226                sb.append("', id ");
227                sb.append(threadId);
228                sb.append(", group '");
229                sb.append(threadGroupName);
230                sb.append("'");
231                sb.append(LF);
232                sb.append("\tMatched disallowed group name pattern '");
233                sb.append(disallowedThreadGroupName);
234                sb.append("'");
235                for(int i = 0; i < stackTrace.length; ++i) {
236                    sb.append(LF);
237                    sb.append("\tat ");
238                    sb.append(stackTrace[i].getClassName());
239                    sb.append(".");
240                    sb.append(stackTrace[i].getMethodName());
241                    sb.append(" (");
242                    sb.append(stackTrace[i].getFileName());
243                    sb.append(":");
244                    sb.append(stackTrace[i].getLineNumber());
245                    sb.append(")");
246                }
247    
248                return sb.toString();
249            }
250    
251            public boolean equals(Object o) {
252                if (this == o) {
253                    return true;
254                }
255                if (o == null || getClass() != o.getClass()) {
256                    return false;
257                }
258                if (!super.equals(o)) {
259                    return false;
260                }
261    
262                NotRunByThreadGroupViolation that = (NotRunByThreadGroupViolation)o;
263    
264                if (!disallowedThreadGroupName.equals(that.disallowedThreadGroupName)) {
265                    return false;
266                }
267    
268                return true;
269            }
270    
271            public int hashCode() {
272                int result = super.hashCode();
273                result = 31 * result + disallowedThreadGroupName.hashCode();
274                return result;
275            }
276        }
277    
278        public static class NotRunByIdViolation extends Violation {
279            public final long disallowedId;
280    
281            public NotRunByIdViolation(String threadName,
282                                       String threadGroupName,
283                                       long threadId,
284                                       StackTraceElement[] ste,
285                                       long disallowedId) {
286                super(TYPE.NOTRUNBY_GROUP, threadName, threadGroupName, threadId, ste);
287                this.disallowedId = disallowedId;
288            }
289    
290            /**
291             * Returns a string representation of the object.
292             *
293             * @return string representation
294             */
295            public String toString() {
296                StringBuilder sb = new StringBuilder();
297                sb.append("Thread Id Violation: NotRunBy (");
298                sb.append(checkCount);
299                sb.append(" ");
300                sb.append(form("check", checkCount));
301                sb.append(", ");
302                sb.append(violationCount);
303                sb.append(" ");
304                sb.append(form("violation", violationCount));
305                sb.append(")");
306                sb.append(LF);
307                sb.append("\tCurrent thread '");
308                sb.append(threadName);
309                sb.append("', id ");
310                sb.append(threadId);
311                sb.append(", group '");
312                sb.append(threadGroupName);
313                sb.append("'");
314                sb.append(LF);
315                sb.append("\tMatched disallowed id ");
316                sb.append(disallowedId);
317                for(int i = 0; i < stackTrace.length; ++i) {
318                    sb.append(LF);
319                    sb.append("\tat ");
320                    sb.append(stackTrace[i].getClassName());
321                    sb.append(".");
322                    sb.append(stackTrace[i].getMethodName());
323                    sb.append(" (");
324                    sb.append(stackTrace[i].getFileName());
325                    sb.append(":");
326                    sb.append(stackTrace[i].getLineNumber());
327                    sb.append(")");
328                }
329    
330                return sb.toString();
331            }
332    
333            public boolean equals(Object o) {
334                if (this == o) {
335                    return true;
336                }
337                if (o == null || getClass() != o.getClass()) {
338                    return false;
339                }
340                if (!super.equals(o)) {
341                    return false;
342                }
343    
344                NotRunByIdViolation that = (NotRunByIdViolation)o;
345    
346                if (disallowedId != that.disallowedId) {
347                    return false;
348                }
349    
350                return true;
351            }
352    
353            public int hashCode() {
354                int result = super.hashCode();
355                result = 31 * result + (int)(disallowedId ^ (disallowedId >>> 32));
356                return result;
357            }
358        }
359    
360        public static class OnlyRunByViolation extends Violation {
361            public final boolean allowedEventThread;
362    
363            public final HashSet<String> allowedThreadNames;
364    
365            public final HashSet<String> allowedGroupNames;
366    
367            public final HashSet<Long> allowedThreadIds;
368    
369            public OnlyRunByViolation(String threadName,
370                                      String threadGroupName,
371                                      long threadId,
372                                      StackTraceElement[] ste,
373                                      boolean allowedEventThread,
374                                      HashSet<String> allowedThreadNames,
375                                      HashSet<String> allowedGroupNames,
376                                      HashSet<Long> allowedThreadIds) {
377                super(TYPE.NOTRUNBY_GROUP, threadName, threadGroupName, threadId, ste);
378                this.allowedEventThread = allowedEventThread;
379                this.allowedThreadNames = allowedThreadNames;
380                this.allowedGroupNames = allowedGroupNames;
381                this.allowedThreadIds = allowedThreadIds;
382            }
383    
384            /**
385             * Returns a string representation of the object.
386             *
387             * @return string representation
388             */
389            public String toString() {
390                StringBuilder sb = new StringBuilder();
391                sb.append("Thread Violation: OnlyRunBy (");
392                sb.append(checkCount);
393                sb.append(" ");
394                sb.append(form("check", checkCount));
395                sb.append(", ");
396                sb.append(violationCount);
397                sb.append(" ");
398                sb.append(form("violation", violationCount));
399                sb.append(")");
400                sb.append(LF);
401                sb.append("\tCurrent thread '");
402                sb.append(threadName);
403                sb.append("', id ");
404                sb.append(threadId);
405                sb.append(", group '");
406                sb.append(threadGroupName);
407                sb.append("' did not match");
408    
409                if (allowedEventThread) {
410                    sb.append(LF);
411                    sb.append("\tthe event thread");
412                }
413                if ((allowedThreadNames != null) && (allowedThreadNames.size() > 0)) {
414                    sb.append(LF);
415                    sb.append("\tany of the allowed thread name patterns");
416                    for(String regex : allowedThreadNames) {
417                        sb.append(LF);
418                        sb.append("\t\t'");
419                        sb.append(regex);
420                        sb.append("'");
421                    }
422                }
423                if ((allowedThreadIds != null) && (allowedThreadIds.size() > 0)) {
424                    sb.append(LF);
425                    sb.append("\tany of the allowed ids");
426                    for(long id : allowedThreadIds) {
427                        sb.append(LF);
428                        sb.append("\t\t");
429                        sb.append(id);
430                    }
431                }
432                if ((allowedGroupNames != null) && (allowedGroupNames.size() > 0)) {
433                    sb.append(LF);
434                    sb.append("\tany of the allowed group name patterns");
435                    for(String regex : allowedGroupNames) {
436                        sb.append(LF);
437                        sb.append("\t\t'");
438                        sb.append(regex);
439                        sb.append("'");
440                    }
441                }
442                for(int i = 0; i < stackTrace.length; ++i) {
443                    sb.append(LF);
444                    sb.append("\tat ");
445                    sb.append(stackTrace[i].getClassName());
446                    sb.append(".");
447                    sb.append(stackTrace[i].getMethodName());
448                    sb.append(" (");
449                    sb.append(stackTrace[i].getFileName());
450                    sb.append(":");
451                    sb.append(stackTrace[i].getLineNumber());
452                    sb.append(")");
453                }
454    
455                return sb.toString();
456            }
457    
458            public boolean equals(Object o) {
459                if (this == o) {
460                    return true;
461                }
462                if (o == null || getClass() != o.getClass()) {
463                    return false;
464                }
465                if (!super.equals(o)) {
466                    return false;
467                }
468    
469                OnlyRunByViolation that = (OnlyRunByViolation)o;
470    
471                if (allowedEventThread != that.allowedEventThread) {
472                    return false;
473                }
474                if (allowedGroupNames != null ? !allowedGroupNames.equals(that.allowedGroupNames) :
475                    that.allowedGroupNames != null) {
476                    return false;
477                }
478                if (allowedThreadIds != null ? !allowedThreadIds.equals(that.allowedThreadIds) :
479                    that.allowedThreadIds != null) {
480                    return false;
481                }
482                if (allowedThreadNames != null ? !allowedThreadNames.equals(that.allowedThreadNames) :
483                    that.allowedThreadNames != null) {
484                    return false;
485                }
486    
487                return true;
488            }
489    
490            public int hashCode() {
491                int result = super.hashCode();
492                result = 31 * result + (allowedEventThread ? 1 : 0);
493                result = 31 * result + (allowedThreadNames != null ? allowedThreadNames.hashCode() : 0);
494                result = 31 * result + (allowedGroupNames != null ? allowedGroupNames.hashCode() : 0);
495                result = 31 * result + (allowedThreadIds != null ? allowedThreadIds.hashCode() : 0);
496                return result;
497            }
498        }
499    
500        public static class PredicateViolation extends Violation {
501            public final String predicateClass;
502    
503            public PredicateViolation(String threadName,
504                                      String threadGroupName,
505                                      long threadId,
506                                      StackTraceElement[] ste,
507                                      String predicateClass) {
508                super(TYPE.PREDICATE, threadName, threadGroupName, threadId, ste);
509                this.predicateClass = predicateClass;
510            }
511    
512            /**
513             * Returns a string representation of the object.
514             *
515             * @return string representation
516             */
517            public String toString() {
518                StringBuilder sb = new StringBuilder();
519                sb.append("Thread Predicate Violation: (");
520                sb.append(checkCount);
521                sb.append(" ");
522                sb.append(form("check", checkCount));
523                sb.append(", ");
524                sb.append(violationCount);
525                sb.append(" ");
526                sb.append(form("violation", violationCount));
527                sb.append(")");
528                sb.append(LF);
529                sb.append("\tCurrent thread '");
530                sb.append(threadName);
531                sb.append("', id ");
532                sb.append(threadId);
533                sb.append(", group '");
534                sb.append(threadGroupName);
535                sb.append("'");
536                sb.append(LF);
537                sb.append("\tViolated predicate @");
538                sb.append(predicateClass.substring(1, predicateClass.length()-1).replace('/','.'));
539                for(int i = 0; i < stackTrace.length; ++i) {
540                    sb.append(LF);
541                    sb.append("\tat ");
542                    sb.append(stackTrace[i].getClassName());
543                    sb.append(".");
544                    sb.append(stackTrace[i].getMethodName());
545                    sb.append(" (");
546                    sb.append(stackTrace[i].getFileName());
547                    sb.append(":");
548                    sb.append(stackTrace[i].getLineNumber());
549                    sb.append(")");
550                }
551    
552                return sb.toString();
553            }
554    
555            public boolean equals(Object o) {
556                if (this == o) {
557                    return true;
558                }
559                if (o == null || getClass() != o.getClass()) {
560                    return false;
561                }
562                if (!super.equals(o)) {
563                    return false;
564                }
565    
566                PredicateViolation that = (PredicateViolation)o;
567    
568                if (predicateClass != null ? !predicateClass.equals(that.predicateClass) : that.predicateClass != null) {
569                    return false;
570                }
571    
572                return true;
573            }
574    
575            public int hashCode() {
576                int result = super.hashCode();
577                result = 31 * result + (predicateClass != null ? predicateClass.hashCode() : 0);
578                return result;
579            }
580        }
581    
582        public static class ExceptionInPredicateViolation extends PredicateViolation {
583            public final Throwable t;
584    
585            public ExceptionInPredicateViolation(String threadName,
586                                                 String threadGroupName,
587                                                 long threadId,
588                                                 StackTraceElement[] ste,
589                                                 String predicateClass,
590                                                 Throwable t) {
591                super(threadName, threadGroupName, threadId, ste, predicateClass);
592                this.t = t;
593            }
594    
595            /**
596             * Returns a string representation of the object.
597             *
598             * @return string representation
599             */
600            public String toString() {
601                StringBuilder sb = new StringBuilder();
602                sb.append("Exception in Thread Predicate Violation: (");
603                sb.append(checkCount);
604                sb.append(" ");
605                sb.append(form("check", checkCount));
606                sb.append(", ");
607                sb.append(violationCount);
608                sb.append(" ");
609                sb.append(form("violation", violationCount));
610                sb.append(")");
611                sb.append(LF);
612                sb.append("\tCurrent thread '");
613                sb.append(threadName);
614                sb.append("', id ");
615                sb.append(threadId);
616                sb.append(", group '");
617                sb.append(threadGroupName);
618                sb.append("'");
619                sb.append(LF);
620                sb.append("\tViolated predicate @");
621                sb.append(predicateClass.substring(1, predicateClass.length()-1).replace('/','.'));
622                sb.append(LF);
623                sb.append("\tThrew ");
624                sb.append(t.toString());
625                for(int i = 0; i < stackTrace.length; ++i) {
626                    sb.append(LF);
627                    sb.append("\tat ");
628                    sb.append(stackTrace[i].getClassName());
629                    sb.append(".");
630                    sb.append(stackTrace[i].getMethodName());
631                    sb.append(" (");
632                    sb.append(stackTrace[i].getFileName());
633                    sb.append(":");
634                    sb.append(stackTrace[i].getLineNumber());
635                    sb.append(")");
636                }
637    
638                return sb.toString();
639            }
640    
641    
642            public boolean equals(Object o) {
643                if (this == o) {
644                    return true;
645                }
646                if (o == null || getClass() != o.getClass()) {
647                    return false;
648                }
649                if (!super.equals(o)) {
650                    return false;
651                }
652    
653                ExceptionInPredicateViolation that = (ExceptionInPredicateViolation)o;
654    
655                // Probably incorrect - comparing Object[] arrays with Arrays.equals
656                return t.equals(that.t);
657            }
658    
659            public int hashCode() {
660                int result = super.hashCode();
661                result = 31 * result + (t != null ? t.hashCode() : 0);
662                return result;
663            }
664        }
665        
666        public static class ArgumentPredicateViolation extends PredicateViolation {
667            public final Object[] args;
668    
669            public ArgumentPredicateViolation(String threadName,
670                                              String threadGroupName,
671                                              long threadId,
672                                              StackTraceElement[] ste,
673                                              String predicateClass,
674                                              Object[] args) {
675                super(threadName, threadGroupName, threadId, ste, predicateClass);
676                this.args = args;
677            }
678    
679            /**
680             * Returns a string representation of the object.
681             *
682             * @return string representation
683             */
684            public String toString() {
685                StringBuilder sb = new StringBuilder();
686                sb.append("Thread Predicate Violation: (");
687                sb.append(checkCount);
688                sb.append(" ");
689                sb.append(form("check", checkCount));
690                sb.append(", ");
691                sb.append(violationCount);
692                sb.append(" ");
693                sb.append(form("violation", violationCount));
694                sb.append(")");
695                sb.append(LF);
696                sb.append("\tCurrent thread '");
697                sb.append(threadName);
698                sb.append("', id ");
699                sb.append(threadId);
700                sb.append(", group '");
701                sb.append(threadGroupName);
702                sb.append("'");
703                sb.append(LF);
704                sb.append("\tViolated predicate @");
705                sb.append(predicateClass.substring(1, predicateClass.length()-1).replace('/','.'));
706                sb.append(LF);
707                sb.append("\tMethod arguments");
708                for(Object a: args) {
709                    sb.append(LF);
710                    sb.append("\t\t'");
711                    sb.append(a.toString());
712                    sb.append("' : ");
713                    sb.append(a.getClass().getName());
714                }
715                for(int i = 0; i < stackTrace.length; ++i) {
716                    sb.append(LF);
717                    sb.append("\tat ");
718                    sb.append(stackTrace[i].getClassName());
719                    sb.append(".");
720                    sb.append(stackTrace[i].getMethodName());
721                    sb.append(" (");
722                    sb.append(stackTrace[i].getFileName());
723                    sb.append(":");
724                    sb.append(stackTrace[i].getLineNumber());
725                    sb.append(")");
726                }
727    
728                return sb.toString();
729            }
730    
731    
732            public boolean equals(Object o) {
733                if (this == o) {
734                    return true;
735                }
736                if (o == null || getClass() != o.getClass()) {
737                    return false;
738                }
739                if (!super.equals(o)) {
740                    return false;
741                }
742    
743                ArgumentPredicateViolation that = (ArgumentPredicateViolation)o;
744    
745                // Probably incorrect - comparing Object[] arrays with Arrays.equals
746                if (!Arrays.equals(args, that.args)) {
747                    return false;
748                }
749    
750                return true;
751            }
752    
753            public int hashCode() {
754                int result = super.hashCode();
755                result = 31 * result + (args != null ? Arrays.hashCode(args) : 0);
756                return result;
757            }
758        }
759    
760        public static class ExceptionInArgumentPredicateViolation extends PredicateViolation {
761            public final Object[] args;
762            public final Throwable t;
763    
764            public ExceptionInArgumentPredicateViolation(String threadName,
765                                                         String threadGroupName,
766                                                         long threadId,
767                                                         StackTraceElement[] ste,
768                                                         String predicateClass,
769                                                         Object[] args,
770                                                         Throwable t) {
771                super(threadName, threadGroupName, threadId, ste, predicateClass);
772                this.args = args;
773                this.t = t;
774            }
775    
776            /**
777             * Returns a string representation of the object.
778             *
779             * @return string representation
780             */
781            public String toString() {
782                StringBuilder sb = new StringBuilder();
783                sb.append("Exception in Thread Predicate Violation: (");
784                sb.append(checkCount);
785                sb.append(" ");
786                sb.append(form("check", checkCount));
787                sb.append(", ");
788                sb.append(violationCount);
789                sb.append(" ");
790                sb.append(form("violation", violationCount));
791                sb.append(")");
792                sb.append(LF);
793                sb.append("\tCurrent thread '");
794                sb.append(threadName);
795                sb.append("', id ");
796                sb.append(threadId);
797                sb.append(", group '");
798                sb.append(threadGroupName);
799                sb.append("'");
800                sb.append(LF);
801                sb.append("\tViolated predicate @");
802                sb.append(predicateClass.substring(1, predicateClass.length()-1).replace('/','.'));
803                sb.append(LF);
804                sb.append("\tMethod arguments");
805                for(Object a: args) {
806                    sb.append(LF);
807                    sb.append("\t\t'");
808                    sb.append(a.toString());
809                    sb.append("' : ");
810                    sb.append(a.getClass().getName());
811                }
812                sb.append(LF);
813                sb.append("\tThrew ");
814                sb.append(t.toString());
815                for(int i = 0; i < stackTrace.length; ++i) {
816                    sb.append(LF);
817                    sb.append("\tat ");
818                    sb.append(stackTrace[i].getClassName());
819                    sb.append(".");
820                    sb.append(stackTrace[i].getMethodName());
821                    sb.append(" (");
822                    sb.append(stackTrace[i].getFileName());
823                    sb.append(":");
824                    sb.append(stackTrace[i].getLineNumber());
825                    sb.append(")");
826                }
827    
828                return sb.toString();
829            }
830    
831    
832            public boolean equals(Object o) {
833                if (this == o) {
834                    return true;
835                }
836                if (o == null || getClass() != o.getClass()) {
837                    return false;
838                }
839                if (!super.equals(o)) {
840                    return false;
841                }
842    
843                ArgumentPredicateViolation that = (ArgumentPredicateViolation)o;
844    
845                // Probably incorrect - comparing Object[] arrays with Arrays.equals
846                if (!Arrays.equals(args, that.args)) {
847                    return false;
848                }
849    
850                return true;
851            }
852    
853            public int hashCode() {
854                int result = super.hashCode();
855                result = 31 * result + (args != null ? Arrays.hashCode(args) : 0);
856                return result;
857            }
858        }
859            
860        public static class ReflectionPredicateViolation extends Violation {
861            public final String predicateString;
862            public final Object[] methodArgs;
863    
864            public ReflectionPredicateViolation(String threadName,
865                                                String threadGroupName,
866                                                long threadId,
867                                                StackTraceElement[] ste,
868                                                String predicateString,
869                                                Object[] methodArgs) {
870                super(Violation.TYPE.REFLECTION_PREDICATE, threadName, threadGroupName, threadId, ste);
871                this.predicateString = predicateString;
872                this.methodArgs = methodArgs;
873            }
874    
875            /**
876             * Returns a string representation of the object.
877             *
878             * @return string representation
879             */
880            public String toString() {
881                StringBuilder sb = new StringBuilder();
882                sb.append("Thread Reflection Predicate Violation: (");
883                sb.append(checkCount);
884                sb.append(" ");
885                sb.append(form("check", checkCount));
886                sb.append(", ");
887                sb.append(violationCount);
888                sb.append(" ");
889                sb.append(form("violation", violationCount));
890                sb.append(")");
891                sb.append(LF);
892                sb.append("\tCurrent thread '");
893                sb.append(threadName);
894                sb.append("', id ");
895                sb.append(threadId);
896                sb.append(", group '");
897                sb.append(threadGroupName);
898                sb.append("'");
899                sb.append(LF);
900                sb.append("\tViolated predicate @");
901                sb.append(predicateString);
902                if (methodArgs!=null) {
903                    sb.append(LF);
904                    sb.append("\tMethod arguments: "+methodArgs.length);
905                    for(Object a: methodArgs) {
906                        sb.append(LF);
907                        sb.append("\t\t'");
908                        sb.append(a.toString());
909                        sb.append("' : ");
910                        sb.append(a.getClass().getName());
911                    }
912                }
913                for(int i = 0; i < stackTrace.length; ++i) {
914                    sb.append(LF);
915                    sb.append("\tat ");
916                    sb.append(stackTrace[i].getClassName());
917                    sb.append(".");
918                    sb.append(stackTrace[i].getMethodName());
919                    sb.append(" (");
920                    sb.append(stackTrace[i].getFileName());
921                    sb.append(":");
922                    sb.append(stackTrace[i].getLineNumber());
923                    sb.append(")");
924                }
925    
926                return sb.toString();
927            }
928    
929            public boolean equals(Object o) {
930                if (this == o) {
931                    return true;
932                }
933                if (o == null || getClass() != o.getClass()) {
934                    return false;
935                }
936                if (!super.equals(o)) {
937                    return false;
938                }
939    
940                PredicateViolation that = (PredicateViolation)o;
941    
942                if (predicateString != null ? !predicateString.equals(that.predicateClass) : that.predicateClass != null) {
943                    return false;
944                }
945    
946                return true;
947            }
948    
949            public int hashCode() {
950                int result = super.hashCode();
951                result = 31 * result + (predicateString != null ? predicateString.hashCode() : 0);
952                return result;
953            }
954        }
955    
956        /**
957         * Name of the Java property that determines the log filename.
958         */
959        public static final String LOG_FILENAME_PROPERTY = "edu.rice.cs.cunit.threadCheck.filename";
960    
961        /**
962         * Default filename for the log, if none specified by Java property.
963         */
964        public static final String DEFAULT_LOG_FILENAME = "threadCheck.log";
965    
966        /**
967         * Default filename for the data log, if none specified by Java property.
968         */
969        public static final String DEFAULT_DAT_SUFFIX = ".dat";
970    
971        /**
972         * True if the code currently executing is due to thread checking; used to avoid infinite recursions.
973         */
974        private static volatile HashSet<Thread> _inCheckCode = new HashSet<Thread>();
975    
976        /**
977         * PrintWriter with the warning log.
978         */
979        private static volatile PrintWriter _log = null;
980    
981        /**
982         * ObjectOutputStream with the warning log as data.
983         */
984        private static volatile ObjectOutputStream _logData = null;
985    
986        /**
987         * Number of checks performed.
988         */
989        private static volatile long _checkCount = 0;
990    
991        /**
992         * Number of violationms found.
993         */
994        private static volatile long _violationCount = 0;
995    
996        /**
997         * Check if the current thread matches the regex for the thread name, and if that is the case, records a warning.
998         *
999         * @param threadName regex of a thread name that the current thread's name may NOT match
1000         */
1001        public static synchronized void checkCurrentThreadName(String threadName) {
1002            final Thread ct = Thread.currentThread();
1003            if (_inCheckCode.contains(ct)) {
1004                return;
1005            }
1006            _inCheckCode.add(ct);
1007            ++_checkCount;
1008            try {
1009                final String curName = ct.getName();
1010                final long curId = ct.getId();
1011                final String curGroupName = ct.getThreadGroup().getName();
1012                if (curName.matches(threadName)) {
1013                    ++_violationCount;
1014                    Violation v = new NotRunByThreadNameViolation(curName, curGroupName, curId, threadName,
1015                                                                  ct.getStackTrace());
1016                    writeLog(v);
1017                }
1018            }
1019            catch(PatternSyntaxException pte) {
1020                pte.printStackTrace();
1021                System.exit(1);
1022            }
1023            finally {
1024                _inCheckCode.remove(ct);
1025            }
1026        }
1027    
1028        /**
1029         * Check if the current thread matches the thread ID, and if this is true, records a warning.
1030         *
1031         * @param threadId ID number that the current thread's ID may not match
1032         */
1033        public static synchronized void checkCurrentThreadId(long threadId) {
1034            final Thread ct = Thread.currentThread();
1035            if (_inCheckCode.contains(ct)) {
1036                return;
1037            }
1038            _inCheckCode.add(ct);
1039            ++_checkCount;
1040            try {
1041                final String curName = ct.getName();
1042                final long curId = ct.getId();
1043                final String curGroupName = ct.getThreadGroup().getName();
1044                if (curId == threadId) {
1045                    ++_violationCount;
1046                    Violation v = new NotRunByIdViolation(curName, curGroupName, curId, ct.getStackTrace(), threadId);
1047                    writeLog(v);
1048                }
1049            }
1050            catch(PatternSyntaxException pte) {
1051                pte.printStackTrace();
1052                System.exit(1);
1053            }
1054            finally {
1055                _inCheckCode.remove(ct);
1056            }
1057        }
1058    
1059        /**
1060         * Check if the current thread matches the regex for the thread group name, and if this is true, records a warning.
1061         *
1062         * @param threadGroupName regex of a thread group name that the current thread's group name may NOT match
1063         */
1064        public static synchronized void checkCurrentThreadGroup(String threadGroupName) {
1065            final Thread ct = Thread.currentThread();
1066            if (_inCheckCode.contains(ct)) {
1067                return;
1068            }
1069            _inCheckCode.add(ct);
1070            ++_checkCount;
1071            try {
1072                final String curName = ct.getName();
1073                final long curId = ct.getId();
1074                final String curGroupName = ct.getThreadGroup().getName();
1075                if (curGroupName.matches(threadGroupName)) {
1076                    ++_violationCount;
1077                    Violation v = new NotRunByThreadGroupViolation(curName, curGroupName, curId, ct.getStackTrace(),
1078                                                                   threadGroupName);
1079                    writeLog(v);
1080                }
1081            }
1082            catch(PatternSyntaxException pte) {
1083                pte.printStackTrace();
1084                System.exit(1);
1085            }
1086            finally {
1087                _inCheckCode.remove(ct);
1088            }
1089        }
1090    
1091        /**
1092         * Write to the error log, and potentially initialize it first.
1093         * @param v violation to log
1094         */
1095        public static synchronized void writeLog(Violation v) {
1096            initLog();
1097            _log.println(v.toString());
1098            _log.flush();
1099            try {
1100                _logData.writeObject(v);
1101                _logData.flush();
1102            }
1103            catch(IOException e) {
1104                e.printStackTrace();
1105                System.exit(1);
1106            }
1107        }
1108    
1109        /**
1110         * Initialize the error log.
1111         */
1112        private static synchronized void initLog() {
1113            if (_log == null) {
1114                String filename = System.getProperty(LOG_FILENAME_PROPERTY);
1115                if (filename == null) {
1116                    filename = DEFAULT_LOG_FILENAME;
1117                }
1118                try {
1119                    _log = new PrintWriter(new FileOutputStream(filename, true));
1120                    _log.println("========================================");
1121                    _log.format("Log opened %04d-%02d-%02d, %02d:%02d:%02d %s (GMT%d)", Calendar.getInstance().get(
1122                        Calendar.YEAR), Calendar.getInstance().get(Calendar.MONTH), Calendar.getInstance().get(
1123                        Calendar.DAY_OF_MONTH), Calendar.getInstance().get(Calendar.HOUR_OF_DAY),
1124                                                Calendar.getInstance().get(Calendar.MINUTE), Calendar.getInstance().get(
1125                        Calendar.SECOND), Calendar.getInstance().getTimeZone().getDisplayName(Calendar.getInstance().get(
1126                        Calendar.DST_OFFSET) == 0, TimeZone.SHORT), ((Calendar.getInstance().get(Calendar.ZONE_OFFSET)
1127                                                                      + Calendar.getInstance().get(Calendar.DST_OFFSET))
1128                                                                     / 1000 / 60 / 60));
1129                    _log.println();
1130                    _log.println("----------------------------------------");
1131                    _log.flush();
1132                }
1133                catch(FileNotFoundException e) {
1134                    e.printStackTrace();
1135                    System.exit(1);
1136                }
1137    
1138                filename = filename + DEFAULT_DAT_SUFFIX;
1139                try {
1140                    _logData = new ObjectOutputStream(new FileOutputStream(filename, true));
1141                }
1142                catch(IOException e) {
1143                    e.printStackTrace();
1144                    System.exit(1);
1145                }
1146            }
1147            else {
1148                _log.println("----------------------------------------");
1149                _log.flush();
1150            }
1151        }
1152    
1153        /**
1154         * Map from thread to a list of allowed thread names for the next call of checkCurrentThread_OnlyRunBy.
1155         */
1156        private static volatile Map<Thread, HashSet<String>> _allowedNames = new HashMap<Thread, HashSet<String>>();
1157    
1158        /**
1159         * Add an allowed thread name pattern to the list.
1160         *
1161         * @param allowed regex that is allowed
1162         */
1163        public static synchronized void addAllowedName_OnlyRunBy(String allowed) {
1164            Thread ct = Thread.currentThread();
1165            if (_allowedNames.get(ct) == null) {
1166                _allowedNames.put(ct, new HashSet<String>());
1167            }
1168            _allowedNames.get(ct).add(allowed);
1169        }
1170    
1171        /**
1172         * Map from thread to a list of allowed ids for the next call of checkCurrentThread_OnlyRunBy.
1173         */
1174        private static volatile Map<Thread, HashSet<Long>> _allowedIds = new HashMap<Thread, HashSet<Long>>();
1175    
1176        /**
1177         * Add an allowed id to the list.
1178         *
1179         * @param allowed id that is allowed
1180         */
1181        public static synchronized void addAllowedId_OnlyRunBy(long allowed) {
1182            Thread ct = Thread.currentThread();
1183            if (_allowedIds.get(ct) == null) {
1184                _allowedIds.put(ct, new HashSet<Long>());
1185            }
1186            _allowedIds.get(ct).add(allowed);
1187        }
1188    
1189        /**
1190         * Map from thread to a list of allowed thread group names for the next call of checkCurrentThread_OnlyRunBy.
1191         */
1192        private static volatile Map<Thread, HashSet<String>> _allowedGroups = new HashMap<Thread, HashSet<String>>();
1193    
1194        /**
1195         * Add an allowed group name pattern to the list.
1196         *
1197         * @param allowed regex that is allowed
1198         */
1199        public static synchronized void addAllowedGroup_OnlyRunBy(String allowed) {
1200            Thread ct = Thread.currentThread();
1201            if (_allowedGroups.get(ct) == null) {
1202                _allowedGroups.put(ct, new HashSet<String>());
1203            }
1204            _allowedGroups.get(ct).add(allowed);
1205        }
1206    
1207        /**
1208         * Map from thread to a boolean indicating whether the event thread is allowed to run.
1209         */
1210        private static volatile Map<Thread, Boolean> _allowEventThread = new HashMap<Thread, Boolean>();
1211    
1212        /**
1213         * Allow the event thread at the next call of checkCurrentThread_OnlyRunBy.
1214         *
1215         * @param realized true if the component has been realized (or if the check should always be performed)
1216         */
1217        public static synchronized void setAllowedEventThread_OnlyRunBy(boolean realized) {
1218            _allowEventThread.put(Thread.currentThread(), realized);
1219        }
1220    
1221        /**
1222         * Check if the current thread's name, id or, or group matches any of items in the three _allowed* lists, and if
1223         * that is NOT the case, records a warning.
1224         */
1225        public static synchronized void checkCurrentThread_OnlyRunBy() {
1226            final Thread ct = Thread.currentThread();
1227            if (_inCheckCode.contains(ct) || ((!_allowEventThread.containsKey(ct)) && ((_allowedNames.get(ct) == null) || (
1228                _allowedNames.get(ct).size() == 0)) && ((_allowedIds.get(ct) == null) || (_allowedIds.get(ct).size() == 0))
1229                                                    && ((_allowedGroups.get(ct) == null) || (_allowedGroups.get(ct).size()
1230                                                                                             == 0)))) {
1231                return;
1232            }
1233            _inCheckCode.add(ct);
1234            ++_checkCount;
1235            try {
1236                final String curName = ct.getName();
1237                final long curId = ct.getId();
1238                final String curGroupName = ct.getThreadGroup().getName();
1239                boolean matches = false;
1240                if (_allowEventThread.containsKey(ct)) {
1241                    if (_allowEventThread.get(ct)) {
1242                        // realized == true, we care whether it's the event thread
1243                        matches = EventQueue.isDispatchThread();
1244                    }
1245                    else {
1246                        // realized == false, we don't care
1247                        matches = true;
1248                    }
1249                }
1250                if ((!matches) && (_allowedNames.get(ct) != null)) {
1251                    for(String regex : _allowedNames.get(ct)) {
1252                        try {
1253                            matches |= curName.matches(regex);
1254                            if (matches) {
1255                                break;
1256                            }
1257                        }
1258                        catch(PatternSyntaxException pte) {
1259                            pte.printStackTrace();
1260                            System.exit(1);
1261                        }
1262                    }
1263                }
1264                if ((!matches) && (_allowedIds.get(ct) != null)) {
1265                    for(long id : _allowedIds.get(ct)) {
1266                        matches |= (curId == id);
1267                        if (matches) {
1268                            break;
1269                        }
1270                    }
1271                }
1272                if ((!matches) && (_allowedGroups.get(ct) != null)) {
1273                    for(String regex : _allowedGroups.get(ct)) {
1274                        try {
1275                            matches |= curGroupName.matches(regex);
1276                            if (matches) {
1277                                break;
1278                            }
1279                        }
1280                        catch(PatternSyntaxException pte) {
1281                            pte.printStackTrace();
1282                            System.exit(1);
1283                        }
1284                    }
1285                }
1286                if (!matches) {
1287                    ++_violationCount;
1288                    Violation v = new OnlyRunByViolation(curName, curGroupName, curId, ct.getStackTrace(),
1289                                                         _allowEventThread.containsKey(ct), _allowedNames.get(ct),
1290                                                         _allowedGroups.get(ct), _allowedIds.get(ct));
1291                    writeLog(v);
1292                }
1293            }
1294            finally {
1295                if (_allowedNames.get(ct) != null) {
1296                    _allowedNames.get(ct).clear();
1297                }
1298                if (_allowedIds.get(ct) != null) {
1299                    _allowedIds.get(ct).clear();
1300                }
1301                if (_allowedGroups.get(ct) != null) {
1302                    _allowedGroups.get(ct).clear();
1303                }
1304                _allowEventThread.remove(ct);
1305                _inCheckCode.remove(ct);
1306            }
1307        }
1308    
1309        /**
1310         * Check if a predicate has failed, and if so, log a violation.
1311         * @param predicateResult result of the call to the predicate method
1312         * @param predicateClass name of the predicate (name of the annotation)
1313         */
1314        public static synchronized void checkCurrentThread_Predicate(boolean predicateResult, String predicateClass) {
1315            final Thread ct = Thread.currentThread();
1316            if (_inCheckCode.contains(ct)) {
1317                return;
1318            }
1319            _inCheckCode.add(ct);
1320            ++_checkCount;
1321            if (!predicateResult) {
1322                final String curName = ct.getName();
1323                final long curId = ct.getId();
1324                final String curGroupName = ct.getThreadGroup().getName();
1325                ++_violationCount;
1326                Violation v = new PredicateViolation(curName, curGroupName, curId, ct.getStackTrace(), predicateClass);
1327                writeLog(v);
1328            }
1329            _inCheckCode.remove(ct);
1330        }
1331    
1332        /**
1333         * Check if a predicate has failed, and if so, log a violation.
1334         * @param args method arguments
1335         * @param predicateResult result of the call to the predicate method
1336         * @param predicateClass name of the predicate (name of the annotation)
1337         */
1338        public static synchronized void checkCurrentThread_Predicate(Object[] args, boolean predicateResult, String predicateClass) {
1339            final Thread ct = Thread.currentThread();
1340            if (_inCheckCode.contains(ct)) {
1341                return;
1342            }
1343            _inCheckCode.add(ct);
1344            ++_checkCount;
1345            if (!predicateResult) {
1346                final String curName = ct.getName();
1347                final long curId = ct.getId();
1348                final String curGroupName = ct.getThreadGroup().getName();
1349                ++_violationCount;
1350                Violation v = new ArgumentPredicateViolation(curName, curGroupName, curId, ct.getStackTrace(), predicateClass, args);
1351                writeLog(v);
1352            }
1353            _inCheckCode.remove(ct);
1354        }
1355    
1356        /**
1357         * Log an exception in a predicate.
1358         * @param t exception thrown
1359         * @param predicateClass name of the predicate (name of the annotation)
1360         */
1361        public static synchronized void checkCurrentThread_PredicateException(Throwable t, String predicateClass) {
1362            final Thread ct = Thread.currentThread();
1363            if (_inCheckCode.contains(ct)) {
1364                return;
1365            }
1366            _inCheckCode.add(ct);
1367            ++_checkCount;
1368            final String curName = ct.getName();
1369            final long curId = ct.getId();
1370            final String curGroupName = ct.getThreadGroup().getName();
1371            ++_violationCount;
1372            Violation v = new ExceptionInPredicateViolation(curName, curGroupName, curId, ct.getStackTrace(), predicateClass, t);
1373            writeLog(v);
1374            _inCheckCode.remove(ct);
1375        }
1376    
1377        /**
1378         * Log an exception in a predicate.
1379         * @param t exception thrown
1380         * @param args method arguments
1381         * @param predicateClass name of the predicate (name of the annotation)
1382         */
1383        public static synchronized void checkCurrentThread_PredicateException(Throwable t, Object[] args, String predicateClass) {
1384            final Thread ct = Thread.currentThread();
1385            if (_inCheckCode.contains(ct)) {
1386                return;
1387            }
1388            _inCheckCode.add(ct);
1389            ++_checkCount;
1390            final String curName = ct.getName();
1391            final long curId = ct.getId();
1392            final String curGroupName = ct.getThreadGroup().getName();
1393            ++_violationCount;
1394            Violation v = new ExceptionInArgumentPredicateViolation(curName, curGroupName, curId, ct.getStackTrace(), predicateClass, args, t);
1395            writeLog(v);
1396            _inCheckCode.remove(ct);
1397        }
1398    
1399        /**
1400         * Checks if the current thread may execute the method from where this method was called purely using reflection.
1401         * If it may not execute, a violation is logged.
1402         * @param callerClass class of the caller
1403         * @param methodName name of the method
1404         * @param methodDesc descriptor of the method
1405         * @param thisO value of this, or null in a static context
1406         * @param methodArgs array of method arguments, or null if none passed
1407         */
1408        public static synchronized void checkCurrentThreadReflection(Class callerClass, String methodName, String methodDesc, Object thisO, Object[] methodArgs) {
1409            final Thread ct = Thread.currentThread();
1410            if (_inCheckCode.contains(ct)) {
1411                return;
1412            }
1413            _inCheckCode.add(ct);
1414            try {
1415    //            System.out.println("Class = "+callerClass.getName());
1416    //            System.out.println("Method = "+methodName);
1417    //            System.out.println("Desc = "+methodDesc);
1418    //            System.out.println("this = "+thisO);
1419    
1420                // determine the calling method
1421                Method callerMethod = getMethodWithNameAndDescriptor(callerClass, methodName, methodDesc);
1422                if (callerMethod==null) {
1423                    initLog();
1424                    _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1425                    _log.println("Thread Checker could not find method "+methodName+methodDesc+" in class "+callerClass.getName());
1426                    _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1427                    _log.flush();
1428                    return;
1429                }
1430    
1431    //            System.out.println("Caller method = "+callerMethod);
1432    
1433                // handle @PredicateLink and @Combine annotations
1434                ThreadCheckAnnotationRecord ar = getMethodAnnotations(callerClass, callerMethod);
1435    
1436    //            System.out.println("@PredicateLinks: "+ar.predicateLinkAnnotations.size());
1437    //            System.out.println("@Combines: "+ar.combineAnnotations.size());
1438    
1439                for(Pair<Annotation,PredicateLink> p: ar.predicateLinkAnnotations) {
1440    //                System.out.println("@PredicateLink: "+p.first()+", "+p.second());
1441                    ++_checkCount;
1442                    Boolean ret = checkPredicateLinkAnnotation(p.first(), p.second(), thisO, methodArgs);
1443                    if ((ret!=null) && (ret==false)) {
1444                        // we have a violation
1445    //                    System.out.println("\tViolation!");
1446                        ++_violationCount;
1447                        Violation v = new ReflectionPredicateViolation(ct.getName(), ct.getThreadGroup().getName(), ct.getId(), ct.getStackTrace(),
1448                                                                       p.first().toString(), methodArgs);
1449                        writeLog(v);
1450                    }
1451                }
1452                for(Pair<Annotation,Combine> p: ar.combineAnnotations) {
1453    //                System.out.println("@Combine: "+p.first()+", "+p.second());
1454                    ++_checkCount;
1455                    Boolean ret = checkCombineAnnotation(p.first(), p.second(), thisO, methodArgs);
1456                    if ((ret!=null) && (ret==false)) {
1457                        // we have a violation
1458                        ++_violationCount;
1459                        Violation v = new ReflectionPredicateViolation(ct.getName(), ct.getThreadGroup().getName(), ct.getId(), ct.getStackTrace(),
1460                                                                       p.first().toString(), methodArgs);
1461                        writeLog(v);
1462                    }
1463                }
1464            }
1465            finally {
1466                _inCheckCode.remove(ct);
1467            }
1468        }
1469    
1470        /**
1471         * Handle a @PredicateLink-style annotation.
1472         * @param ann annotation
1473         * @param pl associated @PredicateLink
1474         * @param thisO value of this, or null
1475         * @return false if there was a violation, or null if there was an error
1476         */
1477        public static Boolean checkPredicateLinkAnnotation(Annotation ann, PredicateLink pl, Object thisO, Object[] methodArgs) {
1478            Class predicateClass = pl.value();
1479            String predicateMethodName = pl.method();
1480            Class annClass = ann.annotationType();
1481            Method[] annMethods = annClass.getDeclaredMethods();
1482            int parenIndex = predicateMethodName.indexOf('(');
1483            if (parenIndex>=0) {
1484                if (!predicateMethodName.endsWith(")")) {
1485                    initLog();
1486                    _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1487                    _log.println("Thread Checker found a bad predicate method name with parameter list that did not end in ')': "+predicateMethodName);
1488                    _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1489                    _log.flush();
1490                    return null;
1491                }
1492                // strip parameter name list in parentheses from name: "methodName(param1,param2)"
1493                String s = predicateMethodName.substring(parenIndex + 1, predicateMethodName.length() - 1);
1494                String[] paramNames = s.split(",");
1495                ArrayList<Method> tempMethods = new ArrayList<Method>();
1496                predicateMethodName = predicateMethodName.substring(0,parenIndex);
1497                if (s.length()>0) {
1498                    for(int i=0; i<annMethods.length; ++i) {
1499                        if (paramNames[i].length()>0) {
1500                            for(Method m: annClass.getDeclaredMethods()) {
1501                                if (m.getName().equals(paramNames[i])) {
1502                                    tempMethods.add(m);
1503                                    break;
1504                                }
1505                            }
1506                            if (annMethods[i]==null) {
1507                                initLog();
1508                                _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1509                                _log.println("Thread Checker found a bad predicate method name with parameter list: that contained");
1510                                _log.println("the parameter "+paramNames[i]+", which wasn't found in the annotation.");
1511                                _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1512                                _log.flush();
1513                                return null;
1514                            }
1515                        }
1516                    }
1517                }
1518                annMethods = tempMethods.toArray(new Method[0]);
1519            }
1520            Method predicateMethod = null;
1521            int start = 1;
1522            if (pl.arguments()) {
1523                ++start;
1524            }
1525            for(Method m: predicateClass.getMethods()) {
1526                // initLog();
1527                // _log.println("Considering "+m+"...");
1528                if ((m.getName().equals(predicateMethodName)) &&
1529                    ((m.getModifiers() & Modifier.STATIC)!=0) &&
1530                    (m.getReturnType().isPrimitive()) &&
1531                    (m.getReturnType().getName().equals("boolean")) &&
1532                    (m.getParameterTypes().length >= start) &&
1533                    (m.getParameterTypes()[0].equals(Object.class))) {
1534                    // _log.println("- static, boolean, takes Object");
1535                    // _log.println("- "+m.getParameterTypes()[1].getName());
1536                    if (pl.arguments()) {
1537                        if (!m.getParameterTypes()[1].getName().equals("[Ljava.lang.Object;")) {
1538                            continue;
1539                        }
1540                    }
1541                    // _log.println("- takes Object[]");
1542                    // name matches, is static, returns boolean, and takes Object as first parameter
1543                    // check parameters
1544                    // _log.println("- m.getParameterTypes().length = "+m.getParameterTypes().length+", start = "+start+
1545                    //              ", annMethods.length = "+annMethods.length);
1546                    if (m.getParameterTypes().length-start!=annMethods.length) {
1547                        // wrong number of parameters
1548                        // _log.println("- wrong number of parameters");
1549                        continue;
1550                    }
1551                    boolean found = true;
1552                    for (int i=start; i<m.getParameterTypes().length; ++i) {
1553                        Class pt = m.getParameterTypes()[i];
1554                        if (!pt.equals(annMethods[i-start].getReturnType())) {
1555                            found = false;
1556                            break;
1557                        }
1558                    }
1559                    if (found) {
1560                        // parameters matched
1561                        predicateMethod = m;
1562                        break;
1563                    }
1564                }
1565            }
1566            if (predicateMethod==null) {
1567                initLog();
1568                // TODO: allow for subclassing
1569                _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1570                _log.println("Thread Checker could not find predicate method with name "+predicateMethodName+
1571                             " in class "+predicateClass.getName()+" suitable for annotation "+annClass.getName());
1572                _log.println("To be suitable, a predicate method has to have the following parameters in the right order:");
1573                _log.println("\tjava.lang.Object");
1574                if (pl.arguments()) {
1575                    _log.println("\tjava.lang.Object[]");
1576                }
1577                for(int i=0; i<annMethods.length; ++i) {
1578                    Method annMethod = annMethods[i];
1579                    _log.println("\t"+annMethod.getReturnType());
1580                }
1581                _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1582                _log.flush();
1583                return null;
1584            }
1585    
1586    //        System.out.println("Predicate method: "+predicateMethod);
1587    
1588            Object[] params = new Object[predicateMethod.getParameterTypes().length];
1589            params[0] = thisO;
1590            if (pl.arguments()) {
1591                params[1] = methodArgs;
1592            }
1593            for(int i=start; i<params.length; ++i) {
1594                Method annMethod = annMethods[i-start];
1595    //            System.out.println("Invoking annotation method "+annMethod);
1596                try {
1597                    annMethod.setAccessible(true);
1598                    params[i] = annMethod.invoke(ann);
1599                }
1600                catch(IllegalAccessException e) {
1601                    initLog();
1602                    _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1603                    _log.println("Thread Checker could not invoke annotation method "+annMethod+": "+
1604                                 e.toString());
1605                    _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1606                    _log.flush();
1607                    return null;
1608                }
1609                catch(InvocationTargetException e) {
1610                    initLog();
1611                    _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1612                    _log.println("Thread Checker could not invoke annotation method "+annMethod+": "+
1613                                 e.toString());
1614                    _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1615                    _log.flush();
1616                    return null;
1617                }
1618            }
1619            try {
1620                predicateMethod.setAccessible(true);
1621                Object retval = predicateMethod.invoke(null, params);
1622                if (!retval.getClass().equals(Boolean.class)) {
1623                    initLog();
1624                    _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1625                    _log.println("Thread Checker invoked predicate method "+predicateMethod+" in class "+predicateClass.getName()+
1626                                 ", but the result was not a boolean.");
1627                    _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1628                    _log.flush();
1629                    return null;
1630                }
1631                return (Boolean)retval;
1632            }
1633            catch(IllegalAccessException e) {
1634                initLog();
1635                _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1636                _log.println("Thread Checker could not invoke invoked predicate method "+predicateMethod+" in class "+predicateClass.getName()+
1637                             ": "+e.toString());
1638                _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1639                _log.flush();
1640                return null;
1641            }
1642            catch(InvocationTargetException e) {
1643                initLog();
1644                _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1645                _log.println("Thread Checker could not invoke invoked predicate method "+predicateMethod+" in class "+predicateClass.getName()+
1646                             ": "+e.toString());
1647                _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1648                _log.flush();
1649                return null;
1650            }
1651        }
1652    
1653        /**
1654         * Handle a @Combine-style annotation.
1655         * @param ann annotation
1656         * @param co associated @Combine
1657         * @param thisO value of this, or null
1658         * @param methodArgs array of method arguments, or null if none passed
1659         * @return false if there was a violation, or null if there was an error
1660         */
1661        public static Boolean checkCombineAnnotation(Annotation ann, Combine co, Object thisO, Object[] methodArgs) {
1662            Combine.Mode mode = co.value();
1663    //        System.out.println("checkCombineAnnotation: "+ann);
1664            // set up starting value: AND/NOT = true, OR/XOR = false, 0 for XOR
1665            boolean result = true;
1666            int count = 0;
1667            if ((mode==Combine.Mode.OR) ||
1668                (mode==Combine.Mode.XOR)) { result = false; }
1669    
1670            Class<? extends Annotation> annClass = ann.annotationType();
1671    //        System.out.println("Processing annotation "+ann);
1672            for (Method m: annClass.getDeclaredMethods()) {
1673                Class<?> retClass = m.getReturnType();
1674    //            System.out.println("\tMethod "+m);
1675    //            System.out.println("\t\tretClass = "+retClass+", annotation? "+retClass.isAnnotation());
1676                boolean isAnnotation = retClass.isAnnotation();
1677                if (retClass.isArray()) {
1678                    // it's an array, make sure it's an array of annotations
1679                    do {
1680                        retClass = retClass.getComponentType();
1681                        if (retClass!=null) { isAnnotation = retClass.isAnnotation(); }
1682                    } while((retClass!=null) && (retClass.isArray()));
1683                }
1684                if (!isAnnotation) {
1685                    // not an annotation, illegal in a @Combine-style annotation
1686                    initLog();
1687                    _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1688                    _log.println("Thread Checker found a @Combine-style annotation "+ann+" that had a non-annotation member.");
1689                    _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1690                    _log.flush();
1691                    return null;
1692                }
1693                PredicateLink memberPL = retClass.getAnnotation(PredicateLink.class);
1694                Combine memberCO = retClass.getAnnotation(Combine.class);
1695                if ((memberPL!=null) && (memberCO!=null)) {
1696                    initLog();
1697                    _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1698                    _log.println("Thread Checker found a @Combine-style annotation "+ann+" that had a member that had "
1699                                 + "both a @PredicateLink and a @Combine meta-annotation.");
1700                    _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1701                    _log.flush();
1702                }
1703                if ((memberPL==null) && (memberCO==null)) {
1704                    initLog();
1705                    _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1706                    _log.println("Thread Checker found a @Combine-style annotation "+ann+" that had a member that had "
1707                                 + "neither a @PredicateLink nor a @Combine meta-annotation.");
1708                    _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1709                    _log.flush();
1710                }
1711    
1712                // invoke method to get member annotation
1713                Object retObj;
1714                try {
1715                    m.setAccessible(true);
1716                    retObj = m.invoke(ann);
1717                }
1718                catch(IllegalAccessException e) {
1719                    initLog();
1720                    _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1721                    _log.println("Thread Checker could not invoke annotation method "+m+": "+
1722                                 e.toString());
1723                    _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1724                    _log.flush();
1725                    return null;
1726                }
1727                catch(InvocationTargetException e) {
1728                    initLog();
1729                    _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1730                    _log.println("Thread Checker could not invoke annotation method "+m+": "+
1731                                 e.toString());
1732                    _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1733                    _log.flush();
1734                    return null;
1735                }
1736    
1737                if (!retObj.getClass().isArray()) {
1738                    Annotation member = (Annotation)retObj;
1739                    Boolean retval = processCombineMember(member, memberPL, memberCO, thisO, methodArgs);
1740                    if (retval==null) {
1741                        // some error occurred
1742                        initLog();
1743                        _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1744                        _log.println("Thread Checker encountered an error while processing member annotation "+member+
1745                                     " of annotation "+ann);
1746                        _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1747                        _log.flush();
1748                        return null;
1749                    }
1750    
1751                    ++count;
1752                    switch(mode) {
1753                        case OR: {
1754                            result |= retval;
1755                            break;
1756                        }
1757                        case AND: {
1758                            result &= retval;
1759                            break;
1760                        }
1761                        case NOT: {
1762                            if (count>1) {
1763                                // not is a unary operation
1764                                initLog();
1765                                _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1766                                _log.println("Thread Checker found a @Combine-style annotation "+ann+" that used NOT with not exactly one operand.");
1767                                _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1768                                _log.flush();
1769                                return null;
1770                            }
1771                            result = !retval;
1772                            break;
1773                        }
1774                        case XOR: {
1775                            result = (count==1);
1776                            break;
1777                        }
1778                        case IMPLIES: {
1779                            // implies must be a single array with two elements
1780                            initLog();
1781                            _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1782                            _log.println("Thread Checker found a @Combine-style annotation "+ann+" that used IMPLIES with not exactly one array with two elements.");
1783                            _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1784                            _log.flush();
1785                            break;
1786                        }
1787                    }
1788                }
1789                else {
1790                    Annotation[] members = (Annotation[])retObj;
1791                    for(Annotation member: members) {
1792                        Boolean retval = processCombineMember(member, memberPL, memberCO, thisO, methodArgs);
1793                        if (retval==null) {
1794                            // some error occurred
1795                            initLog();
1796                            _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1797                            _log.println("Thread Checker encountered an error while processing member annotation "+member+
1798                                         " of annotation "+ann);
1799                            _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1800                            _log.flush();
1801                            return null;
1802                        }
1803    
1804                        ++count;
1805                        boolean impliesOp1 = false;
1806                        switch(mode) {
1807                            case OR: {
1808                                result |= retval;
1809                                break;
1810                            }
1811                            case AND: {
1812                                result &= retval;
1813                                break;
1814                            }
1815                            case NOT: {
1816                                if (count>1) {
1817                                    // not is a unary operation
1818                                    initLog();
1819                                    _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1820                                    _log.println("Thread Checker found a @Combine-style annotation "+ann+" that used NOT with not exactly one operand.");
1821                                    _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1822                                    _log.flush();
1823                                    return null;
1824                                }
1825                                result = !retval;
1826                                break;
1827                            }
1828                            case XOR: {
1829                                result = (count==1);
1830                                break;
1831                            }
1832                            case IMPLIES: {
1833                                if (count>2) {
1834                                    // implies must be a single array with two elements
1835                                    initLog();
1836                                    _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1837                                    _log.println("Thread Checker found a @Combine-style annotation "+ann+" that used IMPLIES with not exactly one array with two elements.");
1838                                    _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
1839                                    _log.flush();
1840                                    break;
1841                                }
1842                                else if (count<2) {
1843                                    // store the first time
1844                                    impliesOp1 = retval;
1845                                }
1846                                if (count==2) {
1847                                    // perform the second time
1848                                    // P Q (P->Q)
1849                                    // T T  T->T = T
1850                                    // T F  T=>F = F
1851                                    // F T  F->T = T
1852                                    // F F  F->F = T
1853                                    // (NOT A) OR B
1854                                    result = !impliesOp1 || retval;
1855                                }
1856                                break;
1857                            }
1858                        }
1859                    }
1860                }
1861            }
1862    //        System.out.println("\tResult = "+result);
1863    //        System.out.flush();
1864    
1865            return result;
1866        }
1867    
1868        /**
1869         * Process the value of a member annotation in a @Combine-style annotation.
1870         * @param member member annotation
1871         * @param memberPL @PredicateLink meta-annotation associated with this member
1872         * @param memberCO @Combine meta-annotation associated with this member
1873         * @param thisO value of this
1874         * @param methodArgs array of method arguments, or null if none passed
1875         * @return true if permitted, false if violation, or null if error occurred.
1876         */
1877        private static Boolean processCombineMember(Annotation member, PredicateLink memberPL, Combine memberCO,
1878                                                    Object thisO, Object[] methodArgs) {
1879            Boolean retval;
1880            if (memberPL!=null) {
1881                // member is a @PredicateLink-style annotation
1882    //            System.out.println("\tRecursing into @PredicateLink annotation "+member+" with @PredicateLink = "+memberPL);
1883                retval = checkPredicateLinkAnnotation(member, memberPL, thisO, methodArgs);
1884            }
1885            else {
1886                // member is a @Combine-style annotation
1887    //            System.out.println("\tRecursing into @Combine annotation "+member+" with @Combine = "+memberCO);
1888                retval = checkCombineAnnotation(member, memberCO, thisO, methodArgs);
1889            }
1890    //        System.out.println("\tResult = "+retval);
1891            return retval;
1892        }
1893    
1894        /**
1895         * Return the method with the specified name and descriptor.
1896         * @param methodClass class
1897         * @param methodName name
1898         * @param methodDesc descriptor
1899         * @return method, or null
1900         */
1901        public static Method getMethodWithNameAndDescriptor(Class methodClass, String methodName, String methodDesc) {
1902            Method callerMethod = null;
1903            for(Method m: methodClass.getDeclaredMethods()) {
1904                if (m.getName().equals(methodName)) {
1905                    String desc = getMethodDescriptor(m);
1906                    if (desc.toString().equals(methodDesc)) {
1907                        callerMethod = m;
1908                        break;
1909                    }
1910                }
1911            }
1912            return callerMethod;
1913        }
1914    
1915        /**
1916         * Return the method descriptor for the method.
1917         * @param m method
1918         * @return descriptor
1919         */
1920        public static String getMethodDescriptor(Method m) {
1921            StringBuilder sb = new StringBuilder();
1922            sb.append('(');
1923            for(Class pt: m.getParameterTypes()) {
1924                String n = pt.getName();
1925                if (!pt.isPrimitive()) {
1926                    // reference
1927                    sb.append(n.replace('.','/'));
1928                }
1929                else {
1930                    // primitive value
1931                    char c = ClassFileTools.getPrimitiveTypeChar(n);
1932                    if (c==0) {
1933                        System.err.println("Unknown primitive type in Thread Checker: "+n);
1934                        System.exit(1);
1935                    }
1936                    else { sb.append(c); }
1937                }
1938            }
1939            sb.append(')');
1940            String n = m.getReturnType().getName();
1941            if (!m.getReturnType().isPrimitive()) {
1942                // reference return type
1943                sb.append(n.replace('.','/'));
1944            }
1945            else {
1946                // primitive return type
1947                char c = ClassFileTools.getPrimitiveTypeChar(n);
1948                if (c==0) {
1949                    System.err.println("Unknown primitive type in Thread Checker: "+n);
1950                    System.exit(1);
1951                }
1952                else { sb.append(c); }
1953            }
1954            return sb.toString();
1955        }
1956    
1957        /**
1958         * Return the correct form, singular or plural, of the word, depending on the count.
1959         * @param word word
1960         * @param count count
1961         * @return word+"s" if count!=1, or word if count==1
1962         */
1963        public static String form(String word, long count) { return (count==1)?word:(word+'s'); }
1964    
1965        /**
1966         * Storage class for predicate annotations.
1967         */
1968        public static class ThreadCheckAnnotationRecord {
1969            public HashSet<Pair<Annotation,PredicateLink>> predicateLinkAnnotations;
1970            public HashSet<Pair<Annotation,Combine>> combineAnnotations;
1971            public boolean suppressSubtypingWarning = false;
1972    
1973            public ThreadCheckAnnotationRecord() {
1974                predicateLinkAnnotations = new HashSet<Pair<Annotation,PredicateLink>>();
1975                combineAnnotations = new HashSet<Pair<Annotation,Combine>>();
1976            }
1977    
1978            public void add(ThreadCheckAnnotationRecord otherAR) {
1979                predicateLinkAnnotations.addAll(otherAR.predicateLinkAnnotations);
1980                combineAnnotations.addAll(otherAR.combineAnnotations);
1981            }
1982        }
1983    
1984        /**
1985         * Get the annotations for the specified method in the specified class file.
1986         * @param cf class file
1987         * @param mi method information
1988         * @return annotations
1989         */
1990        public static ThreadCheckAnnotationRecord getMethodAnnotations(Class cf, Method mi) {
1991            ThreadCheckAnnotationRecord methodAR = getPredicateSets(mi.getAnnotations());
1992    
1993            // a set of method signatures of the form "<method name><descriptor>" that describe methods that have
1994            // been defined in superclasses or implemented interfaces already and to which the class level annotations
1995            // on THIS class should therefore NOT apply
1996            HashSet<String> definedAbove = new HashSet<String>();
1997    
1998            if (cf.getSuperclass()!=null) {
1999                // find the annotations for the same method in the superclass
2000                boolean found = false;
2001                Class curcf = cf;
2002    
2003                while(!found && curcf.getSuperclass()!=null) {
2004                    Class scf = curcf.getSuperclass();
2005                    for(Method smi : scf.getMethods()) {
2006                        definedAbove.add(smi.getName()+getMethodDescriptor(smi));
2007                        if ((smi.getName().equals(mi.getName()) &&
2008                            (getMethodDescriptor(smi).equals(getMethodDescriptor(mi))))) {
2009                            ThreadCheckAnnotationRecord superClassMethodAR = getMethodAnnotations(scf, smi);
2010                            if (!methodAR.suppressSubtypingWarning) {
2011                                // also check for subtyping warnings with predicate annotations?
2012                                checkForSubtypingClassWarnings(cf, mi, scf, methodAR, superClassMethodAR);
2013                            }
2014                            methodAR.add(superClassMethodAR);
2015                            found = true;
2016                        }
2017                    }
2018                    if (!found) {
2019                        curcf = scf;
2020                    }
2021                }
2022    
2023                // find the annotations for all the interfaces
2024                for(Class icf: cf.getInterfaces()) {
2025                    found = false;
2026                    Class scf = icf;
2027                    while(!found && scf!=null) {
2028                        for(Method smi : scf.getMethods()) {
2029                            definedAbove.add(smi.getName()+getMethodDescriptor(smi));
2030                            if ((smi.getName().equals(mi.getName()) &&
2031                                (getMethodDescriptor(smi).equals(getMethodDescriptor(mi))))) {
2032                                ThreadCheckAnnotationRecord interfClassMethodAR = getMethodAnnotations(scf, smi);
2033                                if (!methodAR.suppressSubtypingWarning) {
2034                                    // also check for subtyping warnings with predicate annotations?
2035                                    checkForSubtypingClassWarnings(cf, mi, scf, methodAR, interfClassMethodAR);
2036                                }
2037                                methodAR.add(interfClassMethodAR);
2038                                found = true;
2039                            }
2040                        }
2041                        if (!found) {
2042                            scf = scf.getSuperclass();
2043                        }
2044                    }
2045                }
2046            }
2047    
2048            // add class level annotations, but only for those methods not mentioned in definedAbove
2049            if (!definedAbove.contains(mi.getName()+getMethodDescriptor(mi))) {
2050                methodAR.add(getClassAnnotations(cf));
2051            }
2052    
2053            return methodAR;
2054        }
2055    
2056        /**
2057         * Get the annotations for the specified class file.
2058         * @param cf class file
2059         * @return annotations
2060         */
2061        public static ThreadCheckAnnotationRecord getClassAnnotations(Class cf) {
2062            ThreadCheckAnnotationRecord classAR = getPredicateSets(cf.getAnnotations());
2063    
2064            if (cf.getSuperclass()!=null) {
2065                ThreadCheckAnnotationRecord superClassAR = getClassAnnotations(cf.getSuperclass());
2066                classAR.add(superClassAR);
2067    
2068                // find the annotations for all the interfaces
2069                for(Class icf: cf.getInterfaces()) {
2070                    ThreadCheckAnnotationRecord interfClassAR = getClassAnnotations(icf);
2071                    classAR.add(interfClassAR);
2072                }
2073            }
2074    
2075            return classAR;
2076        }
2077    
2078    
2079        /**
2080         * Get the predicate sets from the array of annotations.
2081         * @param annotations array of annotations
2082         * @return annotation record.
2083         */
2084        public static ThreadCheckAnnotationRecord getPredicateSets(Annotation[] annotations) {
2085            ThreadCheckAnnotationRecord ar = new ThreadCheckAnnotationRecord();
2086            for (Annotation ann: annotations) {
2087                PredicateLink pl = ann.annotationType().getAnnotation(PredicateLink.class);
2088                if (pl!=null) {
2089                    ar.predicateLinkAnnotations.add(new Pair<Annotation,PredicateLink>(ann, pl));
2090                }
2091                Combine co = ann.annotationType().getAnnotation(Combine.class);
2092                if (co!=null) {
2093                    ar.combineAnnotations.add(new Pair<Annotation,Combine>(ann, co));
2094                }
2095                if (ann.annotationType().getAnnotation(SuppressSubtypingWarning.class)!=null) {
2096                    ar.suppressSubtypingWarning = true;
2097                }
2098            }
2099            return ar;
2100        }
2101    
2102        /**
2103         * Check for subtyping warnings.
2104         * @param cf class file
2105         * @param mi method information, or null if on class level
2106         * @param scf superclass/interface class file
2107         * @param classAR subclass annotations
2108         * @param superClassAR superclass annotations
2109         */
2110        public static void checkForSubtypingClassWarnings(Class cf,
2111                                                          Method mi,
2112                                                          Class scf,
2113                                                          ThreadCheckAnnotationRecord classAR,
2114                                                          ThreadCheckAnnotationRecord superClassAR) {
2115            String methodName = null, methodDesc = null;
2116            if (mi!=null) {
2117                methodName = mi.getName();
2118                methodDesc = getMethodDescriptor(mi);
2119            }
2120    
2121            // check for subtyping warnings with predicate annotations
2122            for(Pair<Annotation,PredicateLink> pair: classAR.predicateLinkAnnotations) {
2123                if (!superClassAR.predicateLinkAnnotations.contains(pair)) {
2124                    initLog();
2125                    _log.print("ThreadChecker Reflection Subtyping Warnings: ");
2126                    if (mi==null) {
2127                        // class level warning
2128                        _log.print(cf.getName());
2129                        _log.print(" has predicate annotation ");
2130                        _log.print(pair.first());
2131                        _log.print(" but ");
2132                        _log.print(scf.getName());
2133                    }
2134                    else {
2135                        // method level warning
2136                        _log.print(cf.getName());
2137                        _log.print('.');
2138                        _log.print(mi.getName());
2139                        _log.print(getMethodDescriptor(mi));
2140                        _log.print(" has predicate annotation ");
2141                        _log.print(pair.first());
2142                        _log.print(" but ");
2143                        _log.print(scf.getName());
2144                        _log.print('.');
2145                        _log.print(mi.getName());
2146                        _log.print(getMethodDescriptor(mi));
2147                    }
2148                    _log.println(" does not");
2149                    _log.flush();
2150                }
2151            }
2152        }
2153    }