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 }