All Downloads are FREE. Search and download functionalities are using the official Maven repository.

patterntesting.runtime.monitor.ClasspathMonitor Maven / Gradle / Ivy

Go to download

PatternTesting Runtime (patterntesting-rt) is the runtime component for the PatternTesting framework. It provides the annotations and base classes for the PatternTesting testing framework (e.g. patterntesting-check, patterntesting-concurrent or patterntesting-exception) but can be also used standalone for classpath monitoring or profiling. It uses AOP and AspectJ to perform this feat.

There is a newer version: 2.4.0
Show newest version
/**
 * $Id: ClasspathMonitor.java,v 1.31 2014/01/04 21:56:42 oboehm Exp $
 *
 * Copyright (c) 2008 by Oliver Boehm
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express orimplied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * (c)reated 10.02.2009 by oliver ([email protected])
 */

package patterntesting.runtime.monitor;

import java.io.*;
import java.lang.management.ManagementFactory;
import java.lang.reflect.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.jar.*;
import java.util.zip.*;

import javax.management.*;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.*;

//import patterntesting.runtime.annotation.ProfileMe;
import patterntesting.runtime.jmx.*;
import patterntesting.runtime.util.*;

/**
 * To avoid classpath problems like double entries of the same class or resource
 * in the classpath there are several methods available. 
* To get the boot classpath the system property "sun.boot.class.path" is used to * get them. This will work of course only for the SunVM. * * @author oliver * @since 10.02.2009 * @version $Revision: 1.31 $ * @{link "http://www.javaworld.com/javaworld/javatips/jw-javatip105.html"} */ public final class ClasspathMonitor extends Thread implements ClasspathMonitorMBean { private static final long serialVersionUID = 20090511L; private static final Logger log = LoggerFactory.getLogger(ClasspathMonitor.class); private static final Executor executor = Executors.newCachedThreadPool(); /** the classpath digger. */ private final transient ClasspathDigger classpathDigger; private final transient ClassLoader cloader; private final String[] classpath; private String[] loadedClasses = new String[0]; private final transient FutureTask allClasspathClasses; private final transient FutureTask> unusedClasses; private final List> incompatibleClassList = new ArrayList>(); private static ClasspathMonitor instance; private static boolean shutdownHook = false; /** The doublet list (default visibility for testing). */ private List> doubletList; static { instance = new ClasspathMonitor(); } /** * Instantiates a new classpath monitor. */ private ClasspathMonitor() { this.reset(); this.classpathDigger = new ClasspathDigger(); this.cloader = this.classpathDigger.getClassLoader(); this.classpath = this.classpathDigger.getClasspath(); this.allClasspathClasses = getFutureCasspathClasses(); this.unusedClasses = getFutureUnusedClasses(); } private FutureTask getFutureCasspathClasses() { Callable callable = new Callable() { public String[] call() throws Exception { return getClasspathClassArray(); } }; FutureTask classes = new FutureTask(callable); executor.execute(classes); return classes; } private FutureTask> getFutureUnusedClasses() { Callable> callable = new Callable>() { public Set call() throws Exception { return getClasspathClassSet(); } }; FutureTask> classes = new FutureTask>(callable); executor.execute(classes); return classes; } /** * Resets the doubletList and maybe later other things. * At the moment it is only used by ClasspathMonitorTest * by the testGetDoubletListPerformance() method. */ protected void reset() { doubletList = new ArrayList>(); } /** * Yes, it is a Singleton because it offers only some services. So we don't * need the object twice. * * @return the only instance */ public static ClasspathMonitor getInstance() { return instance; } /** * Register as m bean. */ public static void registerAsMBean() { if (isRegisteredAsMBean()) { log.debug("MBean already registered - registerAsMBean() ignored"); } try { MBeanHelper.registerMBean(getInstance()); } catch (JMException e) { if (log.isInfoEnabled()) { log.info(instance + " can't be registered as MBean (" + e + ")"); } } } /** * If you want to ask JMX if bean is already registered you can ask the * MBeanHelper or you can ask this method. * * @since 1.0 * @return true if MBean is already registered. */ public static boolean isRegisteredAsMBean() { return MBeanHelper.isRegistered(getInstance()); } /** * Which resource. * * @param name the name * * @return the URI * * @see ClasspathMonitorMBean#whichResource(String) */ //@ProfileMe @Description("returns the URI of the given resource") public URI whichResource(final String name) { return classpathDigger.whichResource(name); } /** * Which class. * * @param name the name * * @return the URI * * @see patterntesting.runtime.monitor.ClasspathMonitorMBean#whichClass(java.lang.String) */ //@ProfileMe public URI whichClass(final String name) { String resource = Converter.classToResource(name); return whichResource(resource); } /** * Where is the given class found in the classpath? Which class was loaded * from which URI?. * * @param clazz the class * * @return file or jar path */ //@ProfileMe public URI whichClass(final Class clazz) { return whichClass(clazz.getName()); } /** * Returns the jar file or path where the given classname was found. * * @param classname e.g. java.lang.String * * @return jar or path as URI */ public URI whichClassPath(final String classname) { String resource = Converter.classToResource(classname); return whichResourcePath(resource); } /** * Returns the jar file or path where the given class was found. * * @param clazz e.g. Sting.class * * @return jar or path as URI */ public URI whichClassPath(final Class clazz) { return whichClassPath(clazz.getName()); } /** * Which class path. * * @param p the p * * @return the uRI */ public URI whichClassPath(final Package p) { String resource = Converter.toResource(p); return whichResourcePath(resource); } /** * Returns the jar file or path where the given resource was found. * * @param resource e.g. log4j.properties * * @return jar or path as URI */ public URI whichResourcePath(final String resource) { URI uri = this.whichResource(resource); if (uri == null) { return null; } return ClasspathHelper.getParent(uri, resource); } /** * Which class jar. * * @param clazz the clazz * * @return the jar file */ public JarFile whichClassJar(final Class clazz) { return whichClassJar(clazz.getName()); } /** * Which class jar. * * @param classname the classname * * @return the jar file */ public JarFile whichClassJar(final String classname) { String resource = Converter.classToResource(classname); return whichResourceJar(resource); } /** * Which resource jar. * * @param resource the resource * * @return the jar file */ public JarFile whichResourceJar(final String resource) { return whichResourceJar(this.whichResourcePath(resource)); } /** * Which resource jar. * * @param resource the resource * * @return the jar file */ public static JarFile whichResourceJar(final URI resource) { if (resource == null) { return null; } File file = Converter.toFile(resource); try { return new JarFile(file); } catch (IOException ioe) { log.debug("can't read " + file, ioe); return null; } } /** * Gets the resources. * * @param name the name * @return the resources */ //@ProfileMe public Enumeration getResources(final String name) { try { Enumeration resources = cloader.getResources(name); if (!resources.hasMoreElements()) { if (name.startsWith("/")) { return getResources(name.substring(1)); } if (log.isDebugEnabled()) { log.debug(name + " not found in classpath"); } } return resources; } catch (IOException ioe) { log.info(name + " not found in classpath", ioe); return null; } } /** * Gets the resource. * * @param name the name * * @return the resource */ public static URL getResource(final String name) { return ClasspathMonitor.class.getResource(name); } /** * @param name the name of the resource * @return number of resources * @see ClasspathMonitorMBean#getNoResources(java.lang.String) */ //@ProfileMe public int getNoResources(final String name) { Enumeration resources = getResources(name); if (resources == null) { return 0; } int n = 0; while (resources.hasMoreElements()) { n++; resources.nextElement(); } if (log.isTraceEnabled()) { log.trace(n + " element(s) of " + name + " found in classpath"); } return n; } /** * Gets the no classes. * * @param cl the cl * * @return the no classes */ public int getNoClasses(final Class cl) { return getNoClasses(cl.getName()); } /** * @param classname the classname * @return number of classes * @see ClasspathMonitorMBean#getNoClasses(java.lang.String) */ public int getNoClasses(final String classname) { return getNoResources(Converter.classToResource(classname)); } /** * Checks if is doublet. * * @param name the name * * @return true, if checks if is doublet * * @see patterntesting.runtime.monitor.ClasspathMonitorMBean#isDoublet(java.lang.String) */ //@ProfileMe public boolean isDoublet(final String name) { Enumeration resources = getResources(name); if ((resources == null) || !resources.hasMoreElements()) { throw new NoSuchElementException(name); } resources.nextElement(); if (resources.hasMoreElements()) { logDoublets(name); return true; } else { return false; } } /** * Is the given class a doublet, i.e. can it be found more than once in the * classpath? * * @param clazz the class to be checked * @return true if class is found more than once in the classpath */ //@ProfileMe public boolean isDoublet(final Class clazz) { String classname = clazz.getName(); String resource = Converter.classToResource(classname); return isDoublet(resource); } /** * Gets the first doublet. * * @param name the name * * @return the first doublet * * @see patterntesting.runtime.monitor.ClasspathMonitorMBean#getFirstDoublet(java.lang.String) */ public URL getFirstDoublet(final String name) { return getDoublet(name, 1); } /** * Gets the first doublet. * * @param clazz the class * @return the first doublet */ public URL getFirstDoublet(final Class clazz) { return getDoublet(clazz, 1); } /** * Gets the doublet. * * @param name the name * @param nr the nr * * @return the doublet * * @see patterntesting.runtime.monitor.ClasspathMonitorMBean#getDoublet(java.lang.String, * int) */ public URL getDoublet(final String name, final int nr) { Enumeration resources = getResources(name); for (int i = 0; resources.hasMoreElements(); i++) { URL url = resources.nextElement(); if (i == nr) { return url; } } return null; } /** * Gets the doublet. * * @param clazz the clazz * @param nr the nr * * @return the doublet */ public URL getDoublet(final Class clazz, final int nr) { String resource = Converter.classToResource(clazz.getName()); return getDoublet(resource, nr); } /** * Log doublets. * * @param name the name */ private void logDoublets(final String name) { if (log.isTraceEnabled()) { List doublets = new Vector(); Enumeration resources = getResources(name); while (resources.hasMoreElements()) { URL url = resources.nextElement(); doublets.add(url); } log.trace(name + " doublets found: " + doublets); } } /** * Gets the doublet list. * * @return the doublet list */ //@ProfileMe public synchronized List> getDoubletList() { if (multiThreadingEnabled) { return getDoubletListParallel(); } else { return getDoubletListSerial(); } } /** * Looks for each loaded class if it is a doublet or not. For the * performance reason it looks in the doubletList from the last time if it * is already found. This is done because normally the number of doublets * does not decrease. * * @return a sorted list of found doublets */ //@ProfileMe protected synchronized List> getDoubletListSerial() { List> loadedClassList = this.getLoadedClassList(); for (Class clazz : loadedClassList) { // TODO this can be parallized if (doubletList.contains(clazz)) { continue; } try { if (this.isDoublet(clazz)) { doubletList.add(clazz); } } catch (NoSuchElementException nsee) { if (log.isTraceEnabled()) { log.trace(clazz + " not found -> ignored"); } } } Collections.sort(doubletList, new ObjectComparator()); return doubletList; } /** * Gets the doublets. * * @return the doublets * * @see ClasspathMonitorMBean#getDoublets() */ //@ProfileMe public String[] getDoublets() { log.debug("calculating doublets..."); List> classes = this.getDoubletList(); String[] doublets = new String[classes.size()]; for (int i = 0; i < doublets.length; i++) { doublets[i] = classes.get(i).toString(); } return doublets; } /** * Gets the doublet classpath. * * @return the doublet classpath * * @see ClasspathMonitorMBean#getDoubletClasspath() */ //@ProfileMe public String[] getDoubletClasspath() { try { log.debug("calculating doublet-classpath..."); Set classpathSet = getClasspathSet(this.getDoubletList()); return getAsArray(classpathSet); } catch (ConcurrentModificationException e) { log.info(e + " happens while sorting classes - will try it again..."); ThreadUtil.sleep(); return getDoubletClasspath(); } } /** * Gets the classpath set. * * @param classes the classes * * @return the classpath set */ private SortedSet getClasspathSet(final List> classes) { SortedSet clpath = new TreeSet(); for (Class cl : classes) { String resource = Converter.toResource(cl); Enumeration resources = this.getResources(resource); while (resources.hasMoreElements()) { URL url = resources.nextElement(); URI path = Converter.toURI(url); clpath.add(ClasspathHelper.getParent(path, resource)); } } return clpath; } /** * Dumps the internal fields of the classloader (after an idea from * "Java ist auch eine Insel"). * * {@link "http://www.galileocomputing.de/openbook/javainsel6/javainsel_21_003.htm#mj7e2d89f4b96fde1900bc09fd1db83fb1"} * * @return the class loader details */ //@ProfileMe public String getClassLoaderDetails() { StringBuffer sbuf = new StringBuffer("dump of " + cloader + ":\n"); for (Class cl = this.cloader.getClass(); cl != null; cl = cl .getSuperclass()) { sbuf.append('\t'); dumpFields(sbuf, cl, this.cloader); } return sbuf.toString().trim(); } /** * Dump fields. * * @param sbuf the sbuf * @param cl the cl * @param obj the obj */ //@ProfileMe private static void dumpFields(final StringBuffer sbuf, final Class cl, final Object obj) { Field[] fields = cl.getDeclaredFields(); AccessibleObject.setAccessible(fields, true); for (int i = 0; i < fields.length; i++) { sbuf.append(fields[i]); sbuf.append(" = "); try { sbuf.append(fields[i].get(obj)); } catch (Exception e) { sbuf.append("<" + e + ">"); } sbuf.append('\n'); } } /** * Checks if is classloader supported. * * @return true if it is a known classloader */ public boolean isClassloaderSupported() { return this.classpathDigger.isClassloaderSupported(); } /** * Returns some information about the classloader. At least the user should * be informed if it is a unknown classloader which is not supported or not * tested. * * @return e.g. "unknown classloader xxx - classpath can be wrong" */ public String getClassloaderInfo() { String info = this.isClassloaderSupported() ? "supported" : "unsupported"; return this.cloader.getClass().getName() + " (" + info + ")"; } /** * Gets the loaded packages. * * @return the loaded packages * * @see ClasspathMonitorMBean#getLoadedPackages() */ public String[] getLoadedPackages() { Package[] packages = this.classpathDigger.getLoadedPackageArray(); String[] strings = new String[packages.length]; for (int i = 0; i < packages.length; i++) { strings[i] = packages[i].toString(); } Arrays.sort(strings); return strings; } /** * This method is used by PatternTesting Samples (in packages.jsp). So it * is left here as wrapper around ClasspathDigger. * * @return array of packages */ public Package[] getLoadedPackageArray() { return this.classpathDigger.getLoadedPackageArray(); } /** * If you want to dump all packages you can use this method. The output will * be sorted. * * @return each package in a single line */ public String getLoadedPackagesAsString() { String[] packages = getLoadedPackages(); StringBuffer sbuf = new StringBuffer(); for (int i = 0; i < packages.length; i++) { sbuf.append(packages[i]); sbuf.append('\n'); } return sbuf.toString().trim(); } /** * Returns a list of classes which were loaded by the classloader. * * @return list of classes */ //@ProfileMe public synchronized List> getLoadedClassList() { List> classlist = classpathDigger.getLoadedClassList(); return classlist; } /** * As MBean a string array could be displayed by the 'jconsole'. A class * array not. * * @return the classes as sorted string array */ public synchronized String[] getLoadedClasses() { List> classes = getLoadedClassList(); if (classes.size() != loadedClasses.length) { loadedClasses = new String[classes.size()]; for (int i = 0; i < loadedClasses.length; i++) { loadedClasses[i] = classes.get(i).toString(); } Arrays.sort(loadedClasses); } return loadedClasses; } /** * Gets the loaded classes as string. * * @return the loaded classes as string */ //@ProfileMe public String getLoadedClassesAsString() { List> classes = getLoadedClassList(); Collections.sort(classes, new ObjectComparator()); StringBuffer sbuf = new StringBuffer(); for (Iterator> i = classes.iterator(); i.hasNext();) { sbuf.append(i.next().toString().trim()); sbuf.append('\n'); } return sbuf.toString().trim(); } /** * Checks if the given classname is loaded. * Why does we use not Class as parameter here? If you would allow a * parameter of type "Class" this class will be problably loaded before * and this method will return always true! * * @param classname name of the class * @return true if class is loaded * @see patterntesting.runtime.monitor.ClasspathMonitorMBean#isLoaded(java.lang.String) */ public boolean isLoaded(final String classname) { return this.classpathDigger.isLoaded(classname); } /** * Unused classes are classes which are not loaded but which are found in * the classpath. * * @return unused classes * * @see #getLoadedClasses() * @see #getClasspathClasses() */ //@ProfileMe public String[] getUnusedClasses() { List> classes = getLoadedClassList(); Collection used = new ArrayList(); Set unusedSet = this.getUnusedClassSet(); for (Class cl : classes) { String classname = cl.getName(); if (unusedSet.contains(classname)) { used.add(classname); } } unusedSet.removeAll(used); String[] unused = new String[unusedSet.size()]; unusedSet.toArray(unused); return unused; } private Set getUnusedClassSet() { // return this.unusedClassSet; try { return this.unusedClasses.get(); } catch (InterruptedException e) { log.warn("no result from " + this.unusedClasses, e); return this.getClasspathClassSet(); } catch (ExecutionException e) { log.warn("no result from " + this.unusedClasses, e); return this.getClasspathClassSet(); } } /** * Scans the classpath for all classes. * For performance reason we return no longer a copy but the origin array. * Don't do changes for the returned array! * * @return all classes as String array */ public String[] getClasspathClasses() { try { return this.allClasspathClasses.get(); } catch (InterruptedException e) { log.warn("no result from " + this.allClasspathClasses, e); return this.getClasspathClassArray(); } catch (ExecutionException e) { log.warn("no result from " + this.allClasspathClasses, e); return this.getClasspathClassArray(); } } private Set getClasspathClassSet() { String[] classes = getClasspathClasses(); return new TreeSet(Arrays.asList(classes)); } /** * Gets the classes of a given package name as collection. * * @param packageName the package name * @return the classpath class list */ public Collection getClasspathClassList(final String packageName) { Collection classlist = new ArrayList(); String[] classes = this.getClasspathClasses(); for (int i = 0; i < classes.length; i++) { if (classes[i].startsWith(packageName)) { classlist.add(classes[i]); } } return classlist; } /** * Gets the classes of the given type. * * @param the generic type * @param packageName the package name * @param type the type * @return the classes */ @SuppressWarnings("unchecked") public Collection> getClassList(final String packageName, final Class type) { Collection> classes = new ArrayList>(); Collection> concreteClasses = getConcreteClassList(packageName); for (Class clazz : concreteClasses) { if (type.isAssignableFrom(clazz)) { classes.add(((Class) clazz)); log.trace("subclass of {} found: {}", type, clazz); } } return classes; } /** * Gets a list of concrete classes. These are classes which can be * instantiated, i.e. they are not abstract and have a default * constructor. * * @param packageName the package name * @return the concrete class list */ public Collection> getConcreteClassList(final String packageName) { assert packageName != null; Collection classList = this.getClasspathClassList(packageName); Collection> classes = new ArrayList>(classList.size()); for (String classname : classList) { try { Class clazz = Class.forName(classname); if (!canBeInstantiated(clazz)) { if (log.isTraceEnabled()) { log.trace(clazz + " will be ignored (can't be instantiated)"); } continue; } classes.add(clazz); } catch (ClassNotFoundException e) { log.info(classname + " will be ignored (" + e.getMessage() + ")"); } } return classes; } // /** // * Gets a list of concrete classes of the given type. // * These are classes which can be instantiated, i.e. they are not abstract // * and have a default constructor. // * // * @param packageName the package name // * @param type the type // * @return the classes // */ // public Collection> getConcreteClassList(final String packageName, Class type) { // Collection> classes = new ArrayList>(); // Collection> concreteClasses = this.getConcreteClassList(packageName); // for (Class clazz : concreteClasses) { // if (type.isAssignableFrom(clazz)) { // classes.add(clazz); // if (log.isTraceEnabled()) { // log.trace(type + " found: " + clazz); // } // } // } // return classes; // } private static boolean canBeInstantiated(final Class clazz) { if (clazz.isInterface()) { return false; } int mod = clazz.getModifiers(); if (Modifier.isAbstract(mod)) { return false; } try { clazz.getConstructor(); return true; } catch (SecurityException e) { log.info("can't get default ctor of " + clazz, e); return false; } catch (NoSuchMethodException e) { return false; } } /** * Gets the classpath class set (sorted). * * @return the classpath class set */ //@ProfileMe private Set createClasspathClassSet() { Set classSet = new TreeSet(); for (int i = 0; i < classpath.length; i++) { addClasses(classSet, new File(classpath[i])); } return classSet; } private String[] getClasspathClassArray() { Set classSet = createClasspathClassSet(); return classSet.toArray(new String[classSet.size()]); } /** * Adds the classes. * * @param classSet the class set * @param path the path */ private static void addClasses(final Set classSet, final File path) { if (log.isTraceEnabled()) { log.trace("adding classes from " + path.getAbsolutePath() + "..."); } try { if (path.isDirectory()) { addClassesFromDir(classSet, path); } else { addClassesFromArchive(classSet, path); } } catch (IOException e) { log.warn("can't add classes from " + path.getAbsolutePath()); } } /** * Adds the classes from dir. * * @param classSet the class set * @param dir the dir * * @throws IOException Signals that an I/O exception has occurred. */ //@ProfileMe private static void addClassesFromDir(final Set classSet, final File dir) throws IOException { ClassWalker classWalker = new ClassWalker(dir); Collection classes = classWalker.getClasses(); classSet.addAll(classes); } /** * Adds the classes from archive. * * @param classSet the class set * @param archive the archive * * @throws IOException Signals that an I/O exception has occurred. */ //@ProfileMe private static void addClassesFromArchive(final Set classSet, final File archive) throws IOException { ZipFile zipFile = new ZipFile(archive); try { Enumeration entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); String name = entry.getName(); if (name.endsWith(".class")) { classSet.add(Converter.resourceToClass(name)); } } } finally { zipFile.close(); } } /** * Gets the loaded classpath (without the bootclasspath) as sorted set.
* TODO: this method lets room for more performance (e.g. using a * Hashtable for caching) * * @return the loaded classpath (excluding the bootclasspath) */ //@ProfileMe public SortedSet getUsedClasspathSet() { List> loadedClassList = this.getLoadedClassList(); SortedSet usedClasspathSet = new TreeSet(); for (Class clazz : loadedClassList) { URI classUri = this.whichClass(clazz); if (classUri != null) { URI classpathURI = ClasspathHelper.getParent(classUri, clazz); usedClasspathSet.add(classpathURI); } } return usedClasspathSet; } /** * It might be that the used classpath changes during the calculation of it. * So it is only a good approximation how it looks at the moment of the * call. * * @return the used classpath * * @see ClasspathMonitor#getUsedClasspath() */ //@ProfileMe public String[] getUsedClasspath() { log.debug("calculating used classpath..."); SortedSet classpathSet = this.getUsedClasspathSet(); return getAsArray(classpathSet); } /** * Gets the as array. * * @param classpathSet the classpath set * * @return the as array */ private static String[] getAsArray(final Set classpathSet) { String[] classpath = new String[classpathSet.size()]; Object[] a = classpathSet.toArray(); for (int i = 0; i < classpath.length; i++) { URI uri = (URI) a[i]; classpath[i] = uri.getPath(); if (StringUtils.isEmpty(classpath[i])) { classpath[i] = StringUtils.substringAfterLast(uri.toString(), ":"); } if (classpath[i].endsWith(File.separator)) { classpath[i] = classpath[i].substring(0, (classpath[i].length() - 1)); } } Arrays.sort(classpath); return classpath; } /** * The unused classpath is this path which are not used in past.
* * @return the unused classpath (sorted) */ //@ProfileMe public String[] getUnusedClasspath() { log.debug("calculating unused classpath..."); SortedSet unused = new TreeSet(); log.trace(Arrays.class + " loaded (to get corrected used classpath"); String[] used = this.getUsedClasspath(); for (int i = 0; i < classpath.length; i++) { String path = classpath[i]; if (Arrays.binarySearch(used, path) < 0) { unused.add(path); } } String[] a = new String[unused.size()]; return unused.toArray(a); } /** * To get the boot classpath the sytem property "sun.boot.class.path" is * used to get them. This will work of course only for the SunVM. * * @return the boot classpath as String array */ public String[] getBootClasspath() { return this.classpathDigger.getBootClasspath(); } /** * The classpath is stored in the system property "java.class.path". And * this is the classpath which will be returned. * * @return the classpath as String array */ public String[] getClasspath() { return this.classpath; //return Arrays.copyOf(this.classpath, this.classpath.length); } /** * @param classname * name of the class * @return the serialVersionUID * @throws IllegalAccessException * if class can't be accessed * @see patterntesting.runtime.monitor.ClasspathMonitorMBean#getSerialVersionUID(java.lang.String) */ public Long getSerialVersionUID(final String classname) throws IllegalAccessException { try { Class clazz = Class.forName(classname); return getSerialVersionUID(clazz); } catch (ClassNotFoundException e) { log.info(classname + " not found (" + e.getLocalizedMessage() + ")"); return null; } } /** * Gets the serial version uid. * * @param clazz the clazz * * @return the serial version uid * * @throws IllegalAccessException the illegal access exception */ public Long getSerialVersionUID(final Class clazz) throws IllegalAccessException { try { Field field = ReflectionHelper.getField(clazz, "serialVersionUID"); return (Long) field.get(null); } catch (NoSuchFieldException e) { log.debug(clazz + " has no serialVersionUID"); return null; } } /** * If a MANIFEST is found for a given class the attributes in this * file should be returned as a string array. * E.g. for commons-lang-2.3.jar the string array may looks like *
     * Manifest-Version: 1.0
     * Ant-Version: Apache Ant 1.6.5
     * Created-By: 1.3.1_09-85 ("Apple Computer, Inc.")
     * Package: org.apache.commons.lang
     * Extension-Name: commons-lang
     * Specification-Version: 2.3
     * Specification-Vendor: Apache Software Foundation
     * Specification-Title: Commons Lang
     * Implementation-Version: 2.3
     * Implementation-Vendor: Apache Software Foundation
     * Implementation-Title: Commons Lang
     * Implementation-Vendor-Id: org.apache
     * X-Compile-Source-JDK: 1.3
     * X-Compile-Target-JDK: 1.1
     * 
* * @param clazz the clazz * * @return the attribute entries of the Manifest * (or emtpy array if no Manifest or no attributes are found) */ public String[] getManifestEntries(final Class clazz) { return this.getManifestEntries(clazz.getName()); } /** * If a MANIFEST is found for a given class the attributes in this * file should be returned as a string array. * E.g. for commons-lang-2.3.jar the string array may looks like *
     * Manifest-Version: 1.0
     * Ant-Version: Apache Ant 1.6.5
     * Created-By: 1.3.1_09-85 ("Apple Computer, Inc.")
     * Package: org.apache.commons.lang
     * Extension-Name: commons-lang
     * Specification-Version: 2.3
     * Specification-Vendor: Apache Software Foundation
     * Specification-Title: Commons Lang
     * Implementation-Version: 2.3
     * Implementation-Vendor: Apache Software Foundation
     * Implementation-Title: Commons Lang
     * Implementation-Vendor-Id: org.apache
     * X-Compile-Source-JDK: 1.3
     * X-Compile-Target-JDK: 1.1
     * 
* * @param classname (must be in the classpath, otherwise you'll get a * IllegalArgumentException) * * @return the attribute entries of the Manifest * (or emtpy array if no Manifest or no attributes are found) */ public String[] getManifestEntries(final String classname) { URI classpathURI = whichClassPath(classname); if (classpathURI == null) { throw new IllegalArgumentException(classname + " not found in classpath"); } File path = Converter.toFile(classpathURI); return getManifestEntries(path); } /** * Gets the manifest entries. * * @param path e.g. a classpath or a JAR file * * @return the attribute entries of the manifest file */ private String[] getManifestEntries(final File path) { if (path.isFile()) { try { return getManifestEntries(new JarFile(path)); } catch (IOException ioe) { log.info("no manifest found in " + path, ioe); return new String[0]; } } File manifestFile = new File(path, "META-INF/MANIFEST.MF"); if (!manifestFile.exists()) { if (log.isDebugEnabled()) { log.debug(manifestFile + " does not exist"); } return new String[0]; } try { InputStream istream = new FileInputStream(manifestFile); Manifest manifest = new Manifest(istream); IOUtils.closeQuietly(istream); return getManifestEntries(manifest); } catch (IOException ioe) { log.info("can't read " + manifestFile, ioe); return new String[0]; } } /** * We look for the manifest file for the given JAR file. * * @param jarfile the jarfile * * @return the entries as string array (may be an empty array if no * manifest file was found) */ private String[] getManifestEntries(final JarFile jarfile) { Manifest manifest; try { manifest = jarfile.getManifest(); } catch (IOException ioe) { log.info("no manifest found in " + jarfile, ioe); return new String[0]; } if (manifest == null) { if (log.isDebugEnabled()) { log.debug("no manifest found in " + jarfile); } return new String[0]; } return getManifestEntries(manifest); } /** * Gets the manifest entries. * * @param manifest the manifest * * @return the manifest entries */ private String[] getManifestEntries(final Manifest manifest) { Attributes attributes = manifest.getMainAttributes(); String[] manifestEntries = new String[attributes.size()]; Set> entries = attributes.entrySet(); Iterator> iterator = entries.iterator(); for (int i = 0; i < manifestEntries.length; i++) { Map.Entry entry = iterator.next(); manifestEntries[i] = entry.getKey() + ": " + entry.getValue(); } return manifestEntries; } /** * Looks for each doublet if it has a different doublets. For the * performance reason it looks in the incompatibleClassList from the last * time if it is already found. This is done because normally the number of * incompatible classed does not decrease. * * @return a sorted list of incompatible classes */ //@ProfileMe public synchronized List> getIncompatibleClassList() { List> doublets = this.getDoubletList(); for (Class clazz : doublets) { if (incompatibleClassList.contains(clazz)) { continue; } String resource = Converter.classToResource(clazz.getName()); Enumeration resources = getResources(resource); URL url = resources.nextElement(); ArchivEntry archivEntry = new ArchivEntry(url); while (resources.hasMoreElements()) { url = resources.nextElement(); ArchivEntry doubletEntry = new ArchivEntry(url); if (archivEntry.equals(doubletEntry)) { incompatibleClassList.add(clazz); break; } } } return incompatibleClassList; } /** * Incompatible classes are doublets with different byte codes. * * @return doublet classes with different byte codes. */ public String[] getIncompatibleClasses() { log.debug("calculating incompatible classes..."); List> classList = this.getIncompatibleClassList(); String[] classes = new String[classList.size()]; for (int i = 0; i < classes.length; i++) { classes[i] = classList.get(i).toString(); } return classes; } /** * Gets the incompatible classpath. * * @return the classpathes where incompatible classes were found */ public String[] getIncompatibleClasspath() { log.debug("calculating doublet-classpath..."); Set classpathSet = getClasspathSet(this.getIncompatibleClassList()); return getAsArray(classpathSet); } /** * You can register the instance as shutdown hook. If the VM is terminated * the attributes are logged and dumped to a text file in the tmp directory. * * @see #logMe() * @see #dumpMe() * @see #addMeAsShutdownHook() * @see #removeMeAsShutdownHook() */ public static synchronized void addAsShutdownHook() { Runtime.getRuntime().addShutdownHook(instance); shutdownHook = true; log.debug("{} registered as shutdown hook", instance); } /** * If you have registered the instance you can now de-register it. * * @since 1.0 * @see #addAsShutdownHook() * @see #removeMeAsShutdownHook() */ public static synchronized void removeAsShutdownHook() { Runtime.getRuntime().removeShutdownHook(instance); shutdownHook = false; log.debug("{} de-registered as shutdown hook", instance); } /** * To be able to register the instance as shutdown hook via JMX we can't * use a static method - this is the reason why this additional method * was added. * * @since 1.0 * @see #addAsShutdownHook() */ public void addMeAsShutdownHook() { addAsShutdownHook(); } /** * If you want to unregister the instance as shutdown hook you can use this * (not static) method. * * @since 1.0 * @see #removeAsShutdownHook() * @see #addMeAsShutdownHook() */ public void removeMeAsShutdownHook() { removeAsShutdownHook(); } /** * Here you can ask if the ClasspathMonitor was already registeres ad * shutdown hook. * * @since 1.0 * @return true if it is registered as shutdown hook. */ public synchronized boolean isShutdownHook() { return shutdownHook; } /** * This method is called when the ClasspathMonitor is registered as shutdown * hook. * * @see java.lang.Thread#run() */ @Override public void run() { dumpMe(); } /** * Logs the different array to the log output. */ public void logMe() { try { StringWriter writer = new StringWriter(); this.dumpMe(writer); log.info(writer.toString()); } catch (IOException e) { log.info(this.getClassloaderInfo()); } } /** * This operation dumps the different MBean attributes to a temporary file * with the prefix "cpmon" (for ClasspathMonitor) and the extension ".txt". */ public void dumpMe() { Writer writer = null; try { File dumpFile = File.createTempFile("cpmon", ".txt"); writer = new FileWriter(dumpFile); dumpMe(writer); log.info("ClasspathMonitor attributes dumped to " + dumpFile); } catch (IOException ioe) { log.info("can't dump ClasspathMonitor attributes (" + ioe + ")"); } finally { IOUtils.closeQuietly(writer); } } /** * Dump me. * * @param unbuffered the unbuffered * * @throws IOException Signals that an I/O exception has occurred. */ private void dumpMe(final Writer unbuffered) throws IOException { BufferedWriter writer = new BufferedWriter(unbuffered); dumpArray(writer, "BootClasspath", getBootClasspath()); dumpArray(writer, "Classpath", this.classpath); dumpArray(writer, "ClasspathClasses", this.getClasspathClasses()); dumpArray(writer, "DoubletClasspath", getDoubletClasspath()); dumpArray(writer, "Doublets", getDoublets()); dumpArray(writer, "LoadedClasses", getLoadedClasses()); dumpArray(writer, "LoadedPackages", getLoadedPackages()); dumpArray(writer, "IncompatibleClasses", getIncompatibleClasses()); dumpArray(writer, "IncompatibleClasspath", getIncompatibleClasspath()); dumpArray(writer, "UnusedClasses", getUnusedClasses()); dumpArray(writer, "UnusedClasspath", getUnusedClasspath()); dumpArray(writer, "UsedClasspath", getUsedClasspath()); dumpClassloaderInfo(writer); writer.flush(); } /** * Dump classloader info. * * @param writer the writer * * @throws IOException Signals that an I/O exception has occurred. */ private void dumpClassloaderInfo(final BufferedWriter writer) throws IOException { writer.write("=== ClassloaderInfo ==="); writer.newLine(); writer.write(this.getClassloaderInfo()); writer.newLine(); writer.newLine(); writer.write("----- ClassLoaderDetails -----"); writer.newLine(); writer.write(this.getClassLoaderDetails()); writer.newLine(); writer.newLine(); } /** * Dump array. * * @param writer the writer * @param title the title * @param array the array * * @throws IOException Signals that an I/O exception has occurred. */ private static void dumpArray(final BufferedWriter writer, final String title, final String[] array) throws IOException { writer.write("=== "); writer.write(title); writer.write(" ==="); writer.newLine(); for (int i = 0; i < array.length; i++) { writer.write(array[i]); writer.newLine(); } writer.newLine(); } /** * We don't want to use the toString() implementation of the Thread class so * we use our own one. * * @return the string * * @see java.lang.Thread#toString() */ @Override public String toString() { return this.getClass().getSimpleName() + " for " + this.cloader; } /** * This main method is only for testing the ClasspathMonitor together with * the 'jconsole'. On a Java5 VM start it with the following options: * -Dcom.sun.management.jmxremote.local.only=false * -Dcom.sun.management.jmxremote * * @param args the args * * @throws JMException the JM exception */ public static void main(final String[] args) throws JMException { MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); ObjectName name = new ObjectName("patterntesting.runtime.monitor:type=ClasspathMonitor"); ClasspathMonitor hello = new ClasspathMonitor(); Object mbean = new AnnotatedStandardMBean(hello, ClasspathMonitorMBean.class); mbs.registerMBean(mbean, name); ThreadUtil.sleep(300, TimeUnit.SECONDS); } ///// M U L T I T H R E A D I N G S E C T I O N /////////////////// /** The multi threading enabled. */ private boolean multiThreadingEnabled = getMultiThreadingEnabled(); /** * Multi threading is enabled if more than one processor will be found * or if property "multiThreadingEnabled=true" is set. * @TODO extract it to a (not yet existing) Config class * * @return true on multi core processors */ private static boolean getMultiThreadingEnabled() { String s = System.getProperty("multiThreadingEnabled"); if (s != null) { if (log.isDebugEnabled()) { log.debug("multiThreadingEnabled=" + s); } return s.equalsIgnoreCase("true"); } int n = Runtime.getRuntime().availableProcessors(); boolean enabled = (n > 1); if (log.isDebugEnabled()) { log.debug(n + " processors found, multi threading " + (enabled ? "enabled" : "not enabled")); } return enabled; } /** * Is multi threading enabled? * Multi threading is automatically enabled if more than one processor * is found. Otherwise you can use set the system property * "multiThreadingEnabled=true" to activate it. * * @return true if multi threading is enabled for this class. * * @since 0.9.7 */ public boolean isMultiThreadingEnabled() { return multiThreadingEnabled; } /** * Here you can enable or disable the (experimental) multi threading mode to * see if it is really faster on a mult-core machine. * * @param enabled the enabled * * @since 0.9.7 */ public void setMultiThreadingEnabled(final boolean enabled) { multiThreadingEnabled = enabled; } /** * This is an experiment: can we tune getDoubleList() by using thread * techniques like consumer-/producer-pattern? Or is the synchronization * overhead to big? Let's start and compare later. *
* Multi threading is automatically activated if property * "multiThreadingEnabled" is set to true or if more than one available * processor is found. * * @return a list of doublets * * @since 0.9.7 */ @SuppressWarnings("unchecked") protected synchronized List> getDoubletListParallel() { List> loadedClassList = this.getLoadedClassList(); if (loadedClassList.isEmpty()) { return loadedClassList; } BlockingQueue> queue = new ArrayBlockingQueue>( loadedClassList.size(), false, loadedClassList); Class lastClass = loadedClassList.get(loadedClassList.size() - 1); if (this.isDoublet(lastClass)) { doubletList.add(lastClass); } // create and start multiple threads int n = 2; List>[] l = new List[n]; DoubletDigger[] d = new DoubletDigger[n]; Thread[] t = new Thread[n]; for (int i = 0; i < n; i++) { l[i] = new ArrayList>(); d[i] = new DoubletDigger(queue, lastClass, l[i]); t[i] = new Thread(d[i]); t[i].start(); } // wait for the end of each started thread for (int i = 0; i < n; i++) { try { t[i].join(); } catch (InterruptedException ie) { log.info("interrupted", ie); } } if (log.isTraceEnabled()) { log.trace("adding results from " + n + " threads to doubletList"); } for (int i = 0; i < n; i++) { doubletList.addAll(l[i]); } Collections.sort(doubletList, new ObjectComparator()); return doubletList; } /** * This class is needed to realize multi threading. * * @since 0.9.7 */ class DoubletDigger implements Runnable { /** The queue. */ private final BlockingQueue> queue; /** The last class. */ private final Class lastClass; /** The new doublets. */ private final List> newDoublets; /** * Instantiates a new doublet digger. * * @param queue the queue * @param lastClass the last class * @param newDoublets the new doublets */ public DoubletDigger(final BlockingQueue> queue, final Class lastClass, final List> newDoublets) { this.queue = queue; this.lastClass = lastClass; this.newDoublets = newDoublets; } /** * @see java.lang.Runnable#run() */ public void run() { if (log.isDebugEnabled()) { log.debug("running " + this + "..."); } try { while(true) { Class clazz = queue.take(); if (clazz == lastClass) { queue.put(clazz); break; } if (doubletList.contains(clazz)) { continue; } try { if (isDoublet(clazz)) { newDoublets.add(clazz); } } catch (NoSuchElementException nsee) { if (log.isTraceEnabled()) { log.trace(clazz + " not found -> ignored"); } } } } catch (InterruptedException ie) { log.info("interrupted", ie); } if (log.isTraceEnabled()) { log.trace(this + "finished, " + newDoublets.size() + " doublets found"); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy