Wednesday, August 22, 2012

NetBeans 7.2: Generating (Introduce) Local Extensions

I have already blogged on two refactoring options NetBeans 7.2 provides in my aptly named posts NetBeans 7.2: Refactoring Parameterized Constructor As Builder and NetBeans 7.2: Refactoring Constructor As Static Factory. In this post, I look at another refactoring option introduced with NetBeans 7.2 that may be the most time-saving of them all: Introduce Local Extension.

Martin Fowler's Refactoring Home Page includes a "catalog of common refactorings", including the Introduce Local Extension refactoring. This particular refactoring is described on that page in this way (presenting the issue and the solution), "[Issue] A server class you are using needs several additional methods, but you can't modify the class. [Solution] Create a new class that contains these extra methods. Make this extension class a subclass or a wrapper of the original."

One of the new refactorings available with NetBeans 7.2 is "Introduce Local Extension" and it can be easily accessed by right-clicking on the class that one wants to provide a local extension for, selecting the "Refactor" option to expand that drop-down menu, and selecting "Introduce Local Extension." Alternatively, one can simply highlight the class of interest and use the keyboard: Alt+Shift+X. The former (using the drop-down menus in conjunction with right and then left mouse clicks) is depicted in the following screen snapshot after hovering over usage of the Java Date class in the source code (which is listed after the screen snapshot).

The above screen snapshot shows the result of right-clicking on the Date type used for instance variable date in the following source code listing.

Main.java (Bare Bones Class for Demonstration Purposes)
package dustin.examples;

import java.util.Date;

/**
 *
 * @author Dustin
 */
public class Main
{
   private Date date;

   /**
    * @param arguments the command line arguments
    */
   public static void main(final String[] arguments)
   {
   }
}

When the "Introduce Local Extension" refactoring is selected either by mouse clicking as shown above or through use of Alt+Shift+X, a wizard screen like the following is presented.

In this case, because I used Alt+Shift+X while hovering over the Date data type, the wizard is ready to help me generate a local extension of Java's Date. Note that if I had been hovering over most other parts of this class, the wizard would instead start helping me generate a local extension of my Main class (the class loaded in the NetBeans editor window).

In compliance with earlier cited definition of implementations of the "Introduce Local Extension" refactoring, there are two ways the NetBeans 7.2 wizard allows for this: wrapper (composition) and subtype (implementation inheritance). If the "Wrapper" option is selected (as is the case here), then there are three more "Equality" options to choose from ("Delegate", "Generate", or "Separate"). I'll return to these later. For now, let's assume that "Subtype" is selected. The next screen snapshot shows how the other three options are no longer applicable and are grayed-out.

If I click the "Refactor" button at this point, my new class will also be named Date but will be dustin.examples.Date rather than java.util.Date. In this case, to avoid any confusion, I'm going to change the generated class's name to DustinDate.

It's also worth noting that there is a checkbox that allows me to indicate whether I want to replace the original class (Date in this case) with the refactored local extension. In other words, I can have the refactoring not only create the subtype local extension, but I can have NetBeans replace my use of Date in the Main source code with my new class. This is shown in the next screen snapshot.

The next screen snapshot shows the results of clicking the "Refactor" button and having DustinDate created as a subtype of java.util.Date. Note that the use of Date in Main has been automatically updated to use the newly generated DustinDate, leaving an unused import of java.util.Date behind.

The next code listing is the source code of DustinDate, which was generated solely by NetBeans as a Subtype "Introduce to Local Extension" refactoring of java.util.Date.

DustinDate.java (NetBeans-generated via Subtype "Introduce Local Extension")
package dustin.examples;

import java.util.Date;

/**
 *
 * @author Dustin
 */
public class DustinDate extends Date
{
   /**
    * Allocates a <code>Date</code> object and initializes it so that
    * it represents the time at which it was allocated, measured to the
    * nearest millisecond.
    *
    * @see     java.lang.System#currentTimeMillis()
    */
   public DustinDate()
   {
      super();
   }

   /**
    * Allocates a <code>Date</code> object and initializes it to
    * represent the specified number of milliseconds since the
    * standard base time known as "the epoch", namely January 1,
    * 1970, 00:00:00 GMT.
    *
    * @param   date   the milliseconds since January 1, 1970, 00:00:00 GMT.
    * @see     java.lang.System#currentTimeMillis()
    */
   public DustinDate(long date)
   {
      super(date);
   }

   /**
    * Allocates a <code>Date</code> object and initializes it so that
    * it represents midnight, local time, at the beginning of the day
    * specified by the <code>year</code>, <code>month</code>, and
    * <code>date</code> arguments.
    *
    * @param   year    the year minus 1900.
    * @param   month   the month between 0-11.
    * @param   date    the day of the month between 1-31.
    * @see     java.util.Calendar
    * @deprecated As of JDK version 1.1,
    * replaced by <code>Calendar.set(year + 1900, month, date)</code>
    * or <code>GregorianCalendar(year + 1900, month, date)</code>.
    */
   public DustinDate(int year, int month, int date)
   {
      super(year, month, date);
   }

   /**
    * Allocates a <code>Date</code> object and initializes it so that
    * it represents the instant at the start of the minute specified by
    * the <code>year</code>, <code>month</code>, <code>date</code>,
    * <code>hrs</code>, and <code>min</code> arguments, in the local
    * time zone.
    *
    * @param   year    the year minus 1900.
    * @param   month   the month between 0-11.
    * @param   date    the day of the month between 1-31.
    * @param   hrs     the hours between 0-23.
    * @param   min     the minutes between 0-59.
    * @see     java.util.Calendar
    * @deprecated As of JDK version 1.1,
    * replaced by <code>Calendar.set(year + 1900, month, date,
    * hrs, min)</code> or <code>GregorianCalendar(year + 1900,
    * month, date, hrs, min)</code>.
    */
   public DustinDate(int year, int month, int date, int hrs, int min)
   {
      super(year, month, date, hrs, min);
   }

   /**
    * Allocates a <code>Date</code> object and initializes it so that
    * it represents the instant at the start of the second specified
    * by the <code>year</code>, <code>month</code>, <code>date</code>,
    * <code>hrs</code>, <code>min</code>, and <code>sec</code> arguments,
    * in the local time zone.
    *
    * @param   year    the year minus 1900.
    * @param   month   the month between 0-11.
    * @param   date    the day of the month between 1-31.
    * @param   hrs     the hours between 0-23.
    * @param   min     the minutes between 0-59.
    * @param   sec     the seconds between 0-59.
    * @see     java.util.Calendar
    * @deprecated As of JDK version 1.1,
    * replaced by <code>Calendar.set(year + 1900, month, date,
    * hrs, min, sec)</code> or <code>GregorianCalendar(year + 1900,
    * month, date, hrs, min, sec)</code>.
    */
   public DustinDate(int year, int month, int date, int hrs, int min, int sec)
   {
      super(year, month, date, hrs, min, sec);
   }

   /**
    * Allocates a <code>Date</code> object and initializes it so that
    * it represents the date and time indicated by the string
    * <code>s</code>, which is interpreted as if by the
    * {@link Date#parse} method.
    *
    * @param   s   a string representation of the date.
    * @see     java.text.DateFormat
    * @see     java.util.Date#parse(java.lang.String)
    * @deprecated As of JDK version 1.1,
    * replaced by <code>DateFormat.parse(String s)</code>.
    */
   public DustinDate(String s)
   {
      super(s);
   }
   
}

Because the "subtype" approach was employed, only constructors had to be generated by NetBeans to use the class being extended. The public or protected "get", "set", and any other methods of Date are automatically available to the extending DustinDate class. There are sometimes limitations and disadvantages of using implementation inheritance (it has even been called evil). One such disadvantage is the inability to extend final classes. To illustrate, I'm move to introducing a local extension of java.lang.String instead of Date.

Main.java (String Instance Variable Added)
package dustin.examples;

/**
 *
 * @author Dustin
 */
public class Main
{
   private DustinDate date;
   private String string;

   /**
    * @param arguments the command line arguments
    */
   public static void main(final String[] arguments)
   {
   }
}

Highlighting the String datatype and selecting the "Introduce Local Extension" refactoring leads to a wizard screen like that which follows.

I was pleasantly surprised to see that NetBeans 7.2 does not allow for the "Subtype" option in this case (String is final and cannot be extended). I must use "Wrapper," but can select one of the three "Equality" options for "Wrapper." I'm not going any further with String in this example, but the wizard will generate local extensions as wrappers using any of the three "Equality" options. In all cases, certain methods in the generated class need to be removed or altered for the new code to compile. A screen snapshot of using this with String using "Wrapper" and "Delegate" is shown next.

To more easily demonstrate NetBeans 7.2 refactoring local extension of wrapper type with the different options set, I had NetBeans generate most of the source code for a simple, all-new class called Person that is shown in the next code listing.

package dustin.examples;

import java.util.Objects;

/**
 *
 * @author Dustin
 */
public final class Person
{
   private String lastName;
   private String firstName;

   public Person(final String newLastName, final String newFirstName)
   {
      this.lastName = newLastName;
      this.firstName = newFirstName;
   }

   public String getLastName()
   {
      return this.lastName;
   }

   public void setLastName(String newLastName)
   {
      this.lastName = newLastName;
   }

   public String getFirstName()
   {
      return this.firstName;
   }

   public void setFirstName(String newFirstName)
   {
      this.firstName = newFirstName;
   }

   @Override
   public int hashCode()
   {
      int hash = 3;
      hash = 83 * hash + Objects.hashCode(this.lastName);
      hash = 83 * hash + Objects.hashCode(this.firstName);
      return hash;
   }

   @Override
   public boolean equals(Object obj)
   {
      if (obj == null)
      {
         return false;
      }
      if (getClass() != obj.getClass())
      {
         return false;
      }
      final Person other = (Person) obj;
      if (!Objects.equals(this.lastName, other.lastName))
      {
         return false;
      }
      if (!Objects.equals(this.firstName, other.firstName))
      {
         return false;
      }
      return true;
   }

   @Override
   public String toString()
   {
      return "Person{" + "lastName=" + lastName + ", firstName=" + firstName + '}';
   }

}

When using "Wrapper" type of local extension, there are three possible "Equality" selections. The next three screen snapshots show use of each with a class named appropriately for each. Note that I unchecked the option to change my use of Person to this new class because I wanted to use that original Person class to demonstrate refactoring for "Generate" and "Separate" equality settings in addition to "Delegate."

The three "Wrapper" generated classes (PersonDelegate, PersonGenerate, and PersonSeparate are identical except how they override Object.equals(Object) and Object.hashCode(). Given this, I first show the code listing for PersonDelegate and then show only the different equals implementations for the three approaches.

PersonDelegate.java (NetBeans-generated)
package dustin.examples;

/**
 *
 * @author Dustin
 */
public final class PersonDelegate
{
   private Person delegate;

   public PersonDelegate(Person delegate)
   {
      this.delegate = delegate;
   }

   public PersonDelegate(final String newLastName, final String newFirstName)
   {
      this.delegate = new Person(newLastName, newFirstName);
   }

   public String getLastName()
   {
      return delegate.getLastName();
   }

   public void setLastName(String newLastName)
   {
      delegate.setLastName(newLastName);
   }

   public String getFirstName() {
      return delegate.getFirstName();
   }

   public void setFirstName(String newFirstName)
   {
      delegate.setFirstName(newFirstName);
   }

   public String toString()
   {
      return delegate.toString();
   }

   public boolean equals(Object o)
   {
      Object target = o;
      if (o instanceof PersonDelegate)
      {
         target = ((PersonDelegate) o).delegate;
      }
      return this.delegate.equals(target);
   }

   public int hashCode()
   {
      return this.delegate.hashCode();
   }
}

As stated before the last code listing, only the implementation of "equals" and "hashCode" change depending on the equality setting chosen. To make the differences more obvious, only the equals and hashCode implementations of the three generated classes are shown next.

PersonDelegate.java equals Method - Delegate Equality
   public boolean equals(Object o)
   {
      Object target = o;
      if (o instanceof PersonDelegate)
      {
         target = ((PersonDelegate) o).delegate;
      }
      return this.delegate.equals(target);
   }

   public int hashCode()
   {
      return this.delegate.hashCode();
   }
PersonGenerate.java equals Method - Generate Equality
   @Override
   public boolean equals(Object obj)
   {
      if (obj == null)
      {
         return false;
      }
      if (getClass() != obj.getClass())
      {
         return false;
      }
      final PersonGenerate other = (PersonGenerate) obj;
      if (!Objects.equals(this.delegate, other.delegate))
      {
         return false;
      }
      return true;
   }

   @Override
   public int hashCode()
   {
      int hash = 5;
      hash = 43 * hash + Objects.hashCode(this.delegate);
      return hash;
   }
PersonSeparate.java equals Method - Separate Equality
   public boolean equalsPersonSeparate(PersonSeparate o)
   {
      return this.delegate.equals(o.delegate);
   }

   public boolean equals(Object o)
   {
      Object target = o;
      if (o instanceof PersonSeparate) {
         target = ((PersonSeparate) o).delegate;
      }
      return this.delegate.equals(target);
   }

   public int hashCode()
   {
      return this.delegate.hashCode();
   }

Looking at the different implementations of equals andhashCode helps us to see how they are different. The "Generate" equality approach generates the equals method for this new class using NetBeans's standard generation mechanism. Because I have the source set to JDK 1.7 in my NetBeans project, it takes advantage of the new (to Java 7) Objects class. The "Generate" approach also generates its hash code as if done for an all-new class rather than relying explicitly on the delegate's hash code. The other two "Equality" approaches ("Delegate" and "Separate") for generating a "Wrapper" both simply return the delegate's hash code.

The "Equality" setting for "wrapper" local extensions of Equality "Delegate" or "Separate" lead to the same implementations of equals and hashCode. The difference between the two is that "Separate" adds a new method (not part of the Objects contract) called equalsXXXXXXXX where the XXXXXXXX represents the generated class's name [so it is called equalsPersonSeparate(PersonSeparate) in this case]. Note that this new method does not accept an Object like equals, but expects its own type.

NetBeans 7.2's online help covers all of this in more detail under "Introduce Local Extension Dialog Box." The following is an excerpt from that section.

  • Equality. Select one of the following options to set how the equals and hashCode methods should be handled:
    • Delegate. Select to delegate to the equals and hashCode methods of the original class.
    • Generate. Select to generate new equals and hashCode methods using the IDE code generator.
    • Separate. Select to separate the equals method into two. A new method is added to check if the original class equals the extension class.

Before ending this post, I want to point out how easy it is to use NetBeans to identify differences between the generated files. The next screen snapshot shows what it looks like when one right-clicks on the tab in the source code editor for the PersonDelegate.java class/file. This brings up the drop-down menu that includes "Diff To ..." as an option.

I'm then presented with the option of the file to diff PersonDelegate to in either the same package (or browser allows arbitrary location to be specified) or opened in the editor window. In this case, I have selected PersonGenerate in the right side choices of files already open in the editor.

NetBeans displays the differences between PersonDelegate and PersonGenerate as shown in the next screen snapshot.

Most of the changes indicated by the colored bars on the far right are changes in class names. However, as the image above shows, the substantial changes are in the equality methods equals and hashCode.

The NetBeans diff shows that the primary difference between "Delegate" and "Separate" Equality in the corresponding generated Wrapper classes is the new method in the "Separate" class.

Finally, NetBeans shows that the differences between the "Generate" and "Separate" wrapper implementations.

Using NetBeans 7.2's "Wrapper" approach to the "Introduce Local Extension" refactoring provides an easy mechanism for employing the delegation pattern (as commonly understood these days rather than the more historically based use of it described in The Gang of Four is Wrong and You Don't Understand Delegation).

Conclusion

This post has focused on use of NetBeans 7.2's ability to automatically generate code based on the "Introduce Local Extension" refactoring using implementation inheritance ("Subtype") and composition ("Wrapper"). Along the way, the post also examined NetBeans's handy file differencing ("diff") capability.

No comments: