001    /**
002     * $Id: ClasspathMonitor.java,v 1.31 2014/01/04 21:56:42 oboehm Exp $
003     *
004     * Copyright (c) 2008 by Oliver Boehm
005     *
006     * Licensed under the Apache License, Version 2.0 (the "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *   http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express orimplied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     *
018     * (c)reated 10.02.2009 by oliver (ob@oasd.de)
019     */
020    
021    package patterntesting.runtime.monitor;
022    
023    import java.io.*;
024    import java.lang.management.ManagementFactory;
025    import java.lang.reflect.*;
026    import java.net.*;
027    import java.util.*;
028    import java.util.concurrent.*;
029    import java.util.jar.*;
030    import java.util.zip.*;
031    
032    import javax.management.*;
033    
034    import org.apache.commons.io.IOUtils;
035    import org.apache.commons.lang.StringUtils;
036    import org.slf4j.*;
037    
038    //import patterntesting.runtime.annotation.ProfileMe;
039    import patterntesting.runtime.jmx.*;
040    import patterntesting.runtime.util.*;
041    
042    /**
043     * To avoid classpath problems like double entries of the same class or resource
044     * in the classpath there are several methods available. <br/>
045     * To get the boot classpath the system property "sun.boot.class.path" is used to
046     * get them. This will work of course only for the SunVM.
047     *
048     * @author <a href="boehm@javatux.de">oliver</a>
049     * @since 10.02.2009
050     * @version $Revision: 1.31 $
051     * @{link "http://www.javaworld.com/javaworld/javatips/jw-javatip105.html"}
052     */
053    public final class ClasspathMonitor extends Thread implements ClasspathMonitorMBean {
054    
055        private static final long serialVersionUID = 20090511L;
056        private static final Logger log = LoggerFactory.getLogger(ClasspathMonitor.class);
057        private static final Executor executor = Executors.newCachedThreadPool();
058        /** the classpath digger. */
059        private final transient ClasspathDigger classpathDigger;
060        private final transient ClassLoader cloader;
061        private final String[] classpath;
062        private String[] loadedClasses = new String[0];
063        private final transient FutureTask<String[]> allClasspathClasses;
064        private final transient FutureTask<Set<String>> unusedClasses;
065    
066        private final List<Class<?>> incompatibleClassList = new ArrayList<Class<?>>();
067        private static ClasspathMonitor instance;
068        private static boolean shutdownHook = false;
069        /** The doublet list (default visibility for testing). */
070        private List<Class<?>> doubletList;
071    
072        static {
073            instance = new ClasspathMonitor();
074        }
075    
076        /**
077         * Instantiates a new classpath monitor.
078         */
079        private ClasspathMonitor() {
080            this.reset();
081            this.classpathDigger = new ClasspathDigger();
082            this.cloader = this.classpathDigger.getClassLoader();
083            this.classpath = this.classpathDigger.getClasspath();
084            this.allClasspathClasses = getFutureCasspathClasses();
085            this.unusedClasses = getFutureUnusedClasses();
086        }
087    
088        private FutureTask<String[]> getFutureCasspathClasses() {
089            Callable<String[]> callable = new Callable<String[]>() {
090                public String[] call() throws Exception {
091                    return getClasspathClassArray();
092                }
093            };
094            FutureTask<String[]> classes = new FutureTask<String[]>(callable);
095            executor.execute(classes);
096            return classes;
097        }
098    
099        private FutureTask<Set<String>> getFutureUnusedClasses() {
100            Callable<Set<String>> callable = new Callable<Set<String>>() {
101                public Set<String> call() throws Exception {
102                    return getClasspathClassSet();
103                }
104            };
105            FutureTask<Set<String>> classes = new FutureTask<Set<String>>(callable);
106            executor.execute(classes);
107            return classes;
108        }
109    
110        /**
111         * Resets the doubletList and maybe later other things.
112         * At the moment it is only used by ClasspathMonitorTest
113         * by the testGetDoubletListPerformance() method.
114         */
115        protected void reset() {
116            doubletList = new ArrayList<Class<?>>();
117        }
118    
119        /**
120         * Yes, it is a Singleton because it offers only some services. So we don't
121         * need the object twice.
122         *
123         * @return the only instance
124         */
125        public static ClasspathMonitor getInstance() {
126            return instance;
127        }
128    
129        /**
130         * Register as m bean.
131         */
132        public static void registerAsMBean() {
133            if (isRegisteredAsMBean()) {
134                log.debug("MBean already registered - registerAsMBean() ignored");
135            }
136            try {
137                MBeanHelper.registerMBean(getInstance());
138            } catch (JMException e) {
139                if (log.isInfoEnabled()) {
140                    log.info(instance + " can't be registered as MBean (" + e
141                                    + ")");
142                }
143            }
144        }
145    
146        /**
147         * If you want to ask JMX if bean is already registered you can ask the
148         * MBeanHelper or you can ask this method.
149         *
150         * @since 1.0
151         * @return true if MBean is already registered.
152         */
153        public static boolean isRegisteredAsMBean() {
154            return MBeanHelper.isRegistered(getInstance());
155        }
156    
157        /**
158         * Which resource.
159         *
160         * @param name the name
161         *
162         * @return the URI
163         *
164         * @see ClasspathMonitorMBean#whichResource(String)
165         */
166        //@ProfileMe
167        @Description("returns the URI of the given resource")
168        public URI whichResource(final String name) {
169            return classpathDigger.whichResource(name);
170        }
171    
172        /**
173         * Which class.
174         *
175         * @param name the name
176         *
177         * @return the URI
178         *
179         * @see patterntesting.runtime.monitor.ClasspathMonitorMBean#whichClass(java.lang.String)
180         */
181        //@ProfileMe
182        public URI whichClass(final String name) {
183            String resource = Converter.classToResource(name);
184            return whichResource(resource);
185        }
186    
187        /**
188         * Where is the given class found in the classpath? Which class was loaded
189         * from which URI?.
190         *
191         * @param clazz the class
192         *
193         * @return file or jar path
194         */
195        //@ProfileMe
196        public URI whichClass(final Class<?> clazz) {
197            return whichClass(clazz.getName());
198        }
199    
200        /**
201         * Returns the jar file or path where the given classname was found.
202         *
203         * @param classname e.g. java.lang.String
204         *
205         * @return jar or path as URI
206         */
207        public URI whichClassPath(final String classname) {
208            String resource = Converter.classToResource(classname);
209            return whichResourcePath(resource);
210        }
211    
212        /**
213         * Returns the jar file or path where the given class was found.
214         *
215         * @param clazz e.g. Sting.class
216         *
217         * @return jar or path as URI
218         */
219        public URI whichClassPath(final Class<?> clazz) {
220            return whichClassPath(clazz.getName());
221        }
222    
223        /**
224         * Which class path.
225         *
226         * @param p the p
227         *
228         * @return the uRI
229         */
230        public URI whichClassPath(final Package p) {
231            String resource = Converter.toResource(p);
232            return whichResourcePath(resource);
233        }
234    
235        /**
236         * Returns the jar file or path where the given resource was found.
237         *
238         * @param resource e.g. log4j.properties
239         *
240         * @return jar or path as URI
241         */
242        public URI whichResourcePath(final String resource) {
243            URI uri = this.whichResource(resource);
244            if (uri == null) {
245                return null;
246            }
247            return ClasspathHelper.getParent(uri, resource);
248        }
249    
250        /**
251         * Which class jar.
252         *
253         * @param clazz the clazz
254         *
255         * @return the jar file
256         */
257        public JarFile whichClassJar(final Class<?> clazz) {
258            return whichClassJar(clazz.getName());
259        }
260    
261        /**
262         * Which class jar.
263         *
264         * @param classname the classname
265         *
266         * @return the jar file
267         */
268        public JarFile whichClassJar(final String classname) {
269            String resource = Converter.classToResource(classname);
270            return whichResourceJar(resource);
271        }
272    
273        /**
274         * Which resource jar.
275         *
276         * @param resource the resource
277         *
278         * @return the jar file
279         */
280        public JarFile whichResourceJar(final String resource) {
281            return whichResourceJar(this.whichResourcePath(resource));
282        }
283    
284        /**
285         * Which resource jar.
286         *
287         * @param resource the resource
288         *
289         * @return the jar file
290         */
291        public static JarFile whichResourceJar(final URI resource) {
292            if (resource == null) {
293                return null;
294            }
295            File file = Converter.toFile(resource);
296            try {
297                return new JarFile(file);
298            } catch (IOException ioe) {
299                log.debug("can't read " + file, ioe);
300                return null;
301            }
302        }
303    
304        /**
305         * Gets the resources.
306         *
307         * @param name the name
308         * @return the resources
309         */
310        //@ProfileMe
311        public Enumeration<URL> getResources(final String name) {
312            try {
313                Enumeration<URL> resources = cloader.getResources(name);
314                if (!resources.hasMoreElements()) {
315                    if (name.startsWith("/")) {
316                        return getResources(name.substring(1));
317                    }
318                    if (log.isDebugEnabled()) {
319                        log.debug(name + " not found in classpath");
320                    }
321                }
322                return resources;
323            } catch (IOException ioe) {
324                log.info(name + " not found in classpath", ioe);
325                return null;
326            }
327        }
328    
329        /**
330         * Gets the resource.
331         *
332         * @param name the name
333         *
334         * @return the resource
335         */
336        public static URL getResource(final String name) {
337            return ClasspathMonitor.class.getResource(name);
338        }
339    
340        /**
341         * @param name the name of the resource
342         * @return number of resources
343         * @see ClasspathMonitorMBean#getNoResources(java.lang.String)
344         */
345        //@ProfileMe
346        public int getNoResources(final String name) {
347            Enumeration<URL> resources = getResources(name);
348            if (resources == null) {
349                return 0;
350            }
351            int n = 0;
352            while (resources.hasMoreElements()) {
353                n++;
354                resources.nextElement();
355            }
356            if (log.isTraceEnabled()) {
357                log.trace(n + " element(s) of " + name + " found in classpath");
358            }
359            return n;
360        }
361    
362        /**
363         * Gets the no classes.
364         *
365         * @param cl the cl
366         *
367         * @return the no classes
368         */
369        public int getNoClasses(final Class<?> cl) {
370            return getNoClasses(cl.getName());
371        }
372    
373        /**
374         * @param classname the classname
375         * @return number of classes
376         * @see ClasspathMonitorMBean#getNoClasses(java.lang.String)
377         */
378        public int getNoClasses(final String classname) {
379            return getNoResources(Converter.classToResource(classname));
380        }
381    
382        /**
383         * Checks if is doublet.
384         *
385         * @param name the name
386         *
387         * @return true, if checks if is doublet
388         *
389         * @see patterntesting.runtime.monitor.ClasspathMonitorMBean#isDoublet(java.lang.String)
390         */
391        //@ProfileMe
392        public boolean isDoublet(final String name) {
393            Enumeration<URL> resources = getResources(name);
394            if ((resources == null) || !resources.hasMoreElements()) {
395                throw new NoSuchElementException(name);
396            }
397            resources.nextElement();
398            if (resources.hasMoreElements()) {
399                logDoublets(name);
400                return true;
401            } else {
402                return false;
403            }
404        }
405    
406        /**
407         * Is the given class a doublet, i.e. can it be found more than once in the
408         * classpath?
409         *
410         * @param clazz the class to be checked
411         * @return true if class is found more than once in the classpath
412         */
413        //@ProfileMe
414        public boolean isDoublet(final Class<?> clazz) {
415            String classname = clazz.getName();
416            String resource = Converter.classToResource(classname);
417            return isDoublet(resource);
418        }
419    
420        /**
421         * Gets the first doublet.
422         *
423         * @param name the name
424         *
425         * @return the first doublet
426         *
427         * @see patterntesting.runtime.monitor.ClasspathMonitorMBean#getFirstDoublet(java.lang.String)
428         */
429        public URL getFirstDoublet(final String name) {
430            return getDoublet(name, 1);
431        }
432    
433        /**
434         * Gets the first doublet.
435         *
436         * @param clazz the class
437         * @return the first doublet
438         */
439        public URL getFirstDoublet(final Class<?> clazz) {
440            return getDoublet(clazz, 1);
441        }
442    
443        /**
444         * Gets the doublet.
445         *
446         * @param name the name
447         * @param nr the nr
448         *
449         * @return the doublet
450         *
451         * @see patterntesting.runtime.monitor.ClasspathMonitorMBean#getDoublet(java.lang.String,
452         * int)
453         */
454        public URL getDoublet(final String name, final int nr) {
455            Enumeration<URL> resources = getResources(name);
456            for (int i = 0; resources.hasMoreElements(); i++) {
457                URL url = resources.nextElement();
458                if (i == nr) {
459                    return url;
460                }
461            }
462            return null;
463        }
464    
465        /**
466         * Gets the doublet.
467         *
468         * @param clazz the clazz
469         * @param nr the nr
470         *
471         * @return the doublet
472         */
473        public URL getDoublet(final Class<?> clazz, final int nr) {
474            String resource = Converter.classToResource(clazz.getName());
475            return getDoublet(resource, nr);
476        }
477    
478        /**
479         * Log doublets.
480         *
481         * @param name the name
482         */
483        private void logDoublets(final String name) {
484            if (log.isTraceEnabled()) {
485                List<URL> doublets = new Vector<URL>();
486                Enumeration<URL> resources = getResources(name);
487                while (resources.hasMoreElements()) {
488                    URL url = resources.nextElement();
489                    doublets.add(url);
490                }
491                log.trace(name + " doublets found: " + doublets);
492            }
493        }
494    
495        /**
496         * Gets the doublet list.
497         *
498         * @return the doublet list
499         */
500        //@ProfileMe
501        public synchronized List<Class<?>> getDoubletList() {
502            if (multiThreadingEnabled) {
503                return getDoubletListParallel();
504            } else {
505                return getDoubletListSerial();
506            }
507        }
508    
509        /**
510         * Looks for each loaded class if it is a doublet or not. For the
511         * performance reason it looks in the doubletList from the last time if it
512         * is already found. This is done because normally the number of doublets
513         * does not decrease.
514         *
515         * @return a sorted list of found doublets
516         */
517        //@ProfileMe
518        protected synchronized List<Class<?>> getDoubletListSerial() {
519            List<Class<?>> loadedClassList = this.getLoadedClassList();
520            for (Class<?> clazz : loadedClassList) {
521                // TODO this can be parallized
522                if (doubletList.contains(clazz)) {
523                    continue;
524                }
525                try {
526                    if (this.isDoublet(clazz)) {
527                        doubletList.add(clazz);
528                    }
529                } catch (NoSuchElementException nsee) {
530                    if (log.isTraceEnabled()) {
531                        log.trace(clazz + " not found -> ignored");
532                    }
533                }
534            }
535            Collections.sort(doubletList, new ObjectComparator());
536            return doubletList;
537        }
538    
539        /**
540         * Gets the doublets.
541         *
542         * @return the doublets
543         *
544         * @see ClasspathMonitorMBean#getDoublets()
545         */
546        //@ProfileMe
547        public String[] getDoublets() {
548            log.debug("calculating doublets...");
549            List<Class<?>> classes = this.getDoubletList();
550            String[] doublets = new String[classes.size()];
551            for (int i = 0; i < doublets.length; i++) {
552                doublets[i] = classes.get(i).toString();
553            }
554            return doublets;
555        }
556    
557        /**
558         * Gets the doublet classpath.
559         *
560         * @return the doublet classpath
561         *
562         * @see ClasspathMonitorMBean#getDoubletClasspath()
563         */
564        //@ProfileMe
565        public String[] getDoubletClasspath() {
566            try {
567                log.debug("calculating doublet-classpath...");
568                Set<URI> classpathSet = getClasspathSet(this.getDoubletList());
569                return getAsArray(classpathSet);
570            } catch (ConcurrentModificationException e) {
571                log.info(e + " happens while sorting classes - will try it again...");
572                ThreadUtil.sleep();
573                return getDoubletClasspath();
574            }
575        }
576    
577        /**
578         * Gets the classpath set.
579         *
580         * @param classes the classes
581         *
582         * @return the classpath set
583         */
584        private SortedSet<URI> getClasspathSet(final List<Class<?>> classes) {
585            SortedSet<URI> clpath = new TreeSet<URI>();
586            for (Class<?> cl : classes) {
587                String resource = Converter.toResource(cl);
588                Enumeration<URL> resources = this.getResources(resource);
589                while (resources.hasMoreElements()) {
590                    URL url = resources.nextElement();
591                    URI path = Converter.toURI(url);
592                    clpath.add(ClasspathHelper.getParent(path, resource));
593                }
594            }
595            return clpath;
596        }
597    
598        /**
599         * Dumps the internal fields of the classloader (after an idea from
600         * "Java ist auch eine Insel").
601         *
602         * {@link "http://www.galileocomputing.de/openbook/javainsel6/javainsel_21_003.htm#mj7e2d89f4b96fde1900bc09fd1db83fb1"}
603         *
604         * @return the class loader details
605         */
606        //@ProfileMe
607        public String getClassLoaderDetails() {
608            StringBuffer sbuf = new StringBuffer("dump of " + cloader + ":\n");
609            for (Class<?> cl = this.cloader.getClass(); cl != null; cl = cl
610                    .getSuperclass()) {
611                sbuf.append('\t');
612                dumpFields(sbuf, cl, this.cloader);
613            }
614            return sbuf.toString().trim();
615        }
616    
617        /**
618         * Dump fields.
619         *
620         * @param sbuf the sbuf
621         * @param cl the cl
622         * @param obj the obj
623         */
624        //@ProfileMe
625        private static void dumpFields(final StringBuffer sbuf, final Class<?> cl,
626                final Object obj) {
627            Field[] fields = cl.getDeclaredFields();
628            AccessibleObject.setAccessible(fields, true);
629            for (int i = 0; i < fields.length; i++) {
630                sbuf.append(fields[i]);
631                sbuf.append(" = ");
632                try {
633                    sbuf.append(fields[i].get(obj));
634                } catch (Exception e) {
635                    sbuf.append("<" + e + ">");
636                }
637                sbuf.append('\n');
638            }
639        }
640    
641        /**
642         * Checks if is classloader supported.
643         *
644         * @return true if it is a known classloader
645         */
646        public boolean isClassloaderSupported() {
647            return this.classpathDigger.isClassloaderSupported();
648        }
649    
650        /**
651         * Returns some information about the classloader. At least the user should
652         * be informed if it is a unknown classloader which is not supported or not
653         * tested.
654         *
655         * @return e.g. "unknown classloader xxx - classpath can be wrong"
656         */
657        public String getClassloaderInfo() {
658            String info = this.isClassloaderSupported() ? "supported" : "unsupported";
659            return this.cloader.getClass().getName() + " (" + info + ")";
660        }
661    
662        /**
663         * Gets the loaded packages.
664         *
665         * @return the loaded packages
666         *
667         * @see ClasspathMonitorMBean#getLoadedPackages()
668         */
669        public String[] getLoadedPackages() {
670            Package[] packages = this.classpathDigger.getLoadedPackageArray();
671            String[] strings = new String[packages.length];
672            for (int i = 0; i < packages.length; i++) {
673                strings[i] = packages[i].toString();
674            }
675            Arrays.sort(strings);
676            return strings;
677        }
678    
679        /**
680         * This method is used by PatternTesting Samples (in packages.jsp). So it
681         * is left here as wrapper around ClasspathDigger.
682         *
683         * @return array of packages
684         */
685        public Package[] getLoadedPackageArray() {
686            return this.classpathDigger.getLoadedPackageArray();
687        }
688    
689        /**
690         * If you want to dump all packages you can use this method. The output will
691         * be sorted.
692         *
693         * @return each package in a single line
694         */
695        public String getLoadedPackagesAsString() {
696            String[] packages = getLoadedPackages();
697            StringBuffer sbuf = new StringBuffer();
698            for (int i = 0; i < packages.length; i++) {
699                sbuf.append(packages[i]);
700                sbuf.append('\n');
701            }
702            return sbuf.toString().trim();
703        }
704    
705        /**
706         * Returns a list of classes which were loaded by the classloader.
707         *
708         * @return list of classes
709         */
710        //@ProfileMe
711        public synchronized List<Class<?>> getLoadedClassList() {
712            List<Class<?>> classlist = classpathDigger.getLoadedClassList();
713            return classlist;
714        }
715    
716        /**
717         * As MBean a string array could be displayed by the 'jconsole'. A class
718         * array not.
719         *
720         * @return the classes as sorted string array
721         */
722        public synchronized String[] getLoadedClasses() {
723            List<Class<?>> classes = getLoadedClassList();
724            if (classes.size() != loadedClasses.length) {
725                loadedClasses = new String[classes.size()];
726                for (int i = 0; i < loadedClasses.length; i++) {
727                    loadedClasses[i] = classes.get(i).toString();
728                }
729                Arrays.sort(loadedClasses);
730            }
731            return loadedClasses;
732        }
733    
734        /**
735         * Gets the loaded classes as string.
736         *
737         * @return the loaded classes as string
738         */
739        //@ProfileMe
740        public String getLoadedClassesAsString() {
741            List<Class<?>> classes = getLoadedClassList();
742            Collections.sort(classes, new ObjectComparator());
743            StringBuffer sbuf = new StringBuffer();
744            for (Iterator<Class<?>> i = classes.iterator(); i.hasNext();) {
745                sbuf.append(i.next().toString().trim());
746                sbuf.append('\n');
747            }
748            return sbuf.toString().trim();
749        }
750    
751        /**
752         * Checks if the given classname is loaded.
753         * Why does we use not Class as parameter here? If you would allow a
754         * parameter of type "Class" this class will be problably loaded before
755         * and this method will return always true!
756         *
757         * @param classname name of the class
758         * @return true if class is loaded
759         * @see patterntesting.runtime.monitor.ClasspathMonitorMBean#isLoaded(java.lang.String)
760         */
761        public boolean isLoaded(final String classname) {
762            return this.classpathDigger.isLoaded(classname);
763        }
764    
765        /**
766         * Unused classes are classes which are not loaded but which are found in
767         * the classpath.
768         *
769         * @return unused classes
770         *
771         * @see #getLoadedClasses()
772         * @see #getClasspathClasses()
773         */
774        //@ProfileMe
775        public String[] getUnusedClasses() {
776            List<Class<?>> classes = getLoadedClassList();
777            Collection<String> used = new ArrayList<String>();
778            Set<String> unusedSet = this.getUnusedClassSet();
779            for (Class<?> cl : classes) {
780                String classname = cl.getName();
781                if (unusedSet.contains(classname)) {
782                    used.add(classname);
783                }
784            }
785            unusedSet.removeAll(used);
786            String[] unused = new String[unusedSet.size()];
787            unusedSet.toArray(unused);
788            return unused;
789        }
790    
791        private Set<String> getUnusedClassSet() {
792    //        return this.unusedClassSet;
793            try {
794                return this.unusedClasses.get();
795            } catch (InterruptedException e) {
796                log.warn("no result from " + this.unusedClasses, e);
797                return this.getClasspathClassSet();
798            } catch (ExecutionException e) {
799                log.warn("no result from " + this.unusedClasses, e);
800                return this.getClasspathClassSet();
801            }
802        }
803    
804        /**
805         * Scans the classpath for all classes.
806         * For performance reason we return no longer a copy but the origin array.
807         * Don't do changes for the returned array!
808         *
809         * @return all classes as String array
810         */
811        public String[] getClasspathClasses() {
812            try {
813                return this.allClasspathClasses.get();
814            } catch (InterruptedException e) {
815                log.warn("no result from " + this.allClasspathClasses, e);
816                return this.getClasspathClassArray();
817            } catch (ExecutionException e) {
818                log.warn("no result from " + this.allClasspathClasses, e);
819                return this.getClasspathClassArray();
820            }
821        }
822    
823        private Set<String> getClasspathClassSet() {
824            String[] classes = getClasspathClasses();
825            return new TreeSet<String>(Arrays.asList(classes));
826        }
827    
828        /**
829         * Gets the classes of a given package name as collection.
830         *
831         * @param packageName the package name
832         * @return the classpath class list
833         */
834        public Collection<String> getClasspathClassList(final String packageName) {
835            Collection<String> classlist = new ArrayList<String>();
836            String[] classes = this.getClasspathClasses();
837            for (int i = 0; i < classes.length; i++) {
838                if (classes[i].startsWith(packageName)) {
839                    classlist.add(classes[i]);
840                }
841            }
842            return classlist;
843        }
844    
845        /**
846         * Gets the classes of the given type.
847         *
848         * @param <T> the generic type
849         * @param packageName the package name
850         * @param type the type
851         * @return the classes
852         */
853        @SuppressWarnings("unchecked")
854        public <T> Collection<Class<T>> getClassList(final String packageName, final Class<T> type) {
855            Collection<Class<T>> classes = new ArrayList<Class<T>>();
856            Collection<Class<?>> concreteClasses = getConcreteClassList(packageName);
857            for (Class<?> clazz : concreteClasses) {
858                if (type.isAssignableFrom(clazz)) {
859                    classes.add(((Class<T>) clazz));
860                    log.trace("subclass of {} found: {}", type, clazz);
861                }
862            }
863            return classes;
864        }
865    
866        /**
867         * Gets a list of concrete classes. These are classes which can be
868         * instantiated, i.e. they are not abstract and have a default
869         * constructor.
870         *
871         * @param packageName the package name
872         * @return the concrete class list
873         */
874        public Collection<Class<?>> getConcreteClassList(final String packageName) {
875            assert packageName != null;
876            Collection<String> classList = this.getClasspathClassList(packageName);
877            Collection<Class<?>> classes = new ArrayList<Class<?>>(classList.size());
878            for (String classname : classList) {
879                try {
880                    Class<?> clazz = Class.forName(classname);
881                    if (!canBeInstantiated(clazz)) {
882                        if (log.isTraceEnabled()) {
883                            log.trace(clazz + " will be ignored (can't be instantiated)");
884                        }
885                        continue;
886                    }
887                    classes.add(clazz);
888                } catch (ClassNotFoundException e) {
889                    log.info(classname + " will be ignored (" + e.getMessage() + ")");
890                }
891            }
892            return classes;
893        }
894    
895    //    /**
896    //     * Gets a list of concrete classes of the given type.
897    //     * These are classes which can be instantiated, i.e. they are not abstract
898    //     * and have a default constructor.
899    //     *
900    //     * @param packageName the package name
901    //     * @param type the type
902    //     * @return the classes
903    //     */
904    //    public Collection<Class<?>> getConcreteClassList(final String packageName, Class<?> type) {
905    //        Collection<Class<?>> classes = new ArrayList<Class<?>>();
906    //        Collection<Class<?>> concreteClasses = this.getConcreteClassList(packageName);
907    //        for (Class<?> clazz : concreteClasses) {
908    //            if (type.isAssignableFrom(clazz)) {
909    //                classes.add(clazz);
910    //                if (log.isTraceEnabled()) {
911    //                    log.trace(type + " found: " + clazz);
912    //                }
913    //            }
914    //        }
915    //        return classes;
916    //    }
917    
918        private static boolean canBeInstantiated(final Class<?> clazz) {
919            if (clazz.isInterface()) {
920                return false;
921            }
922            int mod = clazz.getModifiers();
923            if (Modifier.isAbstract(mod)) {
924                return false;
925            }
926            try {
927                clazz.getConstructor();
928                return true;
929            } catch (SecurityException e) {
930                log.info("can't get default ctor of " + clazz, e);
931                return false;
932            } catch (NoSuchMethodException e) {
933                return false;
934            }
935        }
936    
937        /**
938         * Gets the classpath class set (sorted).
939         *
940         * @return the classpath class set
941         */
942        //@ProfileMe
943        private Set<String> createClasspathClassSet() {
944            Set<String> classSet = new TreeSet<String>();
945            for (int i = 0; i < classpath.length; i++) {
946                addClasses(classSet, new File(classpath[i]));
947            }
948            return classSet;
949        }
950    
951        private String[] getClasspathClassArray() {
952            Set<String> classSet = createClasspathClassSet();
953            return classSet.toArray(new String[classSet.size()]);
954        }
955    
956        /**
957         * Adds the classes.
958         *
959         * @param classSet the class set
960         * @param path the path
961         */
962        private static void addClasses(final Set<String> classSet, final File path) {
963            if (log.isTraceEnabled()) {
964                log.trace("adding classes from " + path.getAbsolutePath() + "...");
965            }
966            try {
967                if (path.isDirectory()) {
968                    addClassesFromDir(classSet, path);
969                } else {
970                    addClassesFromArchive(classSet, path);
971                }
972            } catch (IOException e) {
973                log.warn("can't add classes from " + path.getAbsolutePath());
974            }
975        }
976    
977        /**
978         * Adds the classes from dir.
979         *
980         * @param classSet the class set
981         * @param dir the dir
982         *
983         * @throws IOException Signals that an I/O exception has occurred.
984         */
985        //@ProfileMe
986        private static void addClassesFromDir(final Set<String> classSet, final File dir)
987                throws IOException {
988            ClassWalker classWalker = new ClassWalker(dir);
989            Collection<String> classes = classWalker.getClasses();
990            classSet.addAll(classes);
991        }
992    
993        /**
994         * Adds the classes from archive.
995         *
996         * @param classSet the class set
997         * @param archive the archive
998         *
999         * @throws IOException Signals that an I/O exception has occurred.
1000         */
1001        //@ProfileMe
1002        private static void addClassesFromArchive(final Set<String> classSet, final File archive)
1003                throws IOException {
1004            ZipFile zipFile = new ZipFile(archive);
1005            try {
1006                Enumeration<? extends ZipEntry> entries = zipFile.entries();
1007                while (entries.hasMoreElements()) {
1008                    ZipEntry entry = entries.nextElement();
1009                    String name = entry.getName();
1010                    if (name.endsWith(".class")) {
1011                        classSet.add(Converter.resourceToClass(name));
1012                    }
1013                }
1014            } finally {
1015                zipFile.close();
1016            }
1017        }
1018    
1019        /**
1020         * Gets the loaded classpath (without the bootclasspath) as sorted set. <br/>
1021         * TODO: this method lets room for more performance (e.g. using a
1022         * Hashtable<Class, URI> for caching)
1023         *
1024         * @return the loaded classpath (excluding the bootclasspath)
1025         */
1026        //@ProfileMe
1027        public SortedSet<URI> getUsedClasspathSet() {
1028            List<Class<?>> loadedClassList = this.getLoadedClassList();
1029            SortedSet<URI> usedClasspathSet = new TreeSet<URI>();
1030            for (Class<?> clazz : loadedClassList) {
1031                URI classUri = this.whichClass(clazz);
1032                if (classUri != null) {
1033                    URI classpathURI = ClasspathHelper.getParent(classUri, clazz);
1034                    usedClasspathSet.add(classpathURI);
1035                }
1036            }
1037            return usedClasspathSet;
1038        }
1039    
1040        /**
1041         * It might be that the used classpath changes during the calculation of it.
1042         * So it is only a good approximation how it looks at the moment of the
1043         * call.
1044         *
1045         * @return the used classpath
1046         *
1047         * @see ClasspathMonitor#getUsedClasspath()
1048         */
1049        //@ProfileMe
1050        public String[] getUsedClasspath() {
1051            log.debug("calculating used classpath...");
1052            SortedSet<URI> classpathSet = this.getUsedClasspathSet();
1053            return getAsArray(classpathSet);
1054        }
1055    
1056        /**
1057         * Gets the as array.
1058         *
1059         * @param classpathSet the classpath set
1060         *
1061         * @return the as array
1062         */
1063        private static String[] getAsArray(final Set<URI> classpathSet) {
1064            String[] classpath = new String[classpathSet.size()];
1065            Object[] a = classpathSet.toArray();
1066            for (int i = 0; i < classpath.length; i++) {
1067                URI uri = (URI) a[i];
1068                classpath[i] = uri.getPath();
1069                if (StringUtils.isEmpty(classpath[i])) {
1070                    classpath[i] = StringUtils.substringAfterLast(uri.toString(),
1071                            ":");
1072                }
1073                if (classpath[i].endsWith(File.separator)) {
1074                    classpath[i] = classpath[i].substring(0,
1075                            (classpath[i].length() - 1));
1076                }
1077            }
1078            Arrays.sort(classpath);
1079            return classpath;
1080        }
1081    
1082        /**
1083         * The unused classpath is this path which are not used in past. <br/>
1084         *
1085         * @return the unused classpath (sorted)
1086         */
1087        //@ProfileMe
1088        public String[] getUnusedClasspath() {
1089            log.debug("calculating unused classpath...");
1090            SortedSet<String> unused = new TreeSet<String>();
1091            log.trace(Arrays.class + " loaded (to get corrected used classpath");
1092            String[] used = this.getUsedClasspath();
1093            for (int i = 0; i < classpath.length; i++) {
1094                String path = classpath[i];
1095                if (Arrays.binarySearch(used, path) < 0) {
1096                    unused.add(path);
1097                }
1098            }
1099            String[] a = new String[unused.size()];
1100            return unused.toArray(a);
1101        }
1102    
1103        /**
1104         * To get the boot classpath the sytem property "sun.boot.class.path" is
1105         * used to get them. This will work of course only for the SunVM.
1106         *
1107         * @return the boot classpath as String array
1108         */
1109        public String[] getBootClasspath() {
1110            return this.classpathDigger.getBootClasspath();
1111        }
1112    
1113        /**
1114         * The classpath is stored in the system property "java.class.path". And
1115         * this is the classpath which will be returned.
1116         *
1117         * @return the classpath as String array
1118         */
1119        public String[] getClasspath() {
1120            return this.classpath;
1121            //return Arrays.copyOf(this.classpath, this.classpath.length);
1122        }
1123    
1124        /**
1125         * @param classname
1126         *            name of the class
1127         * @return the serialVersionUID
1128         * @throws IllegalAccessException
1129         *             if class can't be accessed
1130         * @see patterntesting.runtime.monitor.ClasspathMonitorMBean#getSerialVersionUID(java.lang.String)
1131         */
1132        public Long getSerialVersionUID(final String classname)
1133                throws IllegalAccessException {
1134            try {
1135                Class<?> clazz = Class.forName(classname);
1136                return getSerialVersionUID(clazz);
1137            } catch (ClassNotFoundException e) {
1138                log.info(classname + " not found (" + e.getLocalizedMessage()
1139                            + ")");
1140                return null;
1141            }
1142        }
1143    
1144        /**
1145         * Gets the serial version uid.
1146         *
1147         * @param clazz the clazz
1148         *
1149         * @return the serial version uid
1150         *
1151         * @throws IllegalAccessException the illegal access exception
1152         */
1153        public Long getSerialVersionUID(final Class<?> clazz)
1154                throws IllegalAccessException {
1155            try {
1156                Field field = ReflectionHelper.getField(clazz, "serialVersionUID");
1157                return (Long) field.get(null);
1158            } catch (NoSuchFieldException e) {
1159                log.debug(clazz + " has no serialVersionUID");
1160                return null;
1161            }
1162        }
1163    
1164        /**
1165         * If a MANIFEST is found for a given class the attributes in this
1166         * file should be returned as a string array.
1167         * E.g. for commons-lang-2.3.jar the string array may looks like
1168         * <pre>
1169         * Manifest-Version: 1.0
1170         * Ant-Version: Apache Ant 1.6.5
1171         * Created-By: 1.3.1_09-85 ("Apple Computer, Inc.")
1172         * Package: org.apache.commons.lang
1173         * Extension-Name: commons-lang
1174         * Specification-Version: 2.3
1175         * Specification-Vendor: Apache Software Foundation
1176         * Specification-Title: Commons Lang
1177         * Implementation-Version: 2.3
1178         * Implementation-Vendor: Apache Software Foundation
1179         * Implementation-Title: Commons Lang
1180         * Implementation-Vendor-Id: org.apache
1181         * X-Compile-Source-JDK: 1.3
1182         * X-Compile-Target-JDK: 1.1
1183         * </pre>
1184         *
1185         * @param clazz the clazz
1186         *
1187         * @return the attribute entries of the Manifest
1188         * (or emtpy array if no Manifest or no attributes are found)
1189         */
1190        public String[] getManifestEntries(final Class<?> clazz) {
1191            return this.getManifestEntries(clazz.getName());
1192        }
1193    
1194        /**
1195         * If a MANIFEST is found for a given class the attributes in this
1196         * file should be returned as a string array.
1197         * E.g. for commons-lang-2.3.jar the string array may looks like
1198         * <pre>
1199         * Manifest-Version: 1.0
1200         * Ant-Version: Apache Ant 1.6.5
1201         * Created-By: 1.3.1_09-85 ("Apple Computer, Inc.")
1202         * Package: org.apache.commons.lang
1203         * Extension-Name: commons-lang
1204         * Specification-Version: 2.3
1205         * Specification-Vendor: Apache Software Foundation
1206         * Specification-Title: Commons Lang
1207         * Implementation-Version: 2.3
1208         * Implementation-Vendor: Apache Software Foundation
1209         * Implementation-Title: Commons Lang
1210         * Implementation-Vendor-Id: org.apache
1211         * X-Compile-Source-JDK: 1.3
1212         * X-Compile-Target-JDK: 1.1
1213         * </pre>
1214         *
1215         * @param classname (must be in the classpath, otherwise you'll get a
1216         * IllegalArgumentException)
1217         *
1218         * @return the attribute entries of the Manifest
1219         * (or emtpy array if no Manifest or no attributes are found)
1220         */
1221        public String[] getManifestEntries(final String classname) {
1222            URI classpathURI = whichClassPath(classname);
1223            if (classpathURI == null) {
1224                throw new IllegalArgumentException(classname + " not found in classpath");
1225            }
1226            File path = Converter.toFile(classpathURI);
1227            return getManifestEntries(path);
1228        }
1229    
1230        /**
1231         * Gets the manifest entries.
1232         *
1233         * @param path e.g. a classpath or a JAR file
1234         *
1235         * @return the attribute entries of the manifest file
1236         */
1237        private String[] getManifestEntries(final File path) {
1238            if (path.isFile()) {
1239                try {
1240                    return getManifestEntries(new JarFile(path));
1241                } catch (IOException ioe) {
1242                    log.info("no manifest found in " + path, ioe);
1243                    return new String[0];
1244                }
1245            }
1246            File manifestFile = new File(path, "META-INF/MANIFEST.MF");
1247            if (!manifestFile.exists()) {
1248                if (log.isDebugEnabled()) {
1249                    log.debug(manifestFile + " does not exist");
1250                }
1251                return new String[0];
1252            }
1253            try {
1254                InputStream istream = new FileInputStream(manifestFile);
1255                Manifest manifest = new Manifest(istream);
1256                IOUtils.closeQuietly(istream);
1257                return getManifestEntries(manifest);
1258            } catch (IOException ioe) {
1259                log.info("can't read " + manifestFile, ioe);
1260                return new String[0];
1261            }
1262        }
1263    
1264        /**
1265         * We look for the manifest file for the given JAR file.
1266         *
1267         * @param jarfile the jarfile
1268         *
1269         * @return the entries as string array (may be an empty array if no
1270         * manifest file was found)
1271         */
1272        private String[] getManifestEntries(final JarFile jarfile) {
1273            Manifest manifest;
1274            try {
1275                manifest = jarfile.getManifest();
1276            } catch (IOException ioe) {
1277                log.info("no manifest found in " + jarfile, ioe);
1278                return new String[0];
1279            }
1280            if (manifest == null) {
1281                if (log.isDebugEnabled()) {
1282                    log.debug("no manifest found in " + jarfile);
1283                }
1284                return new String[0];
1285            }
1286            return getManifestEntries(manifest);
1287        }
1288    
1289        /**
1290         * Gets the manifest entries.
1291         *
1292         * @param manifest the manifest
1293         *
1294         * @return the manifest entries
1295         */
1296        private String[] getManifestEntries(final Manifest manifest) {
1297            Attributes attributes = manifest.getMainAttributes();
1298            String[] manifestEntries = new String[attributes.size()];
1299            Set<Map.Entry<Object, Object>> entries = attributes.entrySet();
1300            Iterator<Map.Entry<Object, Object>> iterator = entries.iterator();
1301            for (int i = 0; i < manifestEntries.length; i++) {
1302                Map.Entry<Object, Object> entry = iterator.next();
1303                manifestEntries[i] = entry.getKey() + ": " + entry.getValue();
1304            }
1305            return manifestEntries;
1306        }
1307    
1308        /**
1309         * Looks for each doublet if it has a different doublets. For the
1310         * performance reason it looks in the incompatibleClassList from the last
1311         * time if it is already found. This is done because normally the number of
1312         * incompatible classed does not decrease.
1313         *
1314         * @return a sorted list of incompatible classes
1315         */
1316        //@ProfileMe
1317        public synchronized List<Class<?>> getIncompatibleClassList() {
1318            List<Class<?>> doublets = this.getDoubletList();
1319            for (Class<?> clazz : doublets) {
1320                if (incompatibleClassList.contains(clazz)) {
1321                    continue;
1322                }
1323                String resource = Converter.classToResource(clazz.getName());
1324                Enumeration<URL> resources = getResources(resource);
1325                URL url = resources.nextElement();
1326                ArchivEntry archivEntry = new ArchivEntry(url);
1327                while (resources.hasMoreElements()) {
1328                    url = resources.nextElement();
1329                    ArchivEntry doubletEntry = new ArchivEntry(url);
1330                    if (archivEntry.equals(doubletEntry)) {
1331                        incompatibleClassList.add(clazz);
1332                        break;
1333                    }
1334                }
1335            }
1336            return incompatibleClassList;
1337        }
1338    
1339        /**
1340         * Incompatible classes are doublets with different byte codes.
1341         *
1342         * @return doublet classes with different byte codes.
1343         */
1344        public String[] getIncompatibleClasses() {
1345            log.debug("calculating incompatible classes...");
1346            List<Class<?>> classList = this.getIncompatibleClassList();
1347            String[] classes = new String[classList.size()];
1348            for (int i = 0; i < classes.length; i++) {
1349                classes[i] = classList.get(i).toString();
1350            }
1351            return classes;
1352        }
1353    
1354        /**
1355         * Gets the incompatible classpath.
1356         *
1357         * @return the classpathes where incompatible classes were found
1358         */
1359        public String[] getIncompatibleClasspath() {
1360            log.debug("calculating doublet-classpath...");
1361            Set<URI> classpathSet = getClasspathSet(this.getIncompatibleClassList());
1362            return getAsArray(classpathSet);
1363        }
1364    
1365        /**
1366         * You can register the instance as shutdown hook. If the VM is terminated
1367         * the attributes are logged and dumped to a text file in the tmp directory.
1368         *
1369         * @see #logMe()
1370         * @see #dumpMe()
1371         * @see #addMeAsShutdownHook()
1372         * @see #removeMeAsShutdownHook()
1373         */
1374        public static synchronized void addAsShutdownHook() {
1375            Runtime.getRuntime().addShutdownHook(instance);
1376            shutdownHook = true;
1377            log.debug("{} registered as shutdown hook", instance);
1378        }
1379    
1380        /**
1381         * If you have registered the instance you can now de-register it.
1382         *
1383         * @since 1.0
1384         * @see #addAsShutdownHook()
1385         * @see #removeMeAsShutdownHook()
1386         */
1387        public static synchronized void removeAsShutdownHook() {
1388            Runtime.getRuntime().removeShutdownHook(instance);
1389            shutdownHook = false;
1390            log.debug("{} de-registered as shutdown hook", instance);
1391        }
1392    
1393        /**
1394         * To be able to register the instance as shutdown hook via JMX we can't
1395         * use a static method - this is the reason why this additional method
1396         * was added.
1397         *
1398         * @since 1.0
1399         * @see #addAsShutdownHook()
1400         */
1401        public void addMeAsShutdownHook() {
1402            addAsShutdownHook();
1403        }
1404    
1405        /**
1406         * If you want to unregister the instance as shutdown hook you can use this
1407         * (not static) method.
1408         *
1409         * @since 1.0
1410         * @see #removeAsShutdownHook()
1411         * @see #addMeAsShutdownHook()
1412         */
1413        public void removeMeAsShutdownHook() {
1414            removeAsShutdownHook();
1415        }
1416    
1417        /**
1418         * Here you can ask if the ClasspathMonitor was already registeres ad
1419         * shutdown hook.
1420         *
1421         * @since 1.0
1422         * @return true if it is registered as shutdown hook.
1423         */
1424        public synchronized boolean isShutdownHook() {
1425            return shutdownHook;
1426        }
1427    
1428        /**
1429         * This method is called when the ClasspathMonitor is registered as shutdown
1430         * hook.
1431         *
1432         * @see java.lang.Thread#run()
1433         */
1434        @Override
1435        public void run() {
1436            dumpMe();
1437        }
1438    
1439        /**
1440         * Logs the different array to the log output.
1441         */
1442        public void logMe() {
1443            try {
1444                StringWriter writer = new StringWriter();
1445                this.dumpMe(writer);
1446                log.info(writer.toString());
1447            } catch (IOException e) {
1448                log.info(this.getClassloaderInfo());
1449            }
1450        }
1451    
1452        /**
1453         * This operation dumps the different MBean attributes to a temporary file
1454         * with the prefix "cpmon" (for ClasspathMonitor) and the extension ".txt".
1455         */
1456        public void dumpMe() {
1457            Writer writer = null;
1458            try {
1459                File dumpFile = File.createTempFile("cpmon", ".txt");
1460                writer = new FileWriter(dumpFile);
1461                dumpMe(writer);
1462                log.info("ClasspathMonitor attributes dumped to " + dumpFile);
1463            } catch (IOException ioe) {
1464                log.info("can't dump ClasspathMonitor attributes (" + ioe + ")");
1465            } finally {
1466                IOUtils.closeQuietly(writer);
1467            }
1468        }
1469    
1470        /**
1471         * Dump me.
1472         *
1473         * @param unbuffered the unbuffered
1474         *
1475         * @throws IOException Signals that an I/O exception has occurred.
1476         */
1477        private void dumpMe(final Writer unbuffered) throws IOException {
1478            BufferedWriter writer = new BufferedWriter(unbuffered);
1479            dumpArray(writer, "BootClasspath", getBootClasspath());
1480            dumpArray(writer, "Classpath", this.classpath);
1481            dumpArray(writer, "ClasspathClasses", this.getClasspathClasses());
1482            dumpArray(writer, "DoubletClasspath", getDoubletClasspath());
1483            dumpArray(writer, "Doublets", getDoublets());
1484            dumpArray(writer, "LoadedClasses", getLoadedClasses());
1485            dumpArray(writer, "LoadedPackages", getLoadedPackages());
1486            dumpArray(writer, "IncompatibleClasses", getIncompatibleClasses());
1487            dumpArray(writer, "IncompatibleClasspath", getIncompatibleClasspath());
1488            dumpArray(writer, "UnusedClasses", getUnusedClasses());
1489            dumpArray(writer, "UnusedClasspath", getUnusedClasspath());
1490            dumpArray(writer, "UsedClasspath", getUsedClasspath());
1491            dumpClassloaderInfo(writer);
1492            writer.flush();
1493        }
1494    
1495        /**
1496         * Dump classloader info.
1497         *
1498         * @param writer the writer
1499         *
1500         * @throws IOException Signals that an I/O exception has occurred.
1501         */
1502        private void dumpClassloaderInfo(final BufferedWriter writer) throws IOException {
1503            writer.write("=== ClassloaderInfo ===");
1504            writer.newLine();
1505            writer.write(this.getClassloaderInfo());
1506            writer.newLine();
1507            writer.newLine();
1508            writer.write("----- ClassLoaderDetails -----");
1509            writer.newLine();
1510            writer.write(this.getClassLoaderDetails());
1511            writer.newLine();
1512            writer.newLine();
1513        }
1514    
1515        /**
1516         * Dump array.
1517         *
1518         * @param writer the writer
1519         * @param title the title
1520         * @param array the array
1521         *
1522         * @throws IOException Signals that an I/O exception has occurred.
1523         */
1524        private static void dumpArray(final BufferedWriter writer, final String title,
1525                final String[] array) throws IOException {
1526            writer.write("=== ");
1527            writer.write(title);
1528            writer.write(" ===");
1529            writer.newLine();
1530            for (int i = 0; i < array.length; i++) {
1531                writer.write(array[i]);
1532                writer.newLine();
1533            }
1534            writer.newLine();
1535        }
1536    
1537        /**
1538         * We don't want to use the toString() implementation of the Thread class so
1539         * we use our own one.
1540         *
1541         * @return the string
1542         *
1543         * @see java.lang.Thread#toString()
1544         */
1545        @Override
1546        public String toString() {
1547            return this.getClass().getSimpleName() + " for " + this.cloader;
1548        }
1549    
1550        /**
1551         * This main method is only for testing the ClasspathMonitor together with
1552         * the 'jconsole'. On a Java5 VM start it with the following options:
1553         * -Dcom.sun.management.jmxremote.local.only=false
1554         * -Dcom.sun.management.jmxremote
1555         *
1556         * @param args the args
1557         *
1558         * @throws JMException the JM exception
1559         */
1560        public static void main(final String[] args) throws JMException {
1561            MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
1562            ObjectName name = new ObjectName("patterntesting.runtime.monitor:type=ClasspathMonitor");
1563            ClasspathMonitor hello = new ClasspathMonitor();
1564            Object mbean = new AnnotatedStandardMBean(hello, ClasspathMonitorMBean.class);
1565            mbs.registerMBean(mbean, name);
1566            ThreadUtil.sleep(300, TimeUnit.SECONDS);
1567        }
1568    
1569    
1570    
1571        /////   M U L T I   T H R E A D I N G   S E C T I O N   ///////////////////
1572    
1573        /** The multi threading enabled. */
1574        private boolean multiThreadingEnabled = getMultiThreadingEnabled();
1575    
1576        /**
1577         * Multi threading is enabled if more than one processor will be found
1578         * or if property "multiThreadingEnabled=true" is set.
1579         * @TODO extract it to a (not yet existing) Config class
1580         *
1581         * @return true on multi core processors
1582         */
1583        private static boolean getMultiThreadingEnabled() {
1584            String s = System.getProperty("multiThreadingEnabled");
1585            if (s != null) {
1586                if (log.isDebugEnabled()) {
1587                    log.debug("multiThreadingEnabled=" + s);
1588                }
1589                return s.equalsIgnoreCase("true");
1590            }
1591            int n = Runtime.getRuntime().availableProcessors();
1592            boolean enabled = (n > 1);
1593            if (log.isDebugEnabled()) {
1594                log.debug(n + " processors found, multi threading "
1595                        + (enabled ? "enabled" : "not enabled"));
1596            }
1597            return enabled;
1598        }
1599    
1600        /**
1601         * Is multi threading enabled?
1602         * Multi threading is automatically enabled if more than one processor
1603         * is found. Otherwise you can use set the system property
1604         * "multiThreadingEnabled=true" to activate it.
1605         *
1606         * @return true if multi threading is enabled for this class.
1607         *
1608         * @since 0.9.7
1609         */
1610        public boolean isMultiThreadingEnabled() {
1611            return multiThreadingEnabled;
1612        }
1613    
1614        /**
1615         * Here you can enable or disable the (experimental) multi threading mode to
1616         * see if it is really faster on a mult-core machine.
1617         *
1618         * @param enabled the enabled
1619         *
1620         * @since 0.9.7
1621         */
1622        public void setMultiThreadingEnabled(final boolean enabled) {
1623            multiThreadingEnabled = enabled;
1624        }
1625    
1626        /**
1627         * This is an experiment: can we tune getDoubleList() by using thread
1628         * techniques like consumer-/producer-pattern? Or is the synchronization
1629         * overhead to big? Let's start and compare later.
1630         * <br/>
1631         * Multi threading is automatically activated if property
1632         * "multiThreadingEnabled" is set to true or if more than one available
1633         * processor is found.
1634         *
1635         * @return a list of doublets
1636         *
1637         * @since 0.9.7
1638         */
1639        @SuppressWarnings("unchecked")
1640        protected synchronized List<Class<?>> getDoubletListParallel() {
1641            List<Class<?>> loadedClassList = this.getLoadedClassList();
1642            if (loadedClassList.isEmpty()) {
1643                return loadedClassList;
1644            }
1645            BlockingQueue<Class<?>> queue = new ArrayBlockingQueue<Class<?>>(
1646                    loadedClassList.size(), false, loadedClassList);
1647            Class<?> lastClass = loadedClassList.get(loadedClassList.size() - 1);
1648            if (this.isDoublet(lastClass)) {
1649                doubletList.add(lastClass);
1650            }
1651            // create and start multiple threads
1652            int n = 2;
1653            List<Class<?>>[] l = new List[n];
1654            DoubletDigger[] d = new DoubletDigger[n];
1655            Thread[] t = new Thread[n];
1656            for (int i = 0; i < n; i++) {
1657                l[i] = new ArrayList<Class<?>>();
1658                d[i] = new DoubletDigger(queue, lastClass, l[i]);
1659                t[i] = new Thread(d[i]);
1660                t[i].start();
1661            }
1662            // wait for the end of each started thread
1663            for (int i = 0; i < n; i++) {
1664                try {
1665                    t[i].join();
1666                } catch (InterruptedException ie) {
1667                    log.info("interrupted", ie);
1668                }
1669            }
1670            if (log.isTraceEnabled()) {
1671                log.trace("adding results from " + n + " threads to doubletList");
1672            }
1673            for (int i = 0; i < n; i++) {
1674                doubletList.addAll(l[i]);
1675            }
1676            Collections.sort(doubletList, new ObjectComparator());
1677            return doubletList;
1678        }
1679    
1680        /**
1681         * This class is needed to realize multi threading.
1682         *
1683         * @since 0.9.7
1684         */
1685        class DoubletDigger implements Runnable {
1686    
1687            /** The queue. */
1688            private final BlockingQueue<Class<?>> queue;
1689    
1690            /** The last class. */
1691            private final Class<?> lastClass;
1692    
1693            /** The new doublets. */
1694            private final List<Class<?>> newDoublets;
1695    
1696            /**
1697             * Instantiates a new doublet digger.
1698             *
1699             * @param queue the queue
1700             * @param lastClass the last class
1701             * @param newDoublets the new doublets
1702             */
1703            public DoubletDigger(final BlockingQueue<Class<?>> queue, final Class<?> lastClass,
1704                    final List<Class<?>> newDoublets) {
1705                this.queue = queue;
1706                this.lastClass = lastClass;
1707                this.newDoublets = newDoublets;
1708            }
1709    
1710            /**
1711             * @see java.lang.Runnable#run()
1712             */
1713            public void run() {
1714                if (log.isDebugEnabled()) {
1715                    log.debug("running " + this + "...");
1716                }
1717                try {
1718                    while(true) {
1719                        Class<?> clazz = queue.take();
1720                        if (clazz == lastClass) {
1721                            queue.put(clazz);
1722                            break;
1723                        }
1724                        if (doubletList.contains(clazz)) {
1725                            continue;
1726                        }
1727                        try {
1728                            if (isDoublet(clazz)) {
1729                                newDoublets.add(clazz);
1730                            }
1731                        } catch (NoSuchElementException nsee) {
1732                            if (log.isTraceEnabled()) {
1733                                log.trace(clazz + " not found -> ignored");
1734                            }
1735                        }
1736                    }
1737                } catch (InterruptedException ie) {
1738                    log.info("interrupted", ie);
1739                }
1740                if (log.isTraceEnabled()) {
1741                    log.trace(this + "finished, " + newDoublets.size() + " doublets found");
1742                }
1743            }
1744    
1745        }
1746    
1747    }