The ability to identify the source of a class definition in a Java application is an especially useful tactic when one knows or has reason to suspect that a particular class is defined twice on the classpath, possibly with different definitions. In general, the first definition of a class found on the classpath is the one used, but this technique can verify the class definition being used or can be useful when there is a very large classpath that makes it difficult to identify all instances of a particular classpath entry.
As discussed in How to Determine Where Your Java Class is Actually Located, Where's That Class Coming From?, How to Know from Which JAR Your Class is Loaded From, Where Did That Class Come From?, Where to Find JARs?, and J2SE Security, the Java SE Security model provides a convenient mechanism for identifying the source (JAR or .class file) of a class definition used in a Java application. This approach involves getting the ProtectionDomain from a class's Class and using that ProtectionDomain to obtain a CodeSource which provides a URL to the source of that particular class definition.
I will include the full code a little later, but here is a sample of code focused on using these Security APIs to get class definition information.
/**
* Provides the name of the classpath entry that provides the class definition
* of the provided object.
*
* @param objectInQuestion Object for which classpath source of instance is
* desired.
*/
public static final String getClassPathSource(final Object objectInQuestion)
{
final StringBuilder builder = new StringBuilder();
final Class clazz = objectInQuestion.getClass();
final ProtectionDomain protectedDomain = clazz.getProtectionDomain();
final CodeSource codeSource = protectedDomain.getCodeSource();
if (codeSource != null)
{
builder.append("CodeSource: ").append(codeSource.toString());
builder.append(Constants.NEW_LINE);
final URL url = codeSource.getLocation();
builder.append("URL: ").append(url.toString());
}
else // codeSource == null
{
builder.append("CodeSource for ")
.append(clazz.getCanonicalName())
.append(" is null.");
}
return builder.toString();
}
As the code listing above shows, it is a relatively straightforward process to use get a particular object's
Class
to obtain a ProtectionDomain
from which a CodeSource
can be obtained. The CodeSource
provides the getLocation() and toString() methods that make it really easy to display the classpath locations from which the class definition came.The code snippet above can be used in a class such as the ClassPathIdentifier class shown next.
ClassPathIdentifier.java
package classpathidentifier;
import java.net.URL;
import java.security.CodeSource;
import java.security.ProtectionDomain;
/**
* This class reports what entry in the classpath provides the definition of
* the provided Object.
*/
public final class ClassPathIdentifier
{
/** This class is not intended for public consumption. */
private ClassPathIdentifier() {}
/**
* Provides the name of the classpath entry that provides the class definition
* of the provided object.
*
* @param objectInQuestion Object for which classpath source of instance is
* desired.
*/
public static final String getClassPathSource(final Object objectInQuestion)
{
final StringBuilder builder = new StringBuilder();
final Class clazz = objectInQuestion.getClass();
final ProtectionDomain protectedDomain = clazz.getProtectionDomain();
final CodeSource codeSource = protectedDomain.getCodeSource();
if (codeSource != null)
{
builder.append("CodeSource: ").append(codeSource.toString());
builder.append(Constants.NEW_LINE);
final URL url = codeSource.getLocation();
builder.append("URL: ").append(url.toString());
}
else // codeSource == null
{
builder.append("CodeSource for ")
.append(clazz.getCanonicalName())
.append(" is null.");
}
return builder.toString();
}
/**
* Test method to demonstrate use of this functionality.
*/
public static void main(final String[] arguments)
{
if (arguments.length < 1)
{
System.err.println("You must provide a fully qualified Class name.");
System.exit(-1);
}
final String classForSource = arguments[0];
if (classForSource.isEmpty())
{
System.err.println("You must provide a valid string for class path/name.");
}
System.out.println("You provided class of " + classForSource + " ...");
try
{
final Class clazz = Class.forName(classForSource);
final Object object = clazz.newInstance();
System.out.println("Source: " + getClassPathSource(object));
}
catch (ClassNotFoundException classNotFoundEx)
{
System.err.println("Could not find class " + arguments[0]);
}
catch (InstantiationException instanceEx)
{
System.err.println("Unable to instantiate instanceof " + classForSource);
}
catch (IllegalAccessException illegalAccessEx)
{
System.err.println("Illegal Access trying to instantiatite " + classForSource);
}
}
}
It is not exciting, but I include the code listing for the Constants class next for completeness.
Constants.java
package classpathidentifier;
/**
* Constants used in simple example demonstrating one way to determine the
* classpath entry from which a particular instance was specified.
*/
public class Constants
{
/** This constructor is not intended for public consumption. */
private Constants() {}
public final static String NEW_LINE = System.getProperty("line.separator");
}
For this example, I am also using a basic class called SomeClass, which is defined next.
SomeClass.java
package classpathidentifier;
public class SomeClass
{
/** An integer. */
private final int someInt;
/** Some String. */
private final String someString;
/**
* Constructor accepting arguments to populate an instance of me.
*/
public SomeClass(final int newInt, final String newString)
{
this.someInt = newInt;
this.someString = newString;
}
/**
* Provide my integer value.
*
* @return My integer value.
*/
public int getSomeInt()
{
return this.someInt;
}
/**
* Provide my String value.
*
* @return My String value.
*/
public String getSomeString()
{
return this.someString;
}
/**
* Provide String representation of me.
*
* @return My String representation.
*/
@Override
public String toString()
{
final StringBuilder builder = new StringBuilder();
builder.append("Some Int: ").append(this.someInt).append(Constants.NEW_LINE);
builder.append("Some String: ").append(this.someString);
return builder.toString();
}
}
With all of the classes defined above, it is time to turn to an executable class that can drive out use of these APIs. The relatively simple Main class demonstrates the approach of using
Class
->ProtectionDomain
->SourceCode
->getLocation()
to determine the original source on the classpath of that class's definition.Main.java
package classpathidentifier;
/**
* Main class that is separate from ClasspathIdentifier class, but uses that
* class.
*/
public class Main
{
/** My instance of SomeClass. */
private SomeClass someClass = new SomeClass(5, "Five");
/** No-arguments constructor doesn't do anything exciting. */
public Main() {}
/**
* Provide access to my instance of SomeClass.
*
* @return My instance of SomeClass.
*/
public SomeClass getSomeClass()
{
return this.someClass;
}
/**
* Main function for running me.
*
* @param arguments Command-line arguments; none expected.
*/
public static void main(final String[] arguments)
{
final Main me = new Main();
printString(ClassPathIdentifier.getClassPathSource(me));
printString(ClassPathIdentifier.getClassPathSource(me.getSomeClass()));
printString(ClassPathIdentifier.getClassPathSource(me.getSomeClass().getSomeInt()));
printString(ClassPathIdentifier.getClassPathSource(me.getSomeClass().getSomeString()));
}
/**
* Print provided String.
*
* @param stringToPrint String to be printed.
*/
private static void printString(final String stringToPrint)
{
System.out.println(Constants.NEW_LINE);
System.out.println(stringToPrint);
}
}
This class demonstrates how this approach works on different classes. When I run this Main class as part of a JAR, the output appears as shown below.
As the above screen snapshot demonstrates, the
getLocation()
method (and the toString()
) provide a URL to the JAR file from which the class was found. The command at the top of the output window shows that this class was indeed in the JAR because that JAR is the only item on the classpath.The next screen snapshot shows execution of the Main class using the
classes
directory in the classpath rather than using a JAR.In this case, we can see that the source of the classpath entries was the directory rather than a JAR file. What happens when both (the JAR and classes directory) are on the classpath? The next screen snapshot demonstrates this with the JAR first and then with the directory first.
The last screen capture indicates that whatever class definition appears first on the classpath (when reading from left to right) is the one used by the class loader for that application.
Finally, you may have noticed that the classes Integer and String from the core Java API have null values for
CodeSource
.Conclusion
There are situations when a mismatch of class definitions or multiply defined class definitions cause runtime headaches. In such cases, an approach like that demonstrated in this blog posting can be useful and provide some relief.