001    package edu.rice.cs.cunit.classFile;
002    
003    import edu.rice.cs.cunit.classFile.attributes.*;
004    import edu.rice.cs.cunit.classFile.constantPool.*;
005    import edu.rice.cs.cunit.classFile.constantPool.visitors.ADefaultPoolInfoVisitor;
006    import edu.rice.cs.cunit.util.ILambda;
007    import junit.framework.TestCase;
008    
009    import java.io.*;
010    import java.util.*;
011    import java.util.jar.JarEntry;
012    import java.util.jar.JarFile;
013    
014    /**
015     * Tools for dealing with class files.
016     *
017     * @author Mathias Ricken
018     */
019    public class ClassFileTools {
020        /**
021         * Return a string that represents the access flags set.
022         *
023         * @param flags access flags
024         *
025         * @return string with access flags as words
026         */
027        public static String getAccessString(short flags) {
028            StringBuilder x = new StringBuilder();
029    
030            if (0 != (flags & ClassFile.ACC_PUBLIC)) {
031                x.append("public ");
032            }
033    
034            if (0 != (flags & ClassFile.ACC_PRIVATE)) {
035                x.append("private ");
036            }
037    
038            if (0 != (flags & ClassFile.ACC_PROTECTED)) {
039                x.append("protected ");
040            }
041    
042            if (0 != (flags & ClassFile.ACC_STATIC)) {
043                x.append("static ");
044            }
045    
046            if (0 != (flags & ClassFile.ACC_FINAL)) {
047                x.append("final ");
048            }
049    
050            if (0 != (flags & ClassFile.ACC_SYNCHRONIZED)) {
051                x.append("synchronized ");
052            }
053    
054            if (0 != (flags & ClassFile.ACC_VOLATILE)) {
055                x.append("volatile ");
056            }
057    
058            if (0 != (flags & ClassFile.ACC_TRANSIENT)) {
059                x.append("transient ");
060            }
061    
062            if (0 != (flags & ClassFile.ACC_NATIVE)) {
063                x.append("native ");
064            }
065    
066            if (0 != (flags & ClassFile.ACC_INTERFACE)) {
067                x.append("interface ");
068            }
069    
070            if (0 != (flags & ClassFile.ACC_ABSTRACT)) {
071                x.append("abstract ");
072            }
073    
074            if (0 != (flags & ClassFile.ACC_STRICT)) {
075                x.append("strictfp ");
076            }
077    
078            // TODO: Handle ACC_SYNTHETIC, ACC_ANNOTATION, ACC_ENUM, ACC_BRIDGE, and ACC_VARARGS here?
079    
080            return x.toString();
081        }
082    
083        /**
084         * Translate a type descriptor descriptor and a variable name into a Java declaration.
085         *
086         * @param typeString type descriptor descriptor
087         * @param varName    variable name
088         *
089         * @return declaration
090         */
091        public static String getTypeString(String typeString, String varName) {
092            int isArray = 0;
093            int ndx = 0;
094            StringBuilder x = new StringBuilder();
095    
096            while('[' == typeString.charAt(ndx)) {
097                ++isArray;
098                ++ndx;
099            }
100    
101            switch(typeString.charAt(ndx)) {
102                case 'B':
103                    x.append("byte");
104                    break;
105                case 'C':
106                    x.append("char");
107                    break;
108                case 'D':
109                    x.append("double");
110                    break;
111                case 'F':
112                    x.append("float");
113                    break;
114                case 'I':
115                    x.append("int");
116                    break;
117                case 'J':
118                    x.append("long");
119                    break;
120                case 'L':
121                    for(int i = ndx + 1; i < typeString.indexOf((int)';'); ++i) {
122                        if ('/' != typeString.charAt(i)) {
123                            x.append(typeString.charAt(i));
124                        }
125                        else {
126                            x.append('.');
127                        }
128                    }
129                    break;
130                case 'V':
131                    x.append("void");
132                    break;
133                case 'S':
134                    x.append("short");
135                    break;
136                case 'Z':
137                    x.append("boolean");
138                    break;
139            }
140            while(0 < isArray) {
141                x.append("[]");
142                isArray--;
143            }
144            x.append(' ');
145            x.append(varName);
146            return x.toString();
147        }
148    
149        /**
150         * Returns the type character associated with a primitive type name, or 0 if there was an error.
151         * @param primTypeName primitive type name, e.g. "void" or "byte"
152         * @return primitive type character, e.g. 'V' or 'B', or 0 if the type name was not recognized
153         */
154        public static char getPrimitiveTypeChar(String primTypeName) {
155            if (primTypeName.equals("void")) { return 'V'; }
156            else if (primTypeName.equals("byte")) { return 'B'; }
157            else if (primTypeName.equals("char")) { return 'C'; }
158            else if (primTypeName.equals("double")) { return 'D'; }
159            else if (primTypeName.equals("float")) { return 'F'; }
160            else if (primTypeName.equals("int")) { return 'I'; }
161            else if (primTypeName.equals("long")) { return 'J'; }
162            else if (primTypeName.equals("short")) { return 'S'; }
163            else if (primTypeName.equals("boolean")) { return 'Z'; }
164            return 0;
165        }
166    
167        /**
168         * Return true if this is a primitive type or an array of primitive types.
169         *
170         * @param typeString type descriptor descriptor
171         *
172         * @return true if primitive type or array of primitive types
173         */
174        public static boolean isPrimitive(String typeString) {
175            int ndx = 0;
176    
177            while('[' == typeString.charAt(ndx)) {
178                ++ndx;
179            }
180    
181            switch(typeString.charAt(ndx)) {
182                case 'B':
183                case 'C':
184                case 'D':
185                case 'F':
186                case 'I':
187                case 'J':
188                case 'V':
189                case 'S':
190                case 'Z':
191                    return true;
192                default:
193                    return false;
194            }
195        }
196    
197        /**
198         * Return a list of signatures.
199         * @param sig concatenated signatures
200         * @return list of signatures
201         */
202        public static List<String> getSignatures(String sig) {
203            ArrayList<String> sigList = new ArrayList<String>();
204            if (sig!=null) {
205                while(sig.length()>0) {
206                    String next = ClassFileTools.getNextSignature(sig);
207                    sigList.add(sig.substring(0, sig.length()-next.length()));
208                    sig = next;
209                }
210            }
211            return sigList;
212        }
213    
214        /**
215         * Return the next descriptor from a string of concatenated signatures. For example, if the descriptor was "[BII",
216         * this method would return "II".
217         *
218         * @param sig concatenated signatures
219         *
220         * @return next descriptor
221         */
222        public static String getNextSignature(String sig) {
223            int ndx = 0;
224            String x;
225    
226            while('[' == sig.charAt(ndx)) {
227                ++ndx;
228            }
229    
230            if ('L' == sig.charAt(ndx)) {
231                while(';' != sig.charAt(ndx)) {
232                    ++ndx;
233                }
234            }
235            ++ndx;
236            x = (sig.substring(ndx));
237            return (x);
238        }
239    
240        /**
241         * Return class name in Java form.
242         *
243         * @param s mangled class name
244         *
245         * @return Java class name
246         */
247        public static String getClassName(String s) {
248            if ('[' == s.charAt(0)) {
249                return getTypeString(s, "");
250            }
251    
252            return s.replace('/','.');
253        }
254    
255    
256        /**
257         * Return set of class names used in this mangled name. Primitives are not included.
258         *
259         * @param s mangled class name
260         *
261         * @return set of Java class names used
262         */
263        public static Set<String> getClassNamesUsed(String s) {
264            HashSet<String> set = new HashSet<String>();
265            final char ch = s.charAt(0);
266    
267            if ('[' == ch) {
268                // only handle reference arrays
269                if ('L' == s.charAt(1)) {
270                    set.addAll(getClassNamesUsed(s.substring(1,s.indexOf(';')+1)));
271                }
272            }
273            else if ('(' == ch) {
274                String p = s.substring(1,s.indexOf(')'));
275                while(0 != p.length()) {
276                    String next = getNextSignature(p);
277                    String param = p.substring(0, p.length()-next.length());
278                    set.addAll(getClassNamesUsed(param));
279                    p = next;
280                }
281                p = s.substring(s.indexOf(')')+1);
282                set.addAll(getClassNamesUsed(p));
283            }
284            else if ('L' == ch) {
285                String p = s.substring(1,s.indexOf(';')).replace('/','.');
286                set.add(p);
287            }
288    
289            return set;
290        }
291    
292        /**
293         * Returns a set of class names used in a class files. Primitives are not included.
294         * @param cf class file
295         * @return set of class names used
296         */
297        public static Set<String> getClassNamesUsed(ClassFile cf) {
298            final HashSet<String> classesUsed = new HashSet<String>();
299            for(final APoolInfo cpi : cf.getConstantPool()) {
300                if ((cpi == cf.getThisClass()) || (cpi == cf.getSuperClass())) {
301                    continue;
302                }
303                cpi.execute(new ADefaultPoolInfoVisitor<Object, Object>() {
304                    public Object nameAndTypeCase(NameAndTypePoolInfo host, Object param) {
305                        String s = host.getDescriptor().toString();
306                        if ('[' == s.charAt(0)) {
307                            // array
308                            if ('L' != s.charAt(1)) {
309                                // ignore primitive arrays
310                                return null;
311                            }
312                            s = s.substring(2);
313                        }
314                        classesUsed.addAll(ClassFileTools.getClassNamesUsed(s));
315                        return null;
316                    }
317    
318                    public Object classCase(ClassPoolInfo host, Object param) {
319                        String s = host.getName().toString();
320                        if ('[' == s.charAt(0)) {
321                            // array
322                            if ('L' != s.charAt(1)) {
323                                // ignore primitive arrays
324                                return null;
325                            }
326                            s = s.substring(2);
327                        }
328                        if (s.endsWith(";")) {
329                            s = s.substring(0, s.length()-1);
330                        }
331                        classesUsed.add(s.replace('/','.'));
332                        return null;
333                    }
334                    public Object defaultCase(APoolInfo host, Object param) {
335                        return null;
336                    }
337                }, null);
338            }
339    
340            classesUsed.add(cf.getThisClassName());
341            classesUsed.add(cf.getSuperClassName());
342    
343            for(FieldInfo field : cf.getFields()) {
344                classesUsed.addAll(getClassNamesUsed(field.getDescriptor().toString()));
345            }
346    
347            for(MethodInfo method : cf.getMethods()) {
348                classesUsed.addAll(getClassNamesUsed(method.getDescriptor().toString()));
349            }
350    
351            AAttributeInfo attr = cf.getAttribute(InnerClassesAttributeInfo.getAttributeName());
352            if (null!=attr) {
353                InnerClassesAttributeInfo ic = (InnerClassesAttributeInfo)attr;
354                for (InnerClassesAttributeInfo.InnerClassesRecord inc: ic.getInnerClasses()) {
355                    inc.innerClass.execute(new ADefaultPoolInfoVisitor<Object, Object>() {
356                        public Object defaultCase(APoolInfo host, Object param) {
357                            return null;
358                        }
359                        public String emptyCase(EmptyPoolInfo host, Object param) {
360                            return null;
361                        }
362                        public String classCase(ClassPoolInfo host, Object param) {
363                            classesUsed.add(getClassName(host.getName().toString()));
364                            return null;
365                        }
366                    }, null);
367                    inc.outerClass.execute(new ADefaultPoolInfoVisitor<Object, Object>() {
368                        public Object defaultCase(APoolInfo host, Object param) {
369                            return null;
370                        }
371                        public String emptyCase(EmptyPoolInfo host, Object param) {
372                            return null;
373                        }
374                        public String classCase(ClassPoolInfo host, Object param) {
375                            classesUsed.add(getClassName(host.getName().toString()));
376                            return null;
377                        }
378                    }, null);
379                }
380            }
381    
382            attr = cf.getAttribute(RuntimeInvisibleAnnotationsAttributeInfo.getAttributeName());
383            if (null!=attr) {
384                RuntimeInvisibleAnnotationsAttributeInfo an = (RuntimeInvisibleAnnotationsAttributeInfo)attr;
385    
386                for (AAnnotationsAttributeInfo.Annotation a: an.getAnnotations()) {
387                    processAnnotation(a, classesUsed);
388    
389                }
390            }
391    
392            attr = cf.getAttribute(RuntimeVisibleAnnotationsAttributeInfo.getAttributeName());
393            if (null!=attr) {
394                RuntimeVisibleAnnotationsAttributeInfo an = (RuntimeVisibleAnnotationsAttributeInfo)attr;
395    
396                for (AAnnotationsAttributeInfo.Annotation a: an.getAnnotations()) {
397                    processAnnotation(a, classesUsed);
398    
399                }
400            }
401    
402            return classesUsed;
403        }
404    
405        /**
406         * Helper for getClassNamesUsed that processes an annotation.
407         * @param a annotation
408         * @param classesUsed set of classes used
409         */
410        private static void processAnnotation(AAnnotationsAttributeInfo.Annotation a, HashSet<String> classesUsed) {
411            classesUsed.add(getTypeString(a.getType(),"").trim());
412    
413            for (AAnnotationsAttributeInfo.Annotation.NameValuePair nvp: a.getPairs()) {
414                AAnnotationsAttributeInfo.Annotation.AMemberValue mv = nvp.getValue();
415                processMemberValue(mv, classesUsed);
416            }
417        }
418    
419        /**
420         * Helper for processAnnotation that processes an annotation member value.
421         * @param mv member value
422         * @param classesUsed set of classes used
423         */
424        private static void processMemberValue(AAnnotationsAttributeInfo.Annotation.AMemberValue mv,
425                                               final HashSet<String> classesUsed) {
426            mv.execute(new AAnnotationsAttributeInfo.Annotation.IMemberValueVisitor<Object, Object>() {
427                public Object constantMemberCase(AAnnotationsAttributeInfo.Annotation.ConstantMemberValue host, Object param) {
428                    host.getConstValue().execute(new ADefaultPoolInfoVisitor<Object, Object>() {
429                        public Object unicodeCase(UnicodePoolInfo host, Object param) {
430                            classesUsed.addAll(getClassNamesUsed("java.lang.String"));
431                            return null;
432                        }
433    
434                        public Object asciizCase(ASCIIPoolInfo host, Object param) {
435                            classesUsed.add("java.lang.String");
436                            return null;
437                        }
438    
439                        public Object defaultCase(APoolInfo host, Object param) {
440                            return null;
441                        }
442                    }, null);
443                    return null;
444                }
445                public Object enumMemberCase(AAnnotationsAttributeInfo.Annotation.EnumMemberValue host, Object param) {
446                    classesUsed.add(getTypeString(host.getTypeName().toString(),"").trim());
447                    return null;
448                }
449                public Object classMemberCase(AAnnotationsAttributeInfo.Annotation.ClassMemberValue host, Object param) {
450                    classesUsed.add("java.lang.Class");
451                    classesUsed.add(getTypeString(host.getClassName().toString(),"").trim());
452                    return null;
453                }
454                public Object annotationMemberCase(AAnnotationsAttributeInfo.Annotation.AnnotationMemberValue host, Object param) {
455                    classesUsed.add("java.lang.annotation.Annotation");
456                    processAnnotation(host.getAnnotation(), classesUsed);
457                    return null;
458                }
459                public Object arrayMemberCase(AAnnotationsAttributeInfo.Annotation.ArrayMemberValue host, Object param) {
460                    for (AAnnotationsAttributeInfo.Annotation.AMemberValue mv: host.getEntries()) {
461                        processMemberValue(mv, classesUsed);
462                    }
463                    return null;
464                }
465            }, null);
466        }
467    
468        /**
469         * Helper class for findClassFile.
470         */
471        public static class ClassLocation {
472            private final String _className;
473            private final InputStream _inputStream;
474            private final File _file;
475            private final ClassFile _cf;
476            private final JarFile _jarFile; // or null if not from a jar
477    
478            public ClassLocation(String className, ClassFile cf, InputStream inputStream, File file) {
479                this(className, cf, inputStream, file, null);
480            }
481    
482            public ClassLocation(String className, ClassFile cf, InputStream inputStream, File file, JarFile jarFile) {
483                this._className = className;
484                this._inputStream = inputStream;
485                this._file = file;
486                this._jarFile = jarFile;
487                this._cf = cf;
488            }
489    
490            public InputStream getInputStream() {
491                return _inputStream;
492            }
493    
494            public File getFile() {
495                return _file;
496            }
497    
498            public String getClassName() {
499                return _className;
500            }
501    
502            public JarFile getJarFile() {
503                return _jarFile;
504            }
505            
506            public ClassFile getClassFile() {
507                return _cf;
508            }
509    
510            public void close() throws IOException {
511                if (_inputStream!=null) { _inputStream.close(); }
512                if (_jarFile!=null) { _jarFile.close(); }
513            }
514        }
515    
516        /**
517         * Find a class.
518         * @param className class to find
519         * @param classPath list with class path entries
520         * @return class location
521         */
522        public static ClassLocation findClassFile(String className, List<String> classPath) {
523            for(String pathStr: classPath) {
524                File path = new File(pathStr);
525                if (path.isFile()) {
526                    if (pathStr.toLowerCase().endsWith(".class")) {
527                        FileInputStream stream = null;
528                        try {
529                            stream = new FileInputStream(path);
530                            ClassFile cf = new ClassFile(stream);
531    
532                            if (cf.getThisClassName().equals(className)) {
533                                return new ClassLocation(className, cf, new FileInputStream(path), path);
534                            }
535                        }
536                        catch(ClassFormatError e) {
537                           continue;
538                        }
539                        catch(IOException e) {
540                            continue;
541                        }
542                        finally {
543                            if (stream!=null) {
544                                try { stream.close(); }
545                                catch(IOException e1) { }
546                            }
547                        }
548                    }
549                    else if (pathStr.toLowerCase().endsWith(".jar")) {
550                        JarFile jf = null;
551                        InputStream stream = null;
552                        try {
553                            jf = new JarFile(path);
554                            JarEntry je = jf.getJarEntry(className.replace('.','/')+".class");
555                            if (null != je) {
556                                stream = jf.getInputStream(je);
557                                ClassFile cf = new ClassFile(stream);
558    
559                                if (cf.getThisClassName().equals(className)) {
560                                    return new ClassLocation(className, cf, jf.getInputStream(je), path, jf);
561                                }
562                                else {
563                                    jf.close();
564                                }
565                            }
566                            else {
567                                if (jf!=null) {
568                                    try { jf.close(); }
569                                    catch(IOException e1) { }
570                                }
571                            }
572                        }
573                        catch(IOException e) {
574                            if (jf!=null) {
575                                try { jf.close(); }
576                                catch(IOException e1) { }
577                            }
578                            continue;
579                        }
580                        catch(ClassFormatError e) {
581                            if (jf!=null) {
582                                try { jf.close(); }
583                                catch(IOException e1) { }
584                            }
585                            continue;
586                        }
587                        finally {
588                            if (stream!=null) {
589                                try { stream.close(); }
590                                catch(IOException e1) { }
591                            }
592                        }
593    
594                    }
595                }
596                else {
597                    File classFile = new File(path, className.replace('.','/')+".class");
598                    if (classFile.isFile()) {
599                        FileInputStream stream = null;
600                        try {
601                            stream = new FileInputStream(classFile);
602                            ClassFile cf = new ClassFile(stream);
603    
604                            if (cf.getThisClassName().equals(className)) {
605                                return new ClassLocation(className, cf, new FileInputStream(classFile), classFile);
606                            }
607                        }
608                        catch(IOException e) {
609                            continue;
610                        }
611                        catch(ClassFormatError e) {
612                            continue;
613                        }
614                        finally {
615                            try { stream.close(); }
616                            catch(IOException e1) { }
617                        }
618                    }
619                }
620            }
621    
622            return null;
623        }
624    
625        /**
626         * Generate the class dependency fix point.
627         * @param classNames list of class names
628         * @param classPath list of class path entries
629         * @param classesUsed set of used classes
630         * @param processClassNameLambda lambda to apply class names to
631         * @param processNotFoundLambda lambda to apply to missing class names to
632         * NOTE: The ClassLocation's input stream will get closed after the lambda has been invoked.
633         *       That means it should not be stored and used later.
634         */
635        public static void generateDependencyFixPoint(Set<String> classNames,
636                                                      List<String> classPath,
637                                                      Set<String> classesUsed,
638                                                      ILambda<Object,ClassLocation> processClassNameLambda,
639                                                      ILambda<Object,String> processNotFoundLambda) {
640            Queue<String> bfQueue = new LinkedList<String>();
641    
642            for(String className: classNames) {
643                bfQueue.add(className);
644    
645                while(!bfQueue.isEmpty()) {
646                    String curClassName = bfQueue.remove();
647                    ClassFileTools.ClassLocation cl = null;
648                    try {
649                        cl = findClassFile(curClassName, classPath);
650                        if ((null!=cl) && (!classesUsed.contains(curClassName))) {
651                            processClassNameLambda.apply(cl);
652                        }
653                        classesUsed.add(curClassName);
654                        if (null==cl) {
655                            processNotFoundLambda.apply(curClassName);
656                            continue;
657                        }
658                        try {
659                            ClassFile cf = new ClassFile(cl.getInputStream());
660                            Set<String> newClasses = getClassNamesUsed(cf);
661                            for(String c: newClasses) {
662                                if ((!classesUsed.contains(c)) && (!bfQueue.contains(c))) {
663                                    bfQueue.add(c);
664                                }
665                            }
666                        }
667                        catch(IOException e) {
668                            processNotFoundLambda.apply(curClassName);
669                        }
670                        catch(ClassFormatError classFormatError) {
671                            processNotFoundLambda.apply(curClassName);
672                        }
673                    }
674                    finally {
675                        try { if (cl!=null) cl.close(); }
676                        catch(IOException e) { /* ignore; shouldn't cause any problems except on Windows with read locks */ }
677                    }
678                }
679            }
680        }
681    
682        /**
683         * Return the class dependency fix point.
684         * @param classNames class names
685         * @param classPath class path entries
686         * @return class dependency fix point
687         */
688        public Set<String> getDependencyFixPoint(Set<String> classNames,
689                                                 List<String> classPath) {
690            HashSet<String> classesUsed = new HashSet<String>();
691            generateDependencyFixPoint(classNames, classPath, classesUsed, new ILambda<Object,ClassLocation>() {
692                public Object apply(ClassLocation param) {
693                    return null;
694                }
695            },new ILambda<Object,String>() {
696                public Object apply(String param) {
697                    return null;
698                }
699            });
700            return classesUsed;
701        }
702    
703        /**
704         * Find a class in the files to consider and return its class file info.
705         *
706         * @param className name of class to find
707         * @param filesToConsider set of file names (*.class and *.jar) to consider during the search.
708         * @return class file info or null if not found
709         */
710        public static ClassFile findClassInFiles(String className, Set<String> filesToConsider) {
711            ClassFile cf = null;
712    
713            for(String name : filesToConsider) {
714                try {
715                    if (name.endsWith(".jar")) {
716                        // this is a jar file
717                        // Debug.out.println("Looking for "+className+".class in "+name);
718                        JarFile jf = new JarFile(name);
719                        JarEntry je = jf.getJarEntry(className + ".class");
720                        if (je != null) {
721                            // Debug.out.println("\tloading...");
722                            return loadClassFromStream(jf.getInputStream(je));
723                        }
724                    }
725                    else {
726                        // treat as directory
727                        String fileName = name + File.separatorChar + className + ".class";
728                        fileName = fileName.replace('/', File.separatorChar);
729                        // Debug.out.println("Looking for "+fileName);
730                        File f = new File(fileName);
731                        if (f.exists() && f.isFile()) {
732                            // Debug.out.println("\tloading...");
733                            return loadClassFromStream(new FileInputStream(f));
734                        }
735                    }
736                }
737                catch(IOException e) {
738                }
739            }
740    
741            return cf;
742        }
743    
744    
745        /**
746         * Load class file info from the stream.
747         *
748         * @param is input stream
749         * @return class file info
750         *
751         * @throws IOException
752         */
753        public static ClassFile loadClassFromStream(InputStream is) throws IOException {
754            byte[] b = new byte[is.available()];
755            int offset = 0, bytesRead;
756            while((offset < b.length) && ((bytesRead = is.read(b, offset, b.length - offset)) != -1)) {
757                offset += bytesRead;
758            }
759            is.close();
760            return new ClassFile(new ByteArrayInputStream(b));
761        }
762    
763        /**
764         * Returns true if the specified class name matches one of the patterns.
765         * !        = make sure class name does NOT match this pattern (prefix only)
766         * ?        = one character<br>
767         * *        = zero or more characters except '.'
768         * ***      = zero or more characters including '.' java.**.lang
769         * [abc]    = Either a, b or c
770         * [a-mA-M] = A letter from the ranges a-m or A-M (inclusive)
771         * The entire class name has to match the pattern, i.e. the regex characters ^ and $ are implicit.
772         * @param className class name
773         * @param classNamePatterns variable argument list of patterns
774         * @return true if the specified class name matches one of the patterns
775         */
776        public static boolean classNameMatches(String className, String... classNamePatterns) {
777            boolean matchFound = false;
778            for (String pattern: classNamePatterns) {
779                boolean negate = false;
780                String regex = pattern;
781                if (regex.indexOf('!')==0) {
782                    negate = true;
783                    regex = regex.substring(1);
784                }
785                // System.out.println(regex);
786                regex = regex.replaceAll("\\.","\\\\.");
787                // System.out.println(regex);
788                regex = regex.replaceAll("\\?",".");
789                // System.out.println(regex);
790                regex = regex.replaceAll("\\$","\\\\\\$");
791                // System.out.println(regex);
792                regex = regex.replaceAll("\\(","\\\\(");
793                // System.out.println(regex);
794                regex = regex.replaceAll("\\)","\\\\)");
795                // System.out.println(regex);
796                regex = regex.replaceAll("\\*\\*\\*",".`");
797                // System.out.println(regex);
798                regex = regex.replaceAll("^\\*\\*\\*",".`");
799                // System.out.println(regex);
800                regex = regex.replaceAll("\\*","[^\\.]*");
801                // System.out.println(regex);
802                regex = regex.replaceAll("\\.`",".*");
803                // System.out.println(regex);
804                regex = "^"+regex+"$";
805                // System.out.println(regex);
806                boolean matches = className.matches(regex);
807                // System.out.println(className+" matches? "+matches);
808                if (matches) {
809                    if (negate) {
810                        return false;
811                    }
812                    else {
813                        matchFound = true;
814                    }
815                }
816            }
817            return matchFound;
818        }
819    
820        /**
821         * Get the Class instance for the given type. The type can be a class formatted "Lfoo/bar/MyClass;",
822         * a primitive type formatted "I", or an array formatted "[I" or "[Lfoo/bar/MyClass;".
823         * @param type type string
824         * @return Class instance
825         */
826        public static Class<?> getClassFromType(String type) throws ClassNotFoundException {
827            if (type.equals("I")) {
828                return int.class;
829            }
830            if (type.equals("J")) {
831                return long.class;
832            }
833            if (type.equals("F")) {
834                return float.class;
835            }
836            if (type.equals("D")) {
837                return double.class;
838            }
839            if (type.equals("B")) {
840                return byte.class;
841            }
842            if (type.equals("C")) {
843                return char.class;
844            }
845            if (type.equals("S")) {
846                return short.class;
847            }
848            if (type.equals("Z")) {
849                return boolean.class;
850            }
851            String tn = type.substring(1,type.length()-1).replace('/','.');
852            return Class.forName(tn);
853        }
854    
855        /**
856         * Get the Class instance for the given type. The type can be a class formatted "Lfoo/bar/MyClass;",
857         * a primitive type formatted "I", or an array formatted "[I" or "[Lfoo/bar/MyClass;".
858         * @param type type string
859         * @param initialize whether the class needs to be initialized
860         * @param loader the class loader
861         * @return Class instance
862         */
863        public static Class<?> getClassFromType(String type, boolean initialize, ClassLoader loader) throws ClassNotFoundException {
864            if (type.equals("I")) {
865                return int.class;
866            }
867            if (type.equals("J")) {
868                return long.class;
869            }
870            if (type.equals("F")) {
871                return float.class;
872            }
873            if (type.equals("D")) {
874                return double.class;
875            }
876            if (type.equals("B")) {
877                return byte.class;
878            }
879            if (type.equals("C")) {
880                return char.class;
881            }
882            if (type.equals("S")) {
883                return short.class;
884            }
885            if (type.equals("Z")) {
886                return boolean.class;
887            }
888            String tn = type.substring(1,type.length()-1).replace('/','.');
889            return Class.forName(tn, initialize, loader);
890        }
891    
892        /**
893         * Get the class/primitive name for the given type. The type can be a class formatted "Lfoo/bar/MyClass;",
894         * a primitive type formatted "I", or an array formatted "[I" or "[Lfoo/bar/MyClass;".
895         * @param type type string
896         * @return type name
897         */
898        public static String getClassNameFromType(String type) {
899            if (type.equals("I")) {
900                return "int";
901            }
902            if (type.equals("J")) {
903                return "long";
904            }
905            if (type.equals("F")) {
906                return "float";
907            }
908            if (type.equals("D")) {
909                return "double";
910            }
911            if (type.equals("B")) {
912                return "byte";
913            }
914            if (type.equals("C")) {
915                return "char";
916            }
917            if (type.equals("S")) {
918                return "short";
919            }
920            if (type.equals("Z")) {
921                return "boolean";
922            }
923            if (type.charAt(0)=='[') {
924                return type.replace('/','.');
925            }
926            return type.substring(1,type.length()-1).replace('/','.');
927        }
928    
929        /**
930         * Test cases.
931         */
932        public static class ClassFileToolsTest extends TestCase {
933            public void testClassNameMatches() {
934                assertEquals(false, classNameMatches("java.lang.Number", "java.*"));
935                assertEquals(true, classNameMatches("java.lang.Number", "java.***"));
936                assertEquals(false, classNameMatches("java.lang.Number", "*java*"));
937                assertEquals(true, classNameMatches("java.lang.Number", "***java***"));
938                assertEquals(true, classNameMatches("java.lang.Number", "java.lang.Number"));
939                assertEquals(false, classNameMatches("java.lang.Number", "!java.lang.Number"));
940                assertEquals(false, classNameMatches("java.lang.Number", "lang.***"));
941                assertEquals(false, classNameMatches("java.lang.Number",
942                                                     "java.***",
943                                                     "sun.management.***",
944                                                     "!java.lang.Number"));
945                assertEquals(false, classNameMatches("sun.Foobar",
946                                                     "java.***",
947                                                     "sun.management.***",
948                                                     "!java.lang.Number"));
949                assertEquals(true, classNameMatches("sun.management.Foobar",
950                                                    "java.***",
951                                                    "sun.management.***",
952                                                    "!java.lang.Number"));
953                assertEquals(true, classNameMatches("java.Foobar",
954                                                    "java.***",
955                                                    "sun.management.***",
956                                                    "!java.lang.Number"));
957                assertEquals(true, classNameMatches("java.util.Foobar",
958                                                    "java.lang.***",
959                                                    "java.util.*",
960                                                    "java.util.concurrent.***",
961                                                    "java.util.logging.***",
962                                                    "sun.reflect.***",
963                                                    "!java.lang.Number"));
964                assertEquals(true, classNameMatches("java.util.concurrent.Foobar",
965                                                    "java.lang.***",
966                                                    "java.util.*",
967                                                    "java.util.concurrent.***",
968                                                    "java.util.logging.***",
969                                                    "sun.reflect.***",
970                                                    "!java.lang.Number"));
971                assertEquals(true, classNameMatches("java.util.logging.Foobar",
972                                                    "java.lang.***",
973                                                    "java.util.*",
974                                                    "java.util.concurrent.***",
975                                                    "java.util.logging.***",
976                                                    "sun.reflect.***",
977                                                    "!java.lang.Number"));
978                assertEquals(false, classNameMatches("java.util.foobar.Foobar",
979                                                     "java.lang.***",
980                                                     "java.util.*",
981                                                     "java.util.concurrent.***",
982                                                     "java.util.logging.***",
983                                                     "sun.reflect.***",
984                                                     "!java.lang.Number"));
985                assertEquals(true, classNameMatches("java/util/Foobar",
986                                                    "java/util/*"));
987                assertEquals(false, classNameMatches("java.util.foo.Foobar",
988                                                     "java.util.*"));
989                assertEquals(true, classNameMatches("java.util.foo.Foobar",
990                                                    "java.util.foo.[a-mA-M]*"));
991                assertEquals(false, classNameMatches("java.util.foo.Foobar",
992                                                     "java.util.foo.[n-zN-Z]*"));
993                assertEquals(true, classNameMatches("java.util.AbstractCollection",
994                                                    "java.util.*Collection*"));
995                assertEquals(true, classNameMatches("java.lang.ref.Reference$1",
996                                                    "java.lang.ref.Reference$*"));
997                assertEquals(false, classNameMatches("java.lang.ref.ReferenceQueue",
998                                                     "java.lang.ref.Reference$*"));
999                assertEquals(true, classNameMatches("edu.rice.cs.cunit.instrumentors.replay.CompactSynchronizedBlockReplayStrategy",
1000                                                    "***Synchronized*"));
1001                assertEquals(false, classNameMatches("anything",
1002                                                     ""));
1003                assertEquals(false, classNameMatches("anything"));
1004                assertEquals(true, classNameMatches("java.lang.Integer",
1005                                                    "***",
1006                                                    "!java.lang.Object",
1007                                                    "!java.lang.String",
1008                                                    "!java.util.zip.*"));
1009                assertEquals(false, classNameMatches("java.lang.Object",
1010                                                     "***",
1011                                                     "!java.lang.Object",
1012                                                     "!java.lang.String",
1013                                                     "!java.util.zip.*"));
1014                assertEquals(false, classNameMatches("java.lang.String",
1015                                                     "***",
1016                                                     "!java.lang.Object",
1017                                                     "!java.lang.String",
1018                                                     "!java.util.zip.*"));
1019                assertEquals(false, classNameMatches("java.util.zip.Inflater",
1020                                                     "***",
1021                                                     "!java.lang.Object",
1022                                                     "!java.lang.String",
1023                                                     "!java.util.zip.*"));
1024                assertEquals(true, classNameMatches("loadClass(Ljava/lang/String;Z)Ljava/lang/Class;",
1025                                                    "loadClass(Ljava/lang/String;Z)Ljava/lang/Class;"));
1026                assertEquals(true, classNameMatches("java.lang.ref.Finalizer$FinalizerThread",
1027                                                    "java.lang.ref.Finalizer$FinalizerThread"));
1028            }
1029        }
1030    }