Thursday, July 26, 2012

Java SE 6


NetBeans 7.1's Internal Compiler and JDK 6 Respecting Return Type for Method Overloading

I was first exposed to Java after several years of C++ experience and so it seemed natural when I learned that Java does not allow method overloading based on return type. The Defining Methodssection of the Classes and Objects lesson in the Java Language Tutorial states, "The compiler does not consider return type when differentiating methods, so you cannot declare two methods with the same signature even if they have a different return type." Indeed, as Vinit Joglekar has pointed out, "It is an accepted fact that Java does not support return-type-based method overloading." TheStackOverflow thread Java - why no return type based method overloading? explains why this is the case in Java. Given this, I was surprised when a colleague showed me a code snippet with two overloaded methods with the same runtime signature that compiled in JDK 6 as long as the return types differed.
The following class compiles successfully with JDK 6, but not with JDK 7.
Compiles in JDK 6 But Not in JDK 7
  1. package examples.dustin;  
  2.   
  3. import java.util.Collection;  
  4.   
  5. /** 
  6.  * Simple example that breaks in Java SE 7, but not in Java SE 6. 
  7.  *  
  8.  * @author Dustin 
  9.  */  
  10. public class Main  
  11. {  
  12.    public static String[] collectionToArray(final Collection<String> strings)  
  13.    {  
  14.       return new String[] { "five" };  
  15.    }  
  16.   
  17.    public static int[] collectionToArray(final Collection<Integer> integers)  
  18.    {  
  19.       return new int[] { 5 };  
  20.    }  
  21.      
  22.    /** 
  23.     * Main function. 
  24.     *  
  25.     * @param arguments The command line arguments; none expected. 
  26.     */  
  27.    public static void main(String[] arguments)  
  28.    {  
  29.    }  
  30. }  
As described in Angelika Langer's What Is Method Overloading?, the above code should not compile. It doesn't in Java SE 7. In NetBeans 7.1, it doesn't. Or, more properly, it's a mixed bag.
As the screen snapshot below demonstrates, NetBeans 7.1 builds the source code above fine as shown in the Output Window when the version of Java associated with the project is Java SE 6. However, the NetBeans editor shows the red squiggly lines indicating compiler error. The next image shows what the error message is.
Although NetBeans 7.1 is able to build the code shown above when it's part of a project associated with Java SE 6 (Update 31 in this case), the code editor still reports the error shown above. This is because NetBeans uses a different version of the Java compiler internally than the one explicitly associated with the project being edited. If I change the version of Java associated with the NetBeans project for the source code above, it will no longer build in NetBeans. This is shown next.
There are a couple interesting things about this bug. First, the fact that this code compiles fine in Java SE 6 but is addressed and does not compile in Java SE 7 means that it is possible for code working in Java SE 6 to not work when the code base is moved to Java SE 7. I downloaded the latest version of JDK 6 available (Java SE 6 Update 31) and confirmed the original code shown above still builds in Java SE 6. It does not build in Java SE 7.
There are other versions of the code above that do not build in Java SE 6 or in Java SE 7. For example, if the code above is changed so that the methods return the same type, the code doesn't build even in Java SE 6. Similarly, if the Collection parameters to the two overloaded methods include a "raw" Collection (no parameterized type), it won't compile in Java SE 6 either. Of course, even if the return types are different, if the same Collection parameterized types are passed to both overloaded methods, even Java SE 6 won't compile this. These three situation are depicted in the following three screen snapshots.
The code that builds in Java SE 6 but not in Java SE 7 needs to have overloaded methods that differ in both return types and in terms of the parameterized types of the collections that make up their method parameters. It doesn't matter if a given return type matches or is related to the parameterized type of the method's parameter as long as they differ. If the return types are the same, Java SE 6 detects a compiler error. Java SE 6 also detects the error if the erased parameters boil down to the same collection after erasure and the return types are not different.
A second interesting thing about this bug is how its handled in NetBeans. Because NetBeans use itsown internal compiler that does not necessarily match the version of the compiler that the developer has associated the IDE project to, you can run into situations like this where the code actually builds in the IDE, but the IDE's functionality such as code editors and project browsers indicate the code breaking.
Because NetBeans 7.1 uses its own internal Java compiler for the code editor, one might wonder if this means Java 7 features could be sneaked in and would work in the IDE but then would not build when attempted from the command line or when explicitly built in the IDE. The next screen snapshot demonstrates why that is not the case. In that snapshot, a Java 7 specific feature is in the code and NetBeans 7.1 properly warns that this is not compatible with the Java 1.6 source setting.
Bug 6182950 (methods clash algorithm should not depend on return type) has addressed the issue in JDK 7, but not in JDK 6. A related bug is Bug 6730568 ("Type erasure affects return types + type parameters"). Three additional references that provide sufficiently more background details are twoStackOverflow threads (Differing behaviour between Java 5 & 6 when overloading generic methodsand What is the concept of erasure in generics in java?) and the Java Tutorial entry on Type Erasure.
The colleague who showed me this issue realized its existence because NetBeans 7.1 reported the "name clash ... have the same erasure" even when he was working with Java SE 6 code. This discovery was "accidental" due to the newer version of NetBeans using Java SE 7 compiler internally, but he welcomed the opportunity to fix the issue now rather than when he migrates to Java SE 7.
I found this issue worth posting a blog post on because it provides a warning about a bug that may already be in some Java SE 6 code bases but will be made all too evident when the code base is moved to Java SE 7. I also posted this because I think it's important to be aware that modern versions of NetBeans use an internal compiler that may be of a different version than the compiler the developer has explicitly associated with his or her NetBeans project.


A Plethora of Java Developments in February 2012

There are several sites (Java.netJavaWorldJavaLobby/DZoneJava reddit, and Java Code Geeks) that I like to browse for the latest Java news. These sites are great and bring the best from around the web from the wider Java community. A nice complement to these sites is Oracle Technology Network'sJava page. There are several Java stories available on the OTN Java page that originate from Oracle and its employees that are of interest to Java developers. I briefly summarize and link to a subset of these in this blog post.
New Java Language and Java VM SpecificationsAlex Buckley's post JLS7 and JVMS7 online announces the availability of new versions of the Java Language Specification and of the Java Virtual Machine Specification. Besides announcing the availability of these new specifications associated explicitly with Java SE 7, the post also provides some interesting background regarding the history of these two specifications. For example, Buckley states, "Only a major Java SE release can change the Java language and JVM." I also find it interesting that these specifications no longer have names based on their edition (was Third Edition for JLS and Second Edition for JVMS). Instead, these two specifications are now named for the edition of Java SE they are associated with. To me, that's much clearer. You may wonder why this wasn't done in the first place and Buckley explains that, "Historically, the JLS and JVMS pre-date the Java Community Process so there was no Java SE platform to which they could be tied." The specifications are available in HTML or PDF format and it is anticipated that they will be published in printed book format in the future.
Java SE 6 End of Life ExtendedHenrik Stahl uses the post Updated Java 6 EOL date to announce that the JDK6 "EOL date has been extended from July 2012 to November 2012, to allow some more time for the transition to JDK 7." He also highlights portions of the updated EOL policy. The Oracle Java SE Support Roadmap (AKA "Java SE EOL Policy") was updated on 15 February 2012 with this new EOL date.
New Java UpdatesThe Java SE News blog contains posts regarding newly available Java updates. The titles of the posts say it all: Java 7 Update 3 and Java 6 Update 31 have released!7u4 Developer Preview is now Available, and 6u32 Developer Preview is now Available.
JSR 354: Money and Currency APIThe JCP Program Office blog features a post announcing JSR 354: Money and Currency API. This JSR proposal describes deficiencies with the already available java.util.Currency class that will be addressed by the JSR. The "proposed Specification" section states:
This JSR will provide a money and currency API for Java, targeted at all users of currencies and monetary amounts in Java. The API will provide support for standard ISO-4217 and custom currencies, and a representation of a monetary amount. It will support currency arithmetic, even across different currencies, and will support foreign currency exchange. Additionally, implementation details surrounding serialization and thread safety are to be considered.
It sounds like there is some optimism about this making it into Java SE 8.
JavaFX 2 Developer CommunityNicolas Lorain writes in JavaFX 2 and the developer community that "JavaFX 2 was only released in October 2011, but there's already a thriving developer community kicking the tires of the new kid on the block." He adds, "There's no denying that we've pretty much started from scratch with JavaFX 2." Lorain then provides evidence of the growing JavaFX 2 community that includes increasing number of discussion threads on the JavaFX 2.0 and Later forum, the developer community contributing roughly 20% of the bug reports related to JavaFX, an "increasing number of people interested in JavaFX are following me" (@javafx4you), and number of community blog posts on JavaFX (references JavaFX Links of the Week). Lorain concludes, "pretty much all the [metrics] I've seen show that JavaFX is growing in terms of popularity."
Incidentally, one of the co-authors of Pro JavaFX 2: A Definitive Guide to Rich Clients with Java Technology has provided some details about that book which will soon be in print and is already available in electronic format.
ConclusionThe Java development community seems more lively and more energetic in recent months (especially since JavaOne 2011) than it has been for years. After years of seeming stagnation, Java-related developments appear to be coming at us more quickly again. It is nice to have so many online forums to get information about these developments.


Generating XML Schema with schemagen and Groovy

I have previously blogged on several utilitarian tools that are provided with the Java SE 6 HotSpot SDK such as jstackjavap, and so forth. I focus on another tool in the same $JAVA_HOME/bin (or%JAVA_HOME%\bin directory: schemagen. Although schemagen is typically used in conjunction with web services and/or JAXB, it can be useful in other contexts as well. Specifically, it can be used as an easy way to create a starting point XML Schema Definition (XSD) for someone who is more comfortable with Java than with XML Schema.

We'll begin with a simple Java class called Person to demonstrate the utility of schemagen. This is shown in the next code listing.

  1. package dustin.examples;  
  2.   
  3. public class Person  
  4. {  
  5.    private String lastName;  
  6.   
  7.    private String firstName;  
  8.   
  9.    private char middleInitial;  
  10.   
  11.    private String identifier;  
  12.   
  13.    /** 
  14.     * No-arguments constructor required for 'schemagen' to create XSD from 
  15.     * this Java class.  Without this "no-arg default constructor," this error 
  16.     * message will be displayed when 'schemagen' is attempted against it: 
  17.     * 
  18.     *      error: dustin.examples.Person does not have a no-arg default 
  19.     *      constructor. 
  20.     */  
  21.    public Person() {}  
  22.   
  23.    public Person(final String newLastName, final String newFirstName)  
  24.    {  
  25.       this.lastName = newLastName;  
  26.       this.firstName = newFirstName;  
  27.    }  
  28.   
  29.    public Person(  
  30.       final String newLastName,  
  31.       final String newFirstName,  
  32.       final char newMiddleInitial)  
  33.    {  
  34.       this.lastName = newLastName;  
  35.       this.firstName = newFirstName;  
  36.       this.middleInitial = newMiddleInitial;  
  37.    }  
  38.   
  39.    public String getLastName()  
  40.    {  
  41.       return this.lastName;  
  42.    }  
  43.   
  44.    public void setLastName(final String newLastName)  
  45.    {  
  46.       this.lastName = newLastName;  
  47.    }  
  48.   
  49.    public String getFirstName()  
  50.    {  
  51.       return this.firstName;  
  52.    }  
  53.   
  54.    public void setFirstName(final String newFirstName)  
  55.    {  
  56.       this.firstName = newFirstName;  
  57.    }  
  58.   
  59.    public char getMiddleInitial()  
  60.    {  
  61.       return this.middleInitial;  
  62.    }  
  63. }  

The class above is very simple, but is adequate for the first example of employing schemagen. As the comment on the no-arguments constructor in the above code states, a constructor without arguments (sometimes called a "default constructor") must be available in the class. Because other constructors are in this class, it is required that a no-args constructor be explicitly specified. I also intentionally provided get/set (accesor/mutator) methods for some of the fields, only an accessor for one of the fields, and neither for a field to demonstrate that schemagen requires get/set methods to be specified if the schema it generates includes a reference to those attributes.

The next screen snapshot demonstrates the most simple use of schemagen in which the generated XML schema file (.xsd) is generated with the default name of schema1.xsd (there is no current way to control this directly with schemagen) and is placed in the same directory from which the schemagencommand is run (output location can be dictated with the -d option).


The generated XSD is shown next.

schema1.xsd
  1. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>  
  2. <xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">  
  3.   
  4.   <xs:complexType name="person">  
  5.     <xs:sequence>  
  6.       <xs:element name="firstName" type="xs:string" minOccurs="0"/>  
  7.       <xs:element name="lastName" type="xs:string" minOccurs="0"/>  
  8.     </xs:sequence>  
  9.   </xs:complexType>  
  10. </xs:schema>  

This is pretty convenient, but is even easier with Groovy. Suppose that one wanted to generate an XSD using schemagen and did not care about or need the original Java class. The following very simple Groovy class could be used. Very little effort is required to write this, but it's compiled .classfile can be used with schemagen.

  1. package dustin.examples;  
  2.   
  3. public class Person2  
  4. {  
  5.    String lastName;  
  6.   
  7.    String firstName;  
  8.   
  9.    char middleInitial;  
  10.   
  11.    String identifier;  
  12. }  

When the above Groovy class is compiled with groovyc, its resulting Person2.class file can be viewed through another useful tool (javap) located in the same directory as schemagen. This is shown in the next screen snapshot. The most important observation is that get/set methods have been automatically generated by Groovy.


When the groovyc-generated .class file is run through schemagen, the XSD is generated and is shown next.

  1. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>  
  2. <xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">  
  3.   
  4.   <xs:complexType name="person2">  
  5.     <xs:sequence>  
  6.       <xs:element name="firstName" type="xs:string" minOccurs="0"/>  
  7.       <xs:element name="identifier" type="xs:string" minOccurs="0"/>  
  8.       <xs:element name="lastName" type="xs:string" minOccurs="0"/>  
  9.       <xs:element name="middleInitial" type="xs:unsignedShort"/>  
  10.     </xs:sequence>  
  11.   </xs:complexType>  
  12. </xs:schema>  

Because I did not explicitly state that Groovy's automatic get/set methods should not be applied, all attributes are represented in the XML. Very little Groovy, but XSD nonetheless.

It is interesting to see what happens when the attributes of the Groovy class are untyped. The next Groovy class listing does not explicitly type the class attributes.

  1. package dustin.examples;  
  2.   
  3. public class Person2  
  4. {  
  5.    def lastName;  
  6.   
  7.    def firstName;  
  8.   
  9.    def middleInitial;  
  10.   
  11.    def identifier;  
  12. }  

When schemagen is run against the above class with untyped attributes, the output XSD looks like this:

  1. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>  
  2. <xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">  
  3.   
  4.   <xs:complexType name="person2">  
  5.     <xs:sequence>  
  6.       <xs:element name="firstName" type="xs:anyType" minOccurs="0"/>  
  7.       <xs:element name="identifier" type="xs:anyType" minOccurs="0"/>  
  8.       <xs:element name="lastName" type="xs:anyType" minOccurs="0"/>  
  9.       <xs:element name="middleInitial" type="xs:anyType" minOccurs="0"/>  
  10.     </xs:sequence>  
  11.   </xs:complexType>  
  12. </xs:schema>  

Not surprisingly, the Groovy class with the untyped attributes leads to an XSD with elements ofanyType. It is remarkably easy to generate Schema with schemagen from a Groovy class, but what if I don't want an attribute of the class to be part of the generated schema? Explicitly specifying an attribute as private communicates to Groovy to not automatically generate get/set methods and hence schemagen will not generate XSD elements for those attributes. The next Groovy class shows two attributes explicitly defined as private and the resultant XSD from running schemagen against the compiled Groovy class is then shown.

  1. package dustin.examples;  
  2.   
  3. public class Person2  
  4. {  
  5.    String lastName;  
  6.   
  7.    String firstName;  
  8.   
  9.    /** private modifier prevents auto Groovy set/get methods */  
  10.    private String middleInitial;  
  11.   
  12.    /** private modifier prevents auto Groovy set/get methods */  
  13.    private String identifier;  
  14. }  

  1. <?xml version="1.0" encoding="UTF-8" standalone="yes"?>  
  2. <xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">  
  3.   
  4.   <xs:complexType name="person2">  
  5.     <xs:sequence>  
  6.       <xs:element name="firstName" type="xs:string" minOccurs="0"/>  
  7.       <xs:element name="lastName" type="xs:string" minOccurs="0"/>  
  8.     </xs:sequence>  
  9.   </xs:complexType>  
  10. </xs:schema>  

Groovy makes it really easy to generate an XSD. The Groovy code required to do so is barely more than a list of attributes and their data types.


Conclusion

The schemagen tool is a highly useful tool most commonly used in conjunction with web services and with JAXB, but I have found several instances where I have needed to create a "quick and dirty" XSD file for a variety of purposes. Taking advantage of Groovy's automatically generated set/get methods and other Groovy conciseness makes it really easy to generate a simple XSD.

Groovy, JMX, and the Attach API

One of the exciting new features of the (then Sun) HotSpot Java SE 6 release was support for theAttach API. The Attach API is "a Sun Microsystems extension that provides a mechanism to attach to a Java virtual machine." Tools such as JConsole and VisualVM "use the Attach API to attach to a target virtual machine and load its tool agent into that virtual machine." Custom Java and Groovy clients can likewise use the Attach API to monitor and manage JVMs.

There are several online resources that demonstrate Java client code that uses the Attach API. These include Daniel Fuchs's code listing for a JVMRuntimeClient, the "Setting up Monitoring and Management Programmatically" section of "Monitoring and Management Using JMX Technology," theCore Java Technology Tech Tip called "The Attach API," and the Javadoc API documentation for the class com.sun.tools.attach.VirtualMachine. These examples generally demonstrate usingVirtualMachine.attach(String) to attach to the virtual machine based on its process ID in String form. This is generally followed by loading the appropriate agent with VirtualMachine.loadAgent(String), where the String parameter represents the path to the JAR file containing the agent. TheVirtualMachine.detach() method can be called to detach from the previously attached JVM.

All of the previously mentioned examples demonstrate use of the Attach API from Java clients. In this post, I demonstrate use of the Attach API via Groovy. The three code listings that follow present three pieces of Groovy code but all work together as a single script. The main script is embodied in the Groovy file getJvmThreadInfo.groovy and is a simple script file that calls the other two Groovy script files (attachToVirtualMachine.groovy and displayMxBeanDerivedInfo.groovy) to attach to the virtual machine and to display details regarding that virtual machine via its MXBeans.

getJvmDetails.groovy
  1. #!/usr/bin/env groovy  
  2. // getJvmDetails.groovy  
  3. //  
  4. // Main script for extracting JVM details via Attach API and JMX.  
  5. // Accepts single parameter which is the process ID (pid) of the Java application  
  6. // whose JVM is to be connected to.  
  7. //  
  8. import static attachToVirtualMachine.retrieveConnector  
  9. import static displayMxBeanDerivedInfo.*  
  10.   
  11. def serverConnection = attachToVirtualMachine.retrieveServerConnection(args[0])  
  12.   
  13. displayMxBeanDerivedInfo.displayThreadInfo(serverConnection)  
  14. displayMxBeanDerivedInfo.displayOperatingSystemInfo(serverConnection)  
  15. displayMxBeanDerivedInfo.displayRuntimeInfo(serverConnection)  
  16. displayMxBeanDerivedInfo.displayMemoryInfo(serverConnection)  

attachToVirtualMachine.groovy
  1. // attachToVirtualMachine.groovy  
  2. //  
  3. // Provide an MBeanServerConnection acquired via the Attach API.  
  4.   
  5. import javax.management.MBeanServerConnection  
  6. import javax.management.remote.JMXConnector  
  7. import javax.management.remote.JMXConnectorFactory  
  8. import javax.management.remote.JMXServiceURL  
  9.   
  10. import com.sun.tools.attach.VirtualMachine  
  11.   
  12.   
  13. /** 
  14.  * Provide an MBeanServerConnection based on the provided process ID (pid). 
  15.  * 
  16.  * @param pid Process ID of Java process for which MBeanServerConnection is 
  17.  *    desired. 
  18.  * @return MBeanServerConnection connecting to Java process identified by pid. 
  19.  */  
  20. def static MBeanServerConnection retrieveServerConnection(String pid)  
  21. {  
  22.    println "Get JMX Connector for pid ${pid}!"  
  23.    def connectorAddressStr = "com.sun.management.jmxremote.localConnectorAddress"  
  24.    def jmxUrl = retrieveUrlForPid(pid, connectorAddressStr)  
  25.    def jmxConnector = JMXConnectorFactory.connect(jmxUrl)  
  26.    return jmxConnector.getMBeanServerConnection()  
  27. }  
  28.   
  29.   
  30. /** 
  31.  * Provide JMX URL for attaching to the provided process ID (pid). 
  32.  * 
  33.  * @param @pid Process ID for which JMX URL is needed to connect. 
  34.  * @param @connectorAddressStr String for connecting. 
  35.  * @return JMX URL to communicating with Java process identified by pid. 
  36.  */  
  37. def static JMXServiceURL retrieveUrlForPid(String pid, String connectorAddressStr)  
  38. {  
  39.    // Attach to the target application's virtual machine  
  40.    def vm = VirtualMachine.attach(pid)  
  41.   
  42.    // Obtain Connector Address  
  43.    def connectorAddress =  
  44.       vm.getAgentProperties().getProperty(connectorAddressStr)  
  45.   
  46.    // Load Agent if no connector address is available  
  47.    if (connectorAddress == null)  
  48.    {  
  49.       def agent = vm.getSystemProperties().getProperty("java.home") +  
  50.           File.separator + "lib" + File.separator + "management-agent.jar"  
  51.       vm.loadAgent(agent)  
  52.   
  53.       // agent is started, get the connector address  
  54.       connectorAddress =  
  55.          vm.getAgentProperties().getProperty(connectorAddressStr)  
  56.    }  
  57.   
  58.    return new JMXServiceURL(connectorAddress);  
  59. }  

displayMxBeanDerivedInfo.groovy
  1. // displayMxBeanDerivedInfo.groovy  
  2. //  
  3. // Display details regarding attached virtual machine and associated MXBeans.  
  4.   
  5. import java.lang.management.ManagementFactory  
  6. import java.lang.management.MemoryMXBean  
  7. import java.lang.management.OperatingSystemMXBean  
  8. import java.lang.management.RuntimeMXBean  
  9. import java.lang.management.ThreadMXBean  
  10. import javax.management.MBeanServerConnection  
  11.   
  12. /** 
  13.  * Display thread information based on ThreadMXBean associated with the provided 
  14.  * MBeanServerConnection. 
  15.  * 
  16.  * @param server MBeanServerConnection to use for obtaining thread information 
  17.  *    via the ThreadMXBean. 
  18.  */  
  19. def static void displayThreadInfo(MBeanServerConnection server)  
  20. {  
  21.    def remoteThreadBean = ManagementFactory.newPlatformMXBeanProxy(  
  22.                              server,  
  23.                              ManagementFactory.THREAD_MXBEAN_NAME,  
  24.                              ThreadMXBean.class);  
  25.   
  26.    println "Deadlocked Threads: ${remoteThreadBean.findDeadlockedThreads()}"  
  27.    println "Monitor Deadlocked Threads: ${remoteThreadBean.findMonitorDeadlockedThreads()}"  
  28.    println "Thread IDs: ${Arrays.toString(remoteThreadBean.getAllThreadIds())}"  
  29.    def threads = remoteThreadBean.dumpAllThreads(truetrue);  
  30.    threads.each  
  31.    {  
  32.       println "\t${it.getThreadName()} (${it.getThreadId()}): ${it.getThreadState()}"  
  33.    }  
  34. }  
  35.   
  36.   
  37. /** 
  38.  * Display operating system information based on OperatingSystemMXBean associated 
  39.  * with the provided MBeanServerConnection. 
  40.  * 
  41.  * @param server MBeanServerConnection to use for obtaining operating system 
  42.  *    information via the OperatingSystemMXBean. 
  43.  */  
  44. def static void displayOperatingSystemInfo(MBeanServerConnection server)  
  45. {  
  46.    def osMxBean = ManagementFactory.newPlatformMXBeanProxy(  
  47.                      server,  
  48.                      ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME,  
  49.                      OperatingSystemMXBean.class)  
  50.    println "Architecture: ${osMxBean.getArch()}"  
  51.    println "Number of Processors: ${osMxBean.getAvailableProcessors()}"  
  52.    println "Name: ${osMxBean.getName()}"  
  53.    println "Version: ${osMxBean.getVersion()}"  
  54.    println "System Load Average: ${osMxBean.getSystemLoadAverage()}"  
  55. }  
  56.   
  57.   
  58. /** 
  59.  * Display operating system information based on RuntimeMXBean associated with 
  60.  * the provided MBeanServerConnection. 
  61.  * 
  62.  * @param server MBeanServerConnection to use for obtaining runtime information 
  63.  *    via the RuntimeMXBean. 
  64.  */  
  65. def static void displayRuntimeInfo(MBeanServerConnection server)  
  66. {  
  67.    def remoteRuntime = ManagementFactory.newPlatformMXBeanProxy(  
  68.                           server,  
  69.                           ManagementFactory.RUNTIME_MXBEAN_NAME,  
  70.                           RuntimeMXBean.class);  
  71.   
  72.    println "Target Virtual Machine: ${remoteRuntime.getName()}"  
  73.    println "Uptime: ${remoteRuntime.getUptime()}"  
  74.    println "Classpath: ${remoteRuntime.getClassPath()}"  
  75.    println "Arguments: ${remoteRuntime.getInputArguments()}"  
  76. }  
  77.   
  78.   
  79. /** 
  80.  * Display operating system information based on MemoryMXBean associated with 
  81.  * the provided MBeanServerConnection. 
  82.  * 
  83.  * @param server MBeanServerConnection to use for obtaining memory information 
  84.  *    via the MemoryMXBean. 
  85.  */  
  86. def static void displayMemoryInfo(MBeanServerConnection server)  
  87. {  
  88.    def memoryMxBean = ManagementFactory.newPlatformMXBeanProxy(  
  89.                          server,  
  90.                          ManagementFactory.MEMORY_MXBEAN_NAME,  
  91.                          MemoryMXBean.class);  
  92.    println "HEAP Memory: ${memoryMxBean.getHeapMemoryUsage()}"  
  93.    println "Non-HEAP Memory: ${memoryMxBean.getNonHeapMemoryUsage()}"  
  94. }  

The three Groovy code listings above together form a script that will use the Attach API to contact to an executing JVM without host or port specified and solely based on the provided process ID. The examples demonstrate use of several of the available MXBeans built into the virtual machine. Because it's Groovy, the code is somewhat more concise than its Java equivalent, especially because no checked exceptions must be explicitly handled and there is no need for explicit classes.

Much more could be done with the information provided via the Attach API and the MXBeans. For example, the Groovy script could be adjusted to persist some of the gathered details to build reports, Java mail could be used to alert individuals when memory constraints or other issues requiring notice occurred, and nearly anything else that can be done in Java could be added to these client scripts to make it easier to monitor and manage Java applications.

Running with the Attach API

The main implementation class of the Attach API, VirtualMachine, is located in the${JAVA_HOME}\lib\tools.jar or %JAVA_HOME\lib\tools.jar JAR file included with the HotSpot SDK distribution. This file typically needs to be explicitly placed on the classpath of the Java client that uses the Attach API unless it is otherwise placed in a directory that is part of that inherent classpath. This is typically not required when using Groovy because it's normally already in Groovy's classpath. I briefly demonstrated this in the post Viewing Groovy Application's Classpath.

Conclusion

The Attach API makes it easier for the Java (or Groovy) developer to write clients that can communicate with, manage, and monitor Java processes. The Attach API provides the same benefits to the developer of custom JMX clients that JConsole and VisualVM leverage.

HotSpot JVM Options Displayed: -XX:+PrintFlagsInitial and -XX:+PrintFlagsFinal

Inspecting HotSpot JVM Options is a great post for those wishing to understand better the options provided by Oracle's (formerly Sun's) HotSpot Java Virtual Machine. In this thorough post, Zahid Qureshi discusses how to use the option -XX:+PrintFlagsFinal in conjunction with -XX:+UnlockDiagnosticVMOptions to "dump out every JVM option and its value." Zahid goes further than this and runs these flags against the HotSpot JVM in both client (his client output here) and server mode (his server output here), compares/diffs the options each uses (his diff results here), and analyzes some of the differences. In doing so, Zahid also demonstrates the "super option" -XX:+AggressiveOpts.

Before reading Zahid's post, I had never seen or read about the XX:+PrintFlagsFinal option. After downloading the latest SDK (Java SE 6 Update 23), I was able to use the option.

Neither specifying -version or specifying -XX:+UnlockDiagnosticVMOptions are required to use the -XX:+PrintFlagsFinal option, but there are advantages of doing so. The advantage of specifying -version is that doing so leads to only the version information being printed after the options rather than the longer Java application launcher usage being printed. The advantage of specifying the unlocking of diagnostic VM options with -XX:+UnlockDiagnosticVMOptions is that diagnostic VM options are included in the output. This is shown in the next screen snapshot.


The -XX:+PrintFlagsFinal (emphasis on "Final") option displays what options HotSpot ended up using for running Java code while -XX:+PrintFlagsInitial (emphasis on "Initial") displays what options were provided to HotSpot initially, before HotSpot has made its own tweaks. Comparing the results of -XX:+PrintFlagsFinal to -XX:+PrintFlagsInitial can obviously be helpful in understanding optimizations that HotSpot has made. More details on these options are available in Re: Bleeding Edge JVM Options and SDN Bug 6914622.

I haven't seen any formal explanation regarding these options (Zahid refers to one of them being "hidden away in the JVM source code" - an advantage of open source!). From the output generated, however, it is possible to gain a good idea of what is displayed. The output of running java -XX:+PrintFlagsFinal -XX:+UnlockDiagnosticVMOptions -version is text with a header row stating [Global flags] followed by numerous rows with one option and its metdata per row. Each row of the output represents a particular global option and has four columns.

The first column appears to reflect the data type of the option (intx, uintx, uint64_t, bool, double, ccstr, ccstrlist). The second column is the name of the flag and the third column is the value, if any, that the flag is set to. The fourth column appears to indicate the type of flag and has values such as {product},{pd product}, {C1 product} for client or {C2 product} for server, {C1 pd product} for client or {C2 pd product} for server, {product rw}{diagnostic} (only if -XX:+UnlockDiagnosticVMOptions was specified), {experimental}, and {manageable}. See Eugene Kuleshov's The most complete list of -XX options for Java 6 JVM for a brief description of most of these categories as well as a listing of most of these options themselves.

The plethora of virtual machine options offered makes it possible to understand and control the virtual machine's behavior more granularly. Although a small subset of the virtual machine options for HotSpotare available online, it is always nice to be able to list them explicitly when needed. The option -XX:+PrintFlagsFinal does just that.

Reproducing "too many constants" Problem in Java

In my previous blog post, I blogged on the "code too large" problem and reproduced that error message. In this post, I look at the very similar "too many constants" error message (not the same thing as the question too many constants?) and demonstrate reproducing it by having too many methods in a generated Java class.

With a few small adaptations, I can adjust the Groovy script that I used to generate a Java class to reproduce the "code too large" error to instead generate a Java class to reproduce the "too many constants" error. Here is the revised script.

generateJavaClassWithManyMethods.groovy
  1. #!/usr/bin/env groovy  
  2.   
  3. import javax.tools.ToolProvider  
  4.   
  5. println "You're running the script ${System.getProperty('script.name')}"  
  6. if (args.length < 2)  
  7. {  
  8.    println "Usage: javaClassGenerationWithManyMethods packageName className baseDir #methods"  
  9.    System.exit(-1)  
  10. }  
  11.   
  12. // No use of "def" makes the variable available to entire script including the  
  13. // defined methods ("global" variables)  
  14.   
  15. packageName = args[0]  
  16. packagePieces = packageName.tokenize(".")  // Get directory names  
  17. def fileName = args[1].endsWith(".java") ? args[1] : args[1] + ".java"  
  18. def baseDirectory = args.length > 2 ? args[2] : System.getProperty("user.dir")  
  19. numberOfMethods = args.length > 3 ? Integer.valueOf(args[3]) : 10  
  20.   
  21. NEW_LINE = System.getProperty("line.separator")  
  22.   
  23. // The setting up of the indentations shows off Groovy's easy feature for  
  24. // multiplying Strings and Groovy's tie of an overloaded * operator for Strings  
  25. // to the 'multiply' method.  In other words, the "multiply" and "*" used here  
  26. // are really the same thing.  
  27. SINGLE_INDENT = '   '  
  28. DOUBLE_INDENT = SINGLE_INDENT.multiply(2)  
  29. TRIPLE_INDENT = SINGLE_INDENT * 3  
  30.   
  31. def outputDirectoryName = createDirectories(baseDirectory)  
  32. def generatedJavaFile = generateJavaClass(outputDirectoryName, fileName)  
  33. compileJavaClass(generatedJavaFile)  
  34.   
  35.   
  36. /** 
  37.  * Generate the Java class and write its source code to the output directory 
  38.  * provided and with the file name provided.  The generated class's name is 
  39.  * derived from the provided file name. 
  40.  * 
  41.  * @param outDirName Name of directory to which to write Java source. 
  42.  * @param fileName Name of file to be written to output directory (should include 
  43.  *    the .java extension). 
  44.  * @return Fully qualified file name of source file. 
  45.  */  
  46. def String generateJavaClass(outDirName, fileName)  
  47. {  
  48.    def className = fileName.substring(0,fileName.size()-5)  
  49.    outputFileName = outDirName.toString() + File.separator + fileName  
  50.    outputFile = new File(outputFileName)  
  51.    outputFile.write "package ${packageName};${NEW_LINE.multiply(2)}"    
  52.    outputFile << "public class ${className}${NEW_LINE}"    
  53.    outputFile << "{${NEW_LINE}"  
  54.    outputFile << "${SINGLE_INDENT}public static void main(final String[] arguments)"  
  55.    outputFile << "${NEW_LINE}${SINGLE_INDENT}{${NEW_LINE}"  
  56.    outputFile << DOUBLE_INDENT << 'final String someString = "Dustin";' << NEW_LINE  
  57.    outputFile << "${SINGLE_INDENT}}${NEW_LINE}"  
  58.    outputFile << buildManyMethods()  
  59.    outputFile << "}"  
  60.    return outputFileName  
  61. }  
  62.   
  63.   
  64. /** 
  65.  * Compile the provided Java source code file name. 
  66.  * 
  67.  * @param fileName Name of Java file to be compiled. 
  68.  */  
  69. def void compileJavaClass(fileName)  
  70. {  
  71.    // Use the Java SE 6 Compiler API (JSR 199)  
  72.    // http://java.sun.com/mailers/techtips/corejava/2007/tt0307.html#1  
  73.    compiler = ToolProvider.getSystemJavaCompiler()  
  74.      
  75.    // The use of nulls in the call to JavaCompiler.run indicate use of defaults  
  76.    // of System.in, System.out, and System.err.   
  77.    int compilationResult = compiler.run(nullnullnull, fileName)  
  78.    if (compilationResult == 0)  
  79.    {  
  80.       println "${fileName} compiled successfully"  
  81.  }  
  82.    else  
  83.    {  
  84.       println "${fileName} compilation failed"  
  85.    }  
  86. }  
  87.   
  88.   
  89. /** 
  90.  * Create directories to which generated files will be written. 
  91.  * 
  92.  * @param baseDir The base directory used in which subdirectories for Java 
  93.  *    source packages will be generated. 
  94.  */  
  95. def String createDirectories(baseDir)  
  96. {  
  97.    def outDirName = new StringBuilder(baseDir)  
  98.    for (pkgDir in packagePieces)  
  99.    {  
  100.       outDirName << File.separator << pkgDir  
  101.    }  
  102.    outputDirectory = new File(outDirName.toString())  
  103.    if (outputDirectory.exists() && outputDirectory.isDirectory())  
  104.    {  
  105.       println "Directory ${outDirName} already exists."  
  106.    }  
  107.    else  
  108.    {  
  109.       isDirectoryCreated = outputDirectory.mkdirs()  // Use mkdirs in case multiple  
  110.       println "Directory ${outputDirectoryName} ${isDirectoryCreated ? 'is' : 'not'} created."  
  111.    }  
  112.    return outDirName.toString()  
  113. }  
  114.   
  115.   
  116. /** 
  117.  * Generate the body of generated Java class source code's main function. 
  118.  */  
  119. def String buildManyMethods()  
  120. {  
  121.    def str = new StringBuilder() << NEW_LINE  
  122.    for (i in 0..numberOfMethods)  
  123.    {  
  124.       str << SINGLE_INDENT << "private void doMethod${i}(){}" << NEW_LINE  
  125.    }  
  126.    return str  
  127. }  


When the above script is run with a parameter of 5 for the number of methods, the following Java code is generated.

  1. package dustin.examples;  
  2.   
  3. public class LotsOfMethods  
  4. {  
  5.    public static void main(final String[] arguments)  
  6.    {  
  7.       final String someString = "Dustin";  
  8.    }  
  9.   
  10.    private void doMethod0(){}  
  11.    private void doMethod1(){}  
  12.    private void doMethod2(){}  
  13.    private void doMethod3(){}  
  14.    private void doMethod4(){}  
  15.    private void doMethod5(){}  
  16. }  


When I turn up the number of generated methods to 65000 methods, I run out of heap space as shown in the next screen snapshot.



The next screen snapshot shows the output of running the script again, but this time with 512 MB maximum heap space specified for the JVM.



What happens when we try to compile a class with too many methods? That is shown in the next screen snapshot that demonstrates what happens when just such a compilation is attempted.



The "too many constants" error message is shown with a pointer at the class keyword in the class definition. The method has too many methods to compile.

When I run javap -c -private dustin.examples.LotsOfMethods (-c to disassemble the code, -private to display the many private methods, and dustin.examples.LotsOfMethods is the name of the generated Java class), I see output like the following (only the first and end shown instead of displaying all 60,000+ methods).

Compiled from "LotsOfMethods.java"
public class dustin.examples.LotsOfMethods extends java.lang.Object{
public dustin.examples.LotsOfMethods();
  Code:
   0: aload_0
   1: invokespecial #1; //Method java/lang/Object."":()V
   4: return

public static void main(java.lang.String[]);
  Code:
   0: return

private void doMethod0();
  Code:
   0: return

private void doMethod1();
  Code:
   0: return

private void doMethod2();
  Code:
   0: return

private void doMethod3();
  Code:
   0: return

private void doMethod4();
  Code:
   0: return

private void doMethod5();
  Code:
   0: return

private void doMethod6();
  Code:
   0: return

private void doMethod7();
  Code:
   0: return

private void doMethod8();
  Code:
   0: return

. . .

. . .

. . .

private void doMethod64992();
  Code:
   0: return

private void doMethod64993();
  Code:
   0: return

private void doMethod64994();
  Code:
   0: return

private void doMethod64995();
  Code:
   0: return

private void doMethod64996();
  Code:
   0: return

private void doMethod64997();
  Code:
   0: return

private void doMethod64998();
  Code:
   0: return

private void doMethod64999();
  Code:
   0: return

private void doMethod65000();
  Code:
   0: return

}



Conclusion

As with the last blog post, this post used Groovy and the Java Compiler API to intentionally reproduce an error that we hope to not see very often.

Reproducing "code too large" Problem in Java

Code conventions and standard software development wisdom dictate that methods should not be too long because they become difficult to fully comprehend, they lose readability when they get too long, they are difficult to appropriately unit test, and they are difficult to reuse. Because most Java developers strive to write highly modular code with small, highly cohesive methods, the "code too large" error in Java is not seen very often. When this error is seen, it is often in generated code.

In this blog post, I intentionally force this "code too large" error to occur. Why in the world would one intentionally do this? In this case, it is because I always understand things better when I tinker with them rather than just reading about them and because doing so gives me a chance to demonstrateGroovy, the Java Compiler API (Java SE 6), and javap.

It turns out that the magic number for the "code too large" error is 65535 bytes (compiled byte code, not source code). Hand-writing a method large enough to lead to this size of a .class file would be tedious (and not worth the effort in my opinion). However, it is typically generated code that leads to this in the wild and so generation of code seems like the best approach to reproducing the problem. When I think of generic Java code generation, I think Groovy.

The Groovy script that soon follows generates a Java class that isn't very exciting. However, the class will have its main function be of an approximate size based on how many conditions I tell the script to create. This allows me to quickly try generating Java classes with different main() method sizes to ascertain when the main() becomes too large.

After the script generates the Java class, it also uses the Java Compiler API to automatically compilethe newly generated Java class for me. The resultant .class file is placed in the same directory as the source .java file. The script, creatively named generateJavaClass.groovy, is shown next.

generateJavaClass.groovy
  1. #!/usr/bin/env groovy  
  2.   
  3. import javax.tools.ToolProvider  
  4.   
  5. println "You're running the script ${System.getProperty('script.name')}"  
  6. if (args.length < 2)  
  7. {  
  8.    println "Usage: javaClassGeneration packageName className baseDir #loops"  
  9.    System.exit(-1)  
  10. }  
  11.   
  12. // No use of "def" makes the variable available to entire script including the  
  13. // defined methods ("global" variables)  
  14.   
  15. packageName = args[0]  
  16. packagePieces = packageName.tokenize(".")  // Get directory names  
  17. def fileName = args[1].endsWith(".java") ? args[1] : args[1] + ".java"  
  18. def baseDirectory = args.length > 2 ? args[2] : System.getProperty("user.dir")  
  19. numberOfConditionals = args.length > 3 ? Integer.valueOf(args[3]) : 10  
  20.   
  21. NEW_LINE = System.getProperty("line.separator")  
  22.   
  23. // The setting up of the indentations shows off Groovy's easy feature for  
  24. // multiplying Strings and Groovy's tie of an overloaded * operator for Strings  
  25. // to the 'multiply' method.  In other words, the "multiply" and "*" used here  
  26. // are really the same thing.  
  27. SINGLE_INDENT = '   '  
  28. DOUBLE_INDENT = SINGLE_INDENT.multiply(2)  
  29. TRIPLE_INDENT = SINGLE_INDENT * 3  
  30.   
  31. def outputDirectoryName = createDirectories(baseDirectory)  
  32. def generatedJavaFile = generateJavaClass(outputDirectoryName, fileName)  
  33. compileJavaClass(generatedJavaFile)  
  34.   
  35.   
  36. /** 
  37.  * Generate the Java class and write its source code to the output directory 
  38.  * provided and with the file name provided.  The generated class's name is 
  39.  * derived from the provided file name. 
  40.  * 
  41.  * @param outDirName Name of directory to which to write Java source. 
  42.  * @param fileName Name of file to be written to output directory (should include 
  43.  *    the .java extension). 
  44.  * @return Fully qualified file name of source file. 
  45.  */  
  46. def String generateJavaClass(outDirName, fileName)  
  47. {  
  48.    def className = fileName.substring(0,fileName.size()-5)  
  49.    outputFileName = outDirName.toString() + File.separator + fileName  
  50.    outputFile = new File(outputFileName)  
  51.    outputFile.write "package ${packageName};${NEW_LINE.multiply(2)}"    
  52.    outputFile << "public class ${className}${NEW_LINE}"    
  53.    outputFile << "{${NEW_LINE}"  
  54.    outputFile << "${SINGLE_INDENT}public static void main(final String[] arguments)"  
  55.    outputFile << "${NEW_LINE}${SINGLE_INDENT}{${NEW_LINE}"  
  56.    outputFile << DOUBLE_INDENT << 'final String someString = "Dustin";' << NEW_LINE  
  57.    outputFile << buildMainBody()  
  58.    outputFile << "${SINGLE_INDENT}}${NEW_LINE}"  
  59.    outputFile << "}"  
  60.    return outputFileName  
  61. }  
  62.   
  63.   
  64. /** 
  65.  * Compile the provided Java source code file name. 
  66.  * 
  67.  * @param fileName Name of Java file to be compiled. 
  68.  */  
  69. def void compileJavaClass(fileName)  
  70. {  
  71.    // Use the Java SE 6 Compiler API (JSR 199)  
  72.    // http://java.sun.com/mailers/techtips/corejava/2007/tt0307.html#1  
  73.    compiler = ToolProvider.getSystemJavaCompiler()  
  74.      
  75.    // The use of nulls in the call to JavaCompiler.run indicate use of defaults  
  76.    // of System.in, System.out, and System.err.   
  77.    int compilationResult = compiler.run(nullnullnull, fileName)  
  78.    if (compilationResult == 0)  
  79.    {  
  80.       println "${fileName} compiled successfully"  
  81.  }  
  82.    else  
  83.    {  
  84.       println "${fileName} compilation failed"  
  85.    }  
  86. }  
  87.   
  88.   
  89. /** 
  90.  * Create directories to which generated files will be written. 
  91.  * 
  92.  * @param baseDir The base directory used in which subdirectories for Java 
  93.  *    source packages will be generated. 
  94.  */  
  95. def String createDirectories(baseDir)  
  96. {  
  97.    def outDirName = new StringBuilder(baseDir)  
  98.    for (pkgDir in packagePieces)  
  99.    {  
  100.       outDirName << File.separator << pkgDir  
  101.    }  
  102.    outputDirectory = new File(outDirName.toString())  
  103.    if (outputDirectory.exists() && outputDirectory.isDirectory())  
  104.    {  
  105.       println "Directory ${outDirName} already exists."  
  106.    }  
  107.    else  
  108.    {  
  109.       isDirectoryCreated = outputDirectory.mkdirs()  // Use mkdirs in case multiple  
  110.       println "Directory ${outputDirectoryName} ${isDirectoryCreated ? 'is' : 'not'} created."  
  111.    }  
  112.    return outDirName.toString()  
  113. }  
  114.   
  115.   
  116. /** 
  117.  * Generate the body of generated Java class source code's main function. 
  118.  */  
  119. def String buildMainBody()  
  120. {  
  121.    def str = new StringBuilder() << NEW_LINE  
  122.    str << DOUBLE_INDENT << "if (someString == null || someString.isEmpty())" << NEW_LINE  
  123.    str << DOUBLE_INDENT << "{" << NEW_LINE  
  124.    str << TRIPLE_INDENT << 'System.out.println("The String is null or empty.");'  
  125.    str << NEW_LINE << DOUBLE_INDENT << "}" << NEW_LINE  
  126.    for (i in 0..numberOfConditionals)  
  127.    {  
  128.       str << DOUBLE_INDENT << 'else if (someString.equals("a' << i << '"))' << NEW_LINE  
  129.       str << DOUBLE_INDENT << "{" << NEW_LINE  
  130.       str << TRIPLE_INDENT << 'System.out.println("You found me!");' << NEW_LINE  
  131.       str << DOUBLE_INDENT << "}" << NEW_LINE  
  132.    }  
  133.    str << DOUBLE_INDENT << "else" << NEW_LINE  
  134.    str << DOUBLE_INDENT << "{" << NEW_LINE  
  135.    str << TRIPLE_INDENT << 'System.out.println("No matching string found.");'  
  136.    str << DOUBLE_INDENT << NEW_LINE << DOUBLE_INDENT << "}" << NEW_LINE  
  137.    return str  
  138. }  


Because this script is intended primarily for generating Java code to learn more about the "code too large" error and to demonstrate a few things, I did not make it nearly as fancy as it could be. For one thing, I did not use Groovy's built-in Apache CLI support for handling command-line arguments as I have demonstrated in previous blog posts on using Groovy to check seventh grade homework.

Even though the script above does not apply Groovy's full potential, it still manages to demonstrate some Groovy niceties. I tried to add comments in the script describing some of these. These include features such as Groovy GDK's String.tokenize method and other useful Groovy String extensions.

When I run this script from the directoryC:\java\examples\groovyExamples\javaClassGeneration with the arguments "dustin.examples" (package structure), "BigClass" (name of generated Java class), "." (current directory is based directory for generated code, and "5" (number of conditionals to be in generated code), the script's output is shown here and in the following screen snapshot:

You're running the script C:\java\examples\groovyExamples\javaClassGeneration\generateJavaClass.groovy
Directory .\dustin\examples already exists.
.\dustin\examples\BigClass.java compiled successfully




This output tells us that the generated Java class with five conditionals compiled successfully. To get a taste of what this generated Java class looks like, we'll look at this newly generated version with only five conditionals.

BigClass.java (generated with 5 conditionals)
  1. package dustin.examples;  
  2.   
  3. public class BigClass  
  4. {  
  5.    public static void main(final String[] arguments)  
  6.    {  
  7.       final String someString = "Dustin";  
  8.   
  9.       if (someString == null || someString.isEmpty())  
  10.       {  
  11.          System.out.println("The String is null or empty.");  
  12.       }  
  13.       else if (someString.equals("a0"))  
  14.       {  
  15.          System.out.println("You found me!");  
  16.       }  
  17.       else if (someString.equals("a1"))  
  18.       {  
  19.          System.out.println("You found me!");  
  20.       }  
  21.       else if (someString.equals("a2"))  
  22.       {  
  23.          System.out.println("You found me!");  
  24.       }  
  25.       else if (someString.equals("a3"))  
  26.       {  
  27.          System.out.println("You found me!");  
  28.       }  
  29.       else if (someString.equals("a4"))  
  30.       {  
  31.          System.out.println("You found me!");  
  32.       }  
  33.       else if (someString.equals("a5"))  
  34.       {  
  35.          System.out.println("You found me!");  
  36.       }  
  37.       else  
  38.       {  
  39.          System.out.println("No matching string found.");        
  40.       }  
  41.    }  
  42. }  


The above code includes two default conditionals every time regardless of how many conditionals are selected when the class generation script is run. In between the check for null/empty String and theelse clause if no other else if has been satisfied are the number of else if statements specified when the class generation script was run. In this case, 5 was that number and so there are five else if conditionals between the two default conditionals on either end. As this demonstrates, it will be easy to scale up the number of conditionals until the Java compiler just won't take it anymore.

I now try the Groovy script for generating the Java class again, but this time go all out and select 5000 as the number of desired conditionals. As the output shown below and in the following screen snapshot indicate, Groovy has no trouble generating the text file representing the Java class with this many conditionals in its main() function, but the Java compiler doesn't like it one bit.

You're running the script C:\java\examples\groovyExamples\javaClassGeneration\generateJavaClass.groovy
Directory .\dustin\examples already exists.
.\dustin\examples\BigClass.java:5: code too large
   public static void main(final String[] arguments)
                      ^
1 error
.\dustin\examples\BigClass.java compilation failed




Obviously, the attempt to compile the generated class with a 5000+2 conditional main was too much. Through a little iterative trial-and-error, I was able to determine that 2265 conditionals (beyond the two defaults) was the maximum compilable number for my main() function and 2266 would break it. This is demonstrated in the next screen snapshot.



Knowing our limits better, we can now "look" at the byte code using the javap tool provided with Sun's JDK to analyze the corresponding class file. Because there was a compiler error when we tried to compile the code with 2266 additional conditionals, we must run javap against the BigClass.class file generated with 2265 additional conditionals. The output of running javap with the -c option for this large class is too large (~1 MB) to bludgeon readers with here. However, I include key snippets from its output below.

Compiled from "BigClass.java"
public class dustin.examples.BigClass extends java.lang.Object{
public dustin.examples.BigClass();
  Code:
   0: aload_0
   1: invokespecial #1; //Method java/lang/Object."<init>":()V
   4: return

public static void main(java.lang.String[]);
  Code:
   0: ldc #2; //String Dustin
   2: ifnonnull 10
   5: goto_w 23
   10: ldc #2; //String Dustin
   12: invokevirtual #3; //Method java/lang/String.isEmpty:()Z
   15: ifne 23
   18: goto_w 36
   23: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream;
   26: ldc #5; //String The String is null or empty.
   28: invokevirtual #6; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   31: goto_w 65512
   36: ldc #2; //String Dustin
   38: ldc #7; //String a0
   40: invokevirtual #8; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   43: ifne 51
   46: goto_w 64
   51: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream;
   54: ldc #9; //String You found me!
   56: invokevirtual #6; //Method java/io/PrintStream.println:


. . . 

. . .

. . .

   65411: goto_w 65512
   65416: ldc #2; //String Dustin
   65418: ldc_w #2272; //String a2263
   65421: invokevirtual #8; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   65424: ifne 65432
   65427: goto_w 65445
   65432: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream;
   65435: ldc #9; //String You found me!
   65437: invokevirtual #6; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   65440: goto_w 65512
   65445: ldc #2; //String Dustin
   65447: ldc_w #2273; //String a2264
   65450: invokevirtual #8; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   65453: ifne 65461
   65456: goto_w 65474
   65461: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream;
   65464: ldc #9; //String You found me!
   65466: invokevirtual #6; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   65469: goto_w 65512
   65474: ldc #2; //String Dustin
   65476: ldc_w #2274; //String a2265
   65479: invokevirtual #8; //Method java/lang/String.equals:(Ljava/lang/Object;)Z
   65482: ifne 65490
   65485: goto_w 65503
   65490: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream;
   65493: ldc #9; //String You found me!
   65495: invokevirtual #6; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   65498: goto_w 65512
   65503: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream;
   65506: ldc_w #2275; //String No matching string found.
   65509: invokevirtual #6; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   65512: return
}


From the snippets of javap output shown above, we see that the highest Code offset (65512) for this function pushing the limits of the method size was getting awfully close to the magic 65535 bytes (216-1 or Short.MAX_VALUE - Short.MIN_VALUE).


Conclusion

Most Java developers don't see the "code too large" problem very often because they write methods and classes that are reasonable in size (or at least more reasonable than the limits allow). However, generated code can much more easily exceed these "limitations." So of what value is intentionally reproducing this error? Well, the next time someone tries to convince you that bigger is better, you can refer that person to this post.


Other Resources

⇒ Java Language Specification, Third Edition

⇒ Class File Format

⇒ Code Too Large for Try Statement?

⇒ Code Too Long

⇒ Is There Any Number of Lines Limit in a Java Class?

Firefox 3.6 Requires Java SE 6 Update 10

Java SE 6 Update 10 was a significant release despite its version numbering indicating a minor release. Among other advancements, Java SE 6 Update 10 introduced the Next-Generation Java Plug-in for a better Java applet experience. It has now been announced that Firefox 3.6 (Namoroka) will require this next-generation plug-in. Users can access the web page athttp://java.com/en/download/help/testvm.xml to test the version of Java supported on a particular machine. The Firefox 3.6 for Developers page specifies advancements in support for Cascading Style SheetsHTMLJavaScriptDOM, and XPath.

Java SourceVersion and Character

The SourceVersion class provides information on Java source versions and can provide some interesting details, including demonstration of terminology covered in the Java Language Specification. In this blog posting, I look briefly at some of the more interesting observations one can make using this class that was introduced with Java SE 6 in conjunction with the Java long-timer class Character.

The SourceVersion class provides several handy static methods for ascertaining details about the source version of the current Java runtime. The methods SourceVersion.latest() andSourceVersion.latestSupported() provide details regarding the latest source versions that can be "modeled" and "fully supported" respectively.

The following code snippet demonstrates these two methods in action.

  1. out.println("Latest source version that can be modeled: ");  
  2. out.println("\tSourceVersion.latest(): " + SourceVersion.latest());  
  3. out.println("Latest source version fully supported by current execution environment ");  
  4. out.println("\tSourceVersion.latestSupported(): " + SourceVersion.latestSupported());  


The output from running this code is shown next.



As the code example and corresponding output above indicate, the currently supported modeled version and currently fully supported versions are easily accessible. Although the SourceVersionclass was introduced with Java SE 6, it has been built to be support future versions of Java. Not only does the Javadoc documentation state that "additional source version constants will be added to model future releases of the language," but the SourceVersion.values() method also provides all supported version enums. A code example and associated output are shown next to demonstrate this method in action.

  1. out.println("SourceVersion enum Values:");  
  2. final SourceVersion[] versions = SourceVersion.values();  
  3. for (final SourceVersion version : versions)  
  4. {  
  5.    out.println("\t" + version);  
  6. }  




The Javadoc documentation tells us the meanings of the various enum values shown in the above output. Each represents a different "source version of the Java programming language" and the platform version it is associated with. As shown earlier, the RELEASE_6 is associated with Java SE 6, RELEASE_5 is associated with J2SE 5, RELEASE_4 is associated with JDK 1.4, RELEASE_3 is associated with JDK 1.3, RELEASE_2 is associated with JDK 1.2, RELEASE_1 is associated with JDK 1.1 and RELEASE_0 is associated with "the original version." The Javadoc documentation for Java SE 7 indicates that SourceVersion.RELEASE_7 is supported in Java SE 7.

The SourceVersion class provides three static methods that each indicate whether a providedCharSequence is an identifierkeyword, or name. The three methods that allow one to dynamically determine if a particular CharSequence fits one or more of the types identifier, name, or keyword are (respectively) SourceVersion.isIdentifier()SourceVersion.isName(), and SourceVersion.isKeyword().

Using these methods allows one to determine if a particular string is reserved as a keyword, is even considered a valid identifier, and if a string that is a valid identifier is not a keyword and is thus a valid name. The isName() method returns true for a "syntactically valid name" that is not also a keyword orliteral. The isKeyword() method indicates if the provided string is one of the keywords listed here.

I have run many different strings of various combinations of these three types in the following code.

  1. public static void printIdentifierTest(final String stringToBeTested)  
  2. {  
  3.    out.println(  
  4.         "Is '" + stringToBeTested + "' an identifier? "  
  5.       + SourceVersion.isIdentifier(stringToBeTested));  
  6. }  
  7.   
  8. public static void printKeywordTest(final String stringToBeTested)  
  9. {  
  10.    out.println(  
  11.         "Is '" + stringToBeTested + "' a keyword? "  
  12.       + SourceVersion.isKeyword(stringToBeTested));  
  13. }  
  14.   
  15. public static void printNameTest(final String stringToBeTested)  
  16. {  
  17.    out.println(  
  18.         "Can '" + stringToBeTested + "' be used as a name? "  
  19.       + SourceVersion.isName(stringToBeTested));  
  20. }  
  21.   
  22. public static void printTests(final String stringToBeTested)  
  23. {  
  24.    out.println("\n===============  " + stringToBeTested + "  ===============");  
  25.    printIdentifierTest(stringToBeTested);  
  26.    printKeywordTest(stringToBeTested);  
  27.    printNameTest(stringToBeTested);  
  28. }  
  29.   
  30. public static void printTests(  
  31.    final String stringToBeTested,  
  32.    final String alternateHeaderString)  
  33. {  
  34.    out.println("\n===============  " + alternateHeaderString + "  ===============");  
  35.    printIdentifierTest(stringToBeTested);  
  36.    printKeywordTest(stringToBeTested);  
  37.    printNameTest(stringToBeTested);  
  38. }  
  39.   
  40. /** 
  41.  * Main function for demonstrating SourceVersion enum. 
  42.  * 
  43.  * @param arguments Command-line arguments: none expected. 
  44.  */  
  45. public static void main(final String[] arguments)  
  46. {  
  47.    final String dustinStr = "Dustin";  
  48.    printTests(dustinStr);  
  49.    final String dustinLowerStr = "dustin";  
  50.    printTests(dustinLowerStr);  
  51.    final String instanceOfStr = "instanceof";  
  52.    printTests(instanceOfStr);  
  53.    final String constStr = "const";  
  54.    printTests(constStr);  
  55.    final String gotoStr = "goto";  
  56.    printTests(gotoStr);  
  57.    final String trueStr = "true";  
  58.    printTests(trueStr);  
  59.    final String nullStr = "null";  
  60.    printTests(nullStr);  
  61.    final String weirdStr = "/#";  
  62.    printTests(weirdStr);  
  63.    final String tabStr = "\t";  
  64.    printTests(tabStr, "TAB (\\t)");  
  65.    final String classStr = "class";  
  66.    printTests(classStr);  
  67.    final String enumStr = "enum";  
  68.    printTests(enumStr);  
  69.    final String assertStr = "assert";  
  70.    printTests(assertStr);  
  71.    final String intStr = "int";  
  72.    printTests(intStr);  
  73.    final String numeralStartStr = "1abc";  
  74.    printTests(numeralStartStr);  
  75.    final String numeralEmbeddedStr = "abc1";  
  76.    printTests(numeralEmbeddedStr);  
  77.    final String dollarStartStr = "$dustin";  
  78.    printTests(dollarStartStr);  
  79.    final String underscoreStartStr = "_dustin";  
  80.    printTests(underscoreStartStr);  
  81.    final String spacesStartStr = " dustin";  
  82.    printTests(spacesStartStr, " dustin (space in front)");  
  83.    final String spacesInStr = "to be";  
  84.    printTests(spacesInStr);  
  85. }  


When the above code is executed the output shown next is generated.

  1. ===============  Dustin  ===============  
  2. Is 'Dustin' an identifier? true  
  3. Is 'Dustin' a keyword? false  
  4. Can 'Dustin' be used as a name? true  
  5.   
  6. ===============  dustin  ===============  
  7. Is 'dustin' an identifier? true  
  8. Is 'dustin' a keyword? false  
  9. Can 'dustin' be used as a name? true  
  10.   
  11. ===============  instanceof  ===============  
  12. Is 'instanceof' an identifier? true  
  13. Is 'instanceof' a keyword? true  
  14. Can 'instanceof' be used as a name? false  
  15.   
  16. ===============  const  ===============  
  17. Is 'const' an identifier? true  
  18. Is 'const' a keyword? true  
  19. Can 'const' be used as a name? false  
  20.   
  21. ===============  goto  ===============  
  22. Is 'goto' an identifier? true  
  23. Is 'goto' a keyword? true  
  24. Can 'goto' be used as a name? false  
  25.   
  26. ===============  true  ===============  
  27. Is 'true' an identifier? true  
  28. Is 'true' a keyword? true  
  29. Can 'true' be used as a name? false  
  30.   
  31. ===============  null  ===============  
  32. Is 'null' an identifier? true  
  33. Is 'null' a keyword? true  
  34. Can 'null' be used as a name? false  
  35.   
  36. ===============  /#  ===============  
  37. Is '/#' an identifier? false  
  38. Is '/#' a keyword? false  
  39. Can '/#' be used as a name? false  
  40.   
  41. ===============  TAB (\t)  ===============  
  42. Is ' ' an identifier? false  
  43. Is ' ' a keyword? false  
  44. Can ' ' be used as a name? false  
  45.   
  46. ===============  class  ===============  
  47. Is 'class' an identifier? true  
  48. Is 'class' a keyword? true  
  49. Can 'class' be used as a name? false  
  50.   
  51. ===============  enum  ===============  
  52. Is 'enum' an identifier? true  
  53. Is 'enum' a keyword? true  
  54. Can 'enum' be used as a name? false  
  55.   
  56. ===============  assert  ===============  
  57. Is 'assert' an identifier? true  
  58. Is 'assert' a keyword? true  
  59. Can 'assert' be used as a name? false  
  60.   
  61. ===============  int  ===============  
  62. Is 'int' an identifier? true  
  63. Is 'int' a keyword? true  
  64. Can 'int' be used as a name? false  
  65.   
  66. ===============  1abc  ===============  
  67. Is '1abc' an identifier? false  
  68. Is '1abc' a keyword? false  
  69. Can '1abc' be used as a name? false  
  70.   
  71. ===============  abc1  ===============  
  72. Is 'abc1' an identifier? true  
  73. Is 'abc1' a keyword? false  
  74. Can 'abc1' be used as a name? true  
  75.   
  76. ===============  $dustin  ===============  
  77. Is '$dustin' an identifier? true  
  78. Is '$dustin' a keyword? false  
  79. Can '$dustin' be used as a name? true  
  80.   
  81. ===============  _dustin  ===============  
  82. Is '_dustin' an identifier? true  
  83. Is '_dustin' a keyword? false  
  84. Can '_dustin' be used as a name? true  
  85.   
  86. ===============   dustin (space in front)  ===============  
  87. Is ' dustin' an identifier? false  
  88. Is ' dustin' a keyword? false  
  89. Can ' dustin' be used as a name? false  
  90.   
  91. ===============  to be  ===============  
  92. Is 'to be' an identifier? false  
  93. Is 'to be' a keyword? false  
  94. Can 'to be' be used as a name? false  


The above output demonstrates that a valid name must be a valid identifier without being a keyword. A keyword must be a valid identifier, but not all identifiers are keywords. Some string values that are not keywords or reserved words are not even identifiers because they don't meet the rules of Java identifiers.

The examples above indicate that we cannot use a name for a variable or other construct that begins with a numeral, but we can use $ and _ for the first character in a name. Another way to determine this is through use of the static method Character.isJavaIdentifierStart(char). The following code snippet demonstrates this along with the similar method Character.isJavaIdentifierPart(char), which returnstrue if the provided character can be in the name anywhere other than the first character.

  1. public static void printTestForValidIdentifierCharacter(  
  2.    final char characterToBeTested)  
  3. {  
  4.    out.println(  
  5.         "Character '" + characterToBeTested  
  6.       + (  Character.isJavaIdentifierStart(characterToBeTested)  
  7.          ? "': VALID "  
  8.          : "': NOT VALID ")  
  9.       + "FIRST character and "  
  10.       + (  Character.isJavaIdentifierPart(characterToBeTested)  
  11.          ? "VALID "  
  12.          : "NOT VALID ")  
  13.       + "OTHER character in a Java name.");  
  14.    out.println(  "\tType of '" + characterToBeTested + "': "  
  15.                + Character.getType(characterToBeTested));  
  16. }  
  17.   
  18. public static void demonstrateCharacterJavaIdentifierStart()  
  19. {  
  20.    out.println("\nTEST FOR FIRST AND OTHER CHARACTERS IN A VALID JAVA NAME");  
  21.    printTestForValidIdentifierCharacter('A');  
  22.    printTestForValidIdentifierCharacter('a');  
  23.    printTestForValidIdentifierCharacter('1');  
  24.    printTestForValidIdentifierCharacter('\\');  
  25.    printTestForValidIdentifierCharacter('_');  
  26.    printTestForValidIdentifierCharacter('$');  
  27.    printTestForValidIdentifierCharacter('#');  
  28.    printTestForValidIdentifierCharacter('\n');  
  29.    printTestForValidIdentifierCharacter('\t');  
  30. }  


The output from the above appears below.

  1. TEST FOR FIRST AND OTHER CHARACTERS IN A VALID JAVA NAME  
  2. Character 'A': VALID FIRST character and VALID OTHER character in a Java name.  
  3.  Type of 'A'1  
  4. Character 'a': VALID FIRST character and VALID OTHER character in a Java name.  
  5.  Type of 'a'2  
  6. Character '1': NOT VALID FIRST character and VALID OTHER character in a Java name.  
  7.  Type of '1'9  
  8. Character '\': NOT VALID FIRST character and NOT VALID OTHER character in a Java name.  
  9.  Type of '\'24  
  10. Character '_': VALID FIRST character and VALID OTHER character in a Java name.  
  11.  Type of '_'23  
  12. Character '$': VALID FIRST character and VALID OTHER character in a Java name.  
  13.  Type of '$'26  
  14. Character '#': NOT VALID FIRST character and NOT VALID OTHER character in a Java name.  
  15.  Type of '#'24  
  16. Character '  
  17. ': NOT VALID FIRST character and NOT VALID OTHER character in a Java name.  
  18.  Type of '  
  19. ': 15  
  20. Character ' ': NOT VALID FIRST character and NOT VALID OTHER character in a Java name.  
  21.  Type of ' '15  


Because the Character.getType(char) method has been with us for quite a while and predates the J2SE 5-introduced enum construct, this method returns primitives integers. One can refer to Java'sConstant Field Values to determine what each of these constants stand for.

To make the above example's output a little more readable, I have added a simple "converter" method that converts the returned int to a more readable String. I have only added switch cases for the integers returned from my example, but one could add cases for all supported types represented by different integers.

  1. public static String extractReadableStringFromJavaCharacterTypeInt(  
  2.    final int characterTypeInt)  
  3. {  
  4.    String characterType;  
  5.    switch (characterTypeInt)  
  6.    {  
  7.       case Character.CONNECTOR_PUNCTUATION :  
  8.          characterType = "Connector Punctuation";  
  9.          break;  
  10.       case Character.CONTROL :  
  11.          characterType = "Control";  
  12.          break;  
  13.       case Character.CURRENCY_SYMBOL :  
  14.          characterType = "Currency Symbol";  
  15.          break;  
  16.       case Character.DECIMAL_DIGIT_NUMBER :  
  17.          characterType = "Decimal Digit Number";  
  18.          break;  
  19.       case Character.LETTER_NUMBER :  
  20.          characterType = "Letter/Number";  
  21.          break;  
  22.       case Character.LOWERCASE_LETTER :  
  23.          characterType = "Lowercase Letter";  
  24.          break;  
  25.       case Character.OTHER_PUNCTUATION :  
  26.          characterType = "Other Punctuation";  
  27.          break;  
  28.       case Character.UPPERCASE_LETTER :  
  29.          characterType = "Uppercase Letter";  
  30.          break;  
  31.       default : characterType = "Unknown Character Type Integer: " + characterTypeInt;   
  32.    }  
  33.    return characterType;  
  34. }  


When the integers returned from Character.getType(char) in the example two listings ago are run through this switch statement, the revised output appears as shown next.

  1. TEST FOR FIRST AND OTHER CHARACTERS IN A VALID JAVA NAME  
  2. Character 'A': VALID FIRST character and VALID OTHER character in a Java name.  
  3.  Type of 'A': Uppercase Letter  
  4. Character 'a': VALID FIRST character and VALID OTHER character in a Java name.  
  5.  Type of 'a': Lowercase Letter  
  6. Character '1': NOT VALID FIRST character and VALID OTHER character in a Java name.  
  7.  Type of '1': Decimal Digit Number  
  8. Character '\': NOT VALID FIRST character and NOT VALID OTHER character in a Java name.  
  9.  Type of '\': Other Punctuation  
  10. Character '_': VALID FIRST character and VALID OTHER character in a Java name.  
  11.  Type of '_': Connector Punctuation  
  12. Character '$': VALID FIRST character and VALID OTHER character in a Java name.  
  13.  Type of '$': Currency Symbol  
  14. Character '#': NOT VALID FIRST character and NOT VALID OTHER character in a Java name.  
  15.  Type of '#': Other Punctuation  
  16. Character '  
  17. ': NOT VALID FIRST character and NOT VALID OTHER character in a Java name.  
  18.  Type of '  
  19. ': Control  
  20. Character ' ': NOT VALID FIRST character and NOT VALID OTHER character in a Java name.  
  21.  Type of ' ': Control  


The SourceVersion class is useful for dynamically determining information about the Java source code version and the keywords and valid names applicable for that version. The Character class also provides useful information on what a particular character's type is and whether or not that character can be used as the first character of a name or as any other character in a valid name.


No comments:

Post a Comment