Tuesday, October 5, 2010

javac's -Xprint Option

With ready accessibility to the Java SDK API documentation and Java EE API documentation online and with the convenience of IDE method name completion, it is often easy to determine a class's public exposed API. However, even with these great tools at hand, I always like to be aware of useful command-line tools to supplement these tools. Such knowledge can be particularly useful if in an environment where the online documentation or properly working IDE are not as easily available or are less desirable because of their overhead. The Sun/Oracle javac compiler supports the non-standard (and hence not guaranteed to be in other Java compiler implementations or even in future Oracle javac implementations) -Xprint option to make this easy. A side benefit of this option is for generating a textual representation of a class's API.

The javac documentation describes the -Xprint option: "Print out textual representation of specified types for debugging purposes; perform neither annotation processing nor compilation. The format of the output may change."

The output of -Xprint on a .class file looks very similar to javap's output. For example, javac -Xprint shows the following output when run against java.lang.Object:

javac -Xprint java.lang.Object
package java.lang;

public class Object {

  public Object();

  private static native void registerNatives();

  public final native java.lang.Class getClass();

  public native int hashCode();

  public boolean equals(java.lang.Object arg0);

  protected native java.lang.Object clone() throws java.lang.CloneNotSupportedException;

  public java.lang.String toString();

  public final native void notify();

  public final native void notifyAll();

  public final native void wait(long arg0) throws java.lang.InterruptedException;

  public final void wait(long arg0,
    int arg1) throws java.lang.InterruptedException;

  public final void wait() throws java.lang.InterruptedException;

  protected void finalize() throws java.lang.Throwable;
}

Compare the above output from javac -Xprint on java.lang.Object to the output (shown next) of running javap against java.lang.Object:


Compiled from "Object.java"
public class java.lang.Object{
    public java.lang.Object();
    public final native java.lang.Class getClass();
    public native int hashCode();
    public boolean equals(java.lang.Object);
    protected native java.lang.Object clone()       throws java.lang.CloneNotSupportedException;
    public java.lang.String toString();
    public final native void notify();
    public final native void notifyAll();
    public final native void wait(long)       throws java.lang.InterruptedException;
    public final void wait(long, int)       throws java.lang.InterruptedException;
    public final void wait()       throws java.lang.InterruptedException;
    protected void finalize()       throws java.lang.Throwable;
    static {};
}

Of course, the output above is shown with javap's default settings. The private and protected members can be displayed using javap's -private and -protected options.

One advantage of javac -Xprint, however, is that the -Xprint option can be easily used with javac if one wants to see the interface for multiple files. The javac compiler can be used as normal against the normal source code files, but with the -Xprint option specified, the interfaces will be printed out without actually compiling the code. This is demonstrated here. I add the -Xprint option to my javac compiler when compiling the examples from my last blog post on javac -Xlint and get the following output:

package dustin.examples;

/**
 * Simple class intended to help demonstrate -Xlint:overrides by providing a
 * method that won't be overridden quite the same by its child.
 */
public class BaseClass {
  protected java.util.List<java.lang.String> names;

  public BaseClass();

  public void addNames(final java.lang.String[] newNames);
}
package dustin.examples;

/**
 * Simple class intended to help demonstrate -Xlint:overrides by "sort of"
 * overriding a method defined in its parent.
 */
public class ChildClass extends dustin.examples.BaseClass {

  public ChildClass();

  @java.lang.Override
  public void addNames(final java.lang.String... newNames);
}
package dustin.examples;

/**
 * Simple Color representation.
 */
public enum Color {

  BLACK,
  BLUE,
  BROWN,
  CORAL,
  EGGSHELL,
  GREEN,
  MAUVE,
  ORANGE,
  PINK,
  PURPLE,
  RED,
  TAN,
  WHITE,
  YELLOW;
  public static dustin.examples.Color[] values();

  public static dustin.examples.Color valueOf(java.lang.String name);

  private Color();
}
package dustin.examples;

/**
 * Gender.
 */
public enum Gender {

  FEMALE,
  MALE;
  public static dustin.examples.Gender[] values();

  public static dustin.examples.Gender valueOf(java.lang.String name);

  private Gender();
}
package dustin.examples;

/**
 * Main executable demonstrating HotSpot's non-standard Xlint warning flags.
 */
public class Main {

  public Main();
  /**
   *Fred. 
   */
  private static final dustin.examples.Person fred;
  /**
   *Wilma. 
   */
  private static final dustin.examples.Person wilma;
  /**
   *Barney. 
   */
  private static final dustin.examples.Person barney;

  /**
   * Demonstrates -Xlint:cast warning of a redundant cast.
   */
  private static void demonstrateCastWarning();

  /**
   * Cause -Xlint:deprecation to print warning about use of deprecated method.
   */
  private static void demonstrateDeprecationWarning();

  /**
   * Cause -Xlint:fallthrough to print warning about use of switch/case
   * fallthrough.
   */
  private static void demonstrateFallthroughWarning();

  /**
   * Demonstrate -Xlint:finally generating warning message when a {@code finally}
   * block cannot end normally.
   */
  private static void demonstrateFinallyWarning();

  /**
   * Divide the provided divisor into the provided dividend and return the
   * resulting quotient. No checks are made to ensure that divisor is not zero.
   * @param dividend Integer to be divided.
   * @param divisor Integer by which dividend will be divided.
   * @return Quotient of division of dividend by divisor.
   */
  private static double divideIntegersForDoubleQuotient(final int dividend,
    final int divisor);

  /**
   * Demonstrate -Xlint:divzero in action by dividing an int by a literal zero.
   */
  private static void demonstrateDivideByZeroWarning();

  /**
   * Surprisingly, there is no -Xlint warning option for this highly
   * suspicious situation of passing an object to a Set.contains call that
   * could not possibly hold that type of object (could never result to true).
   */
  private static void demonstrateNoContainsWarning();

  /**
   * This method demonstrates how javac's -Xlint:empty works. Note that javac's
   * -Xlint:empty will only flag the empty statement involved in the "if" block,
   * but does not flag the empty statements associated with the do-while loop,
   * the while loop, the for loop, or the if-else. NetBeans does flag these if
   * the appropriate "Hints" are turned on.
   */
  private static void demonstrateEmptyWarning();

  /**
   * Divide the provided divisor into the provided dividend and return the
   * resulting quotient. No checks are made to ensure that divisor is not zero.
   * @param dividend Integer to be divided.
   * @return Quotient of division of dividend by divisor.
   */
  private static long divideIntegerByZeroForLongQuotient(final int dividend);

  /**
   * Demonstrate the commonly seen -Xlint:unchecked in action.
   * @return Set of Person objects.
   */
  private static java.util.Set<dustin.examples.Person> demonstrateUncheckedWarning();

  /**
   * Main executable function to demonstrate -Xlint.  Various -Xlint options
   * are demonstrated as follows:
   * <ul>
   * <li>{@code -Xlint:cast}</li>: This class's method demonstrateCastWarning()
   *     demonstrates how a redundant cast can lead to this warning.</li>
   * <li>{@code -Xlint:deprecation}: This class's method demonstrateDeprecationWarning()
   *     intentionally invokes a deprecated method in the Person class.</li>
   * <li>{@code -Xlint:divzero}: This class's demonstrateDivideByZeroWarning()
   *     method demonstrates this warning generated when a literal zero is
   *     used as a divisor in integer division.</li>
   * <li>{@code -Xlint:empty}: This class's demonstateEmptyWarning() method
   *     demonstrates that (likely accidental) "if" expression without any
   *     result of the condition being {@code true} being performed results in
   *     a warning this option's set.
   * <li>{@code -Xlint:fallthrough}: This class's method demonstrateFallthroughWarning()
   *     demonstrates how {@code switch} statements with {@code case} expressions
   *     without their own {@code break} statements may or may not lead to
   *     this producing a warning message.</li>
   * <li>{@code -Xlint:finally}: This class's method demonstrateFinallyWarning()
   *     demonstrates the warning related to a return from a {@code finally}
   *     clause.</li>
   * <li>{@code -Xlint:overrides}: This is demonstrated in classes external to
   *     this one: {@code BaseClass} and its child class @{code ChildClass}.</li>
   * <li>{@code -Xlint:path}: This is shown by providing a path to the javac
   *     compiler's classpath option for a location that does not exist.</li>
   * <li>{@code -Xlint:serial}: The Person class implements the Serializable
   *     interface, but does not declare an explicit serialVersionUID.</li>
   * <li>{@code -Xlint:unchecked}: This class's demonstrateUncheckedWarning()
   *     demonstrates this warning message.</li>
   * </ul>
   * @param arguments Command-line arguments: none expected.
   */
  public static void main(final java.lang.String[] arguments);
}
package dustin.examples;

/**
 * Person class that intentionally has problems that will be flagged as warnings
 * by javac with -X non-standard options.
 */
public final class Person implements java.io.Serializable {
  private final java.lang.String lastName;
  private final java.lang.String firstName;
  private final dustin.examples.Gender gender;
  private final dustin.examples.Color favoriteColor;

  public Person(final java.lang.String newLastName,
    final java.lang.String newFirstName,
    final dustin.examples.Gender newGender,
    final dustin.examples.Color newFavoriteColor);

  public java.lang.String getLastName();

  public java.lang.String getFirstName();

  public java.lang.String getFullName();

  /**
   * Provide the person's full name.
   * @return Full name of this person.
   * @deprecated Use getFullName() instead.
   */
  @java.lang.Deprecated
  public java.lang.String getName();

  public dustin.examples.Gender getGender();

  public dustin.examples.Color getFavoriteColor();

  /**
   * NetBeans-generated equals(Object) method checks for equality of provided
   * object to me.
   * @param obj Object to be compared to me for equality.
   * @return {@code true} if the provided object and I are considered equal.
   */
  @java.lang.Override
  public boolean equals(java.lang.Object obj);

  /**
   * NetBeans-generated hashCode() method.
   * @return Hash code for this instance.
   */
  @java.lang.Override
  public int hashCode();

  @java.lang.Override
  public java.lang.String toString();
}

The output of my own classes shown above is a reminder of another nicety of javac -Xprint: it includes the Javadoc comments of the members and methods that it prints out when source code is available. This can be helpful in understanding what the parameters of the API methods are. If javac -Xprint is run directly against a .class file, it does not include the Javadoc comments; if it's run against the .java source file, it does include the Javadoc comments.

For example, if I change my directory to the installation directory of the Spring Framework, I can use the command
javac -Xprint src\org\springframework\core\Constants.java
to see the org.springframework.core.Constants class members and methods with accompanying Javadoc comments. Alternatively, I could run javac -Xprint against the compiled .class file as well (but won't see the Javadoc comments in that output) with the command

javac -cp dist\spring.jar -Xprint org.springframework.core.Constants

It is nice to be able to run javac -Xprint against compiled binaries (.class) or raw source (.java).

Another interesting facet of the javac -Xprint is that it demonstrates what annotation processors can do in Java SE 6.

I usually use an IDE and the online Javadoc-based API documentation to learn new APIs or remind myself of the specifics of APIs I don't use frequently. However, every once in a while, I find it useful to be aware of the existence of javap and of javac -Xprint for providing a quick reminder of the available APIs on my own classes and on third-party classes.

No comments: