Showing posts with label Guava. Show all posts
Showing posts with label Guava. Show all posts

Monday, December 23, 2013

Determining Presence of Characters or Integers in String with Guava CharMatcher and Apache Commons Lang StringUtils

A recent Reddit post asked the question, "Is there a predefined method for checking if a variable value contains a particular character or integer?" That question-based title was also asked a different way, "A method or quick way for checking if a variable contains any numbers say or ('x',2,'B') like a list?" I am not aware of any single method call within the standard SDK libraries to do this (other than using a carefully designed regular expression), but in this post I answer those questions using Guava's CharMatcher and Apache Common Lang's StringUtils class.

Java's String class does have a contains method that can be used to determine if a single character is contained in that String or if a certain explicitly specified sequence of characters is contained in that String. However, I'm not aware of any way in a single executable statement (not counting regular expressions) to ask Java if a given String contains any of a specified set of characters without needing to contain all of them or contain them in the specified order. Both Guava and Apache Commons Lang do provide mechanisms for just this thing.

Apache Commons Lang (version 3.1 used in this post) provides overloaded StringUtils.containsAny methods that easily accomplish this request. Both overloaded versions expect the first parameter passed to them to be the String (or more precisely, the CharSequence) to be tested to see if it contains a given letter or integer. The first overloaded version, StringUtils.containsAny(CharSequence, char...) accepts zero or more char elements to be tested to see if any of them are in the String represented by the first argument. The second overloaded version, StringUtils.containsAny(CharSequence, CharSequence) expects the second argument to contain all the potential characters to be searched for in the first argument as a single sequence of characters.

The following code listing demonstrates using this Apache Commons Lang approach to determine if a given string contains certain characters. All three statements will pass their assertions because "Inspired by Actual Events" does include 'd' and 'A', but not 'Q'. Because it is only necessary for any one of the provided characters to be present to return true, the first two assertions of true pass. The third assertion passes because the string does NOT contain the only provided letter and so the negative is asserted.

Determining String Contains A Character with StringUtils
private static void demoStringContainingLetterInStringUtils()
{
   assert StringUtils.containsAny("Inspired by Actual Events", 'd', 'A');  // true: both contained
   assert StringUtils.containsAny("Inspired by Actual Events", 'd', 'Q');  // true: one contained
   assert !StringUtils.containsAny("Inspired by Actual Events", 'Q');      // true: none contained (!)
}

Guava's CharMatcher can also be used in a similar manner as demonstrated in the next code listing.

Determining String Contains A Character with CharMatcher
private static void demoStringContainingLetterInGuava()
{
   assert CharMatcher.anyOf("Inspired by Actual Events").matchesAnyOf(new String(new char[]{'d', 'A'}));
   assert CharMatcher.anyOf("Inspired by Actual Events").matchesAnyOf(new String (new char[] {'d', 'Q'}));
   assert !CharMatcher.anyOf("Inspired by Actual Events").matchesAnyOf(new String(new char[]{'Q'}));
}

What if we specifically want to make sure at least one character in a given String/CharSequence is a numeric (integer), but we cannot be guaranteed that the entire string is numerics? The same approach as used above with Apache Commons Lang's StringUtils can be applied here with the only change being that the provided letters to be matched are the numeric digits 0 through 9. This is shown in the next screen snapshot.

Determining String Contains a Numeral with StringUtils
private static void demoStringContainingNumericDigitInStringUtils()
{
   assert !StringUtils.containsAny("Inspired by Actual Events", "0123456789");
   assert StringUtils.containsAny("Inspired by Actual Events 2013", "0123456789");
}

Guava's CharMatcher has a really slick way of expressing this question of whether a provided sequence of characters includes at least one numeral. This is shown in the next code listing.

Determining String Contains a Numeral with CharMatcher
private static void demoStringContainingNumericDigitInGuava()
{
   assert !CharMatcher.DIGIT.matchesAnyOf("Inspired by Actual Events");
   assert CharMatcher.DIGIT.matchesAnyOf("Inspired by Actual Events 2013");
}

CharMatcher.DIGIT provides a concise and expressive approach to specifying that we want to match a digit. Fortunately, CharMatcher provides numerous other public fields similar to DIGIT for convenience in determining if strings contain other types of characters.

For completeness, I have included the single class containing all of the above examples in the next code listing. This class's main() function can be run with the -enableassertions (or -ea) flag set on the Java launcher and will complete without any AssertionErrors.

StringContainsDemonstrator.java
package dustin.examples.strings;

import com.google.common.base.CharMatcher;
import static java.lang.System.out;

import org.apache.commons.lang3.StringUtils;

/**
 * Demonstrate Apache Commons Lang StringUtils and Guava's CharMatcher. This
 * class exists to demonstrate Apache Commons Lang StringUtils and Guava's
 * CharMatcher support for determining if a particular character or set of
 * characters or integers is contained within a given
 * 
 * This class's tests depend on asserts being enabled, so specify the JVM option
 * -enableassertions (-ea) when running this example.
 * 
 * @author Dustin
 */
public class StringContainsDemonstrator
{
   private static final String CANDIDATE_STRING = "Inspired by Actual Events";
   private static final String CANDIDATE_STRING_WITH_NUMERAL = CANDIDATE_STRING + " 2013";
   private static final char FIRST_CHARACTER = 'd';
   private static final char SECOND_CHARACTER = 'A';
   private static final String CHARACTERS = new String(new char[]{FIRST_CHARACTER, SECOND_CHARACTER});
   private static final char NOT_CONTAINED_CHARACTER = 'Q';
   private static final String NOT_CONTAINED_CHARACTERS = new String(new char[]{NOT_CONTAINED_CHARACTER});
   private static final String MIXED_CONTAINED_CHARACTERS = new String (new char[] {FIRST_CHARACTER, NOT_CONTAINED_CHARACTER});
   private static final String NUMERIC_CHARACTER_SET = "0123456789";

   private static void demoStringContainingLetterInGuava()
   {
      assert CharMatcher.anyOf(CANDIDATE_STRING).matchesAnyOf(CHARACTERS);
      assert CharMatcher.anyOf(CANDIDATE_STRING).matchesAnyOf(MIXED_CONTAINED_CHARACTERS);
      assert !CharMatcher.anyOf(CANDIDATE_STRING).matchesAnyOf(NOT_CONTAINED_CHARACTERS);
   }

   private static void demoStringContainingNumericDigitInGuava()
   {
      assert !CharMatcher.DIGIT.matchesAnyOf(CANDIDATE_STRING);
      assert CharMatcher.DIGIT.matchesAnyOf(CANDIDATE_STRING_WITH_NUMERAL);
   }

   private static void demoStringContainingLetterInStringUtils()
   {
      assert StringUtils.containsAny(CANDIDATE_STRING, FIRST_CHARACTER, SECOND_CHARACTER);
      assert StringUtils.containsAny(CANDIDATE_STRING, FIRST_CHARACTER, NOT_CONTAINED_CHARACTER);
      assert !StringUtils.containsAny(CANDIDATE_STRING, NOT_CONTAINED_CHARACTER);
   }

   private static void demoStringContainingNumericDigitInStringUtils()
   {
      assert !StringUtils.containsAny(CANDIDATE_STRING, NUMERIC_CHARACTER_SET);
      assert StringUtils.containsAny(CANDIDATE_STRING_WITH_NUMERAL, NUMERIC_CHARACTER_SET);
   }

   /**
    * Indicate whether assertions are enabled.
    * 
    * @return {@code true} if assertions are enabled or {@code false} if
    *    assertions are not enabled (are disabled).
    */
   private static boolean areAssertionsEnabled()
   {
      boolean enabled = false; 
      assert enabled = true;
      return enabled;
   }

   /**
    * Main function for running methods to demonstrate Apache Commons Lang
    * StringUtils and Guava's CharMatcher support for determining if a particular
    * character or set of characters or integers is contained within a given
    * String.
    * 
    * @param args the command line arguments Command line arguments; none expected.
    */
   public static void main(String[] args)
   {
      if (!areAssertionsEnabled())
      {
         out.println("This class cannot demonstrate anything without assertions enabled.");
         out.println("\tPlease re-run with assertions enabled (-ea).");
         System.exit(-1);
      }

      out.println("Beginning demonstrations...");
      demoStringContainingLetterInGuava();
      demoStringContainingLetterInStringUtils();
      demoStringContainingNumericDigitInGuava();
      demoStringContainingNumericDigitInStringUtils();
      out.println("...Demonstrations Ended");
   }
}

Guava and Apache Commons Lang are very popular with Java developers because of the methods they provide beyond what the SDK provides that Java developers commonly need. In this post, I looked at how Guava's CharMatcher and Apache Commons Lang's StringUtils can be used to concisely but expressively test to determine if any of a set of specified characters exists within a provided string.

Tuesday, May 1, 2012

Recent Interesting Software Development Posts - 1 May 2012

This post references and very briefly summarizes several recent posts on subjects of interest to software developers such as NetBeans 7.1.2, progress on Java 8's Lambda Expressions, Guava Release 12, TestNG, Scala, and the expense of ineffective meetings.

NetBeans 7.1.2

The release of NetBeans 7.1.2 includes JDK 7 Update 4 (first version of Java SE 7 for Mac OS X).

State of the Lambda: Libraries Edition

After attending JavaOne 2011, I believe that lambda expressions are going to dramatically change how we write Java. Therefore, it is always interesting to read about progress related to Lambda expressions. In the April 2012 post State of the Lambda: Libraries Edition, Brian Goetz "describes the design approach taken in the rough prototype that has been implemented in the Lambda Project repository." He points out that this description is "intended as a working straw-man proposal" and that "the final version may look different." Another Goetz April 2012 post (Translation of Lambda Expressions) "outlines the strategy for translating lambda expressions and method references from Java source code into bytecode."

Guava Release 12

Guava Release 12 was released this week. According to the Guava Release 12 Release Notes, this is the first release of Guava that requires Java SE 6: "Guava 12.0 is the first release to require JDK6. Users requiring JDK5 compatibility may continue to use Guava 11.0.2"

TestNG Rather than JUnit

Tomek Kaczanowski has posted the slides he prepared "to persuade my colleagues to migrate from JUnit to TestNG" in his post Why TestNG and Not JUnit?

Scala or Java? Exploring Myths and Facts

The post Scala or Java? Exploring myths and facts addresses some of the alleged pros and cons of Scala articulated online. The author addresses questions about productivity, complexity, concurrency support, tooling, extensibility, interoperability, performance, and backwards compatibility.

The Expense of Ineffective Meetings

Jeffrey Scott Klubeck's post The Expense of Ineffective Meetings is not necessarily new, but it is new to me. This short post is well worth the couple of minutes to read and articulates well what many of us have felt after having a particularly useless meeting foisted upon us. I love the quote, "Patrick Lencioni, author of Death by Meeting, says that bad meetings not only exact a toll on the attendees as they suffer through them, but also cause real human anguish in the form of anger, lethargy, cynicism, and even in the form of lower self-esteem."

10 Hard Truths Developer Must Accept

In the post 10 hard truths developers must learn to accept, Peter Wayner offers ten things that are reminders that "programming offers an array of bitter pills to swallow."

SSH Tunneling Explained

Buddhika Chamith's post SSH Tunneling Explained is a detailed and illustrated overview of SSH tunneling.

Conclusion

This post has referenced a small set of other blog posts, articles, and announcements that I have found particularly interesting in recent weeks.

Monday, December 26, 2011

Guava Release 11's IntMath

As I stated earlier in the post Sneaking a Peek at Guava Release 11, Guava Release 11 provides numerous new classes including several classes specifically related to mathematical operations. In this post, I look at one of these that is targeted at integer math: Guava's IntMath.

As stated in my previous post, com.google.common.math.IntMath is largely based on Henry S. Warren, Jr.'s Hacker's Delight. As the class name indicates, the operations of this class are focused on "arithmetic on values of type int."

The next screen snapshot shows the methods and static attributes supported by IntMath as listed by javap:

For convenience, I have listed a text version of the above javap output here.

Compiled from "IntMath.java"
public final class com.google.common.math.IntMath {
  static final int MAX_POWER_OF_SQRT2_UNSIGNED;
  static final int[] POWERS_OF_10;
  static final int[] HALF_POWERS_OF_10;
  static final int FLOOR_SQRT_MAX_INT;
  static final int[] FACTORIALS;
  static int[] BIGGEST_BINOMIALS;
  public static boolean isPowerOfTwo(int);
  public static int log2(int, java.math.RoundingMode);
  public static int log10(int, java.math.RoundingMode);
  public static int pow(int, int);
  public static int sqrt(int, java.math.RoundingMode);
  public static int divide(int, int, java.math.RoundingMode);
  public static int mod(int, int);
  public static int gcd(int, int);
  public static int checkedAdd(int, int);
  public static int checkedSubtract(int, int);
  public static int checkedMultiply(int, int);
  public static int checkedPow(int, int);
  public static int factorial(int);
  public static int binomial(int, int);
  static {};
}

As the image and text output above indicate, the IntMath class has much to offer in terms of functionality related to integer arithmetic. The remainder of this post will demonstrate this.

Factorial calculation is commonly implemented in software development examples, especially in academic contexts such as illustrating recursion. The blog post Implementing the Factorial Function Using Java and Guava is devoted to different implementations of factorial implemented in Java and Guava. Guava Release 11 now provides a method for calculating factorial in IntMath.factorial(int).

Guava's IntMath.factorial(int) Demonstrated
   /**
    * Demonstrate factorial calculation.
    */
   public static void demoFactorial()
   {
      final int x = 5;
      final int factorial = IntMath.factorial(x);
      out.println("Factorial of x=" + x + " is " + factorial);
   }

Another often mathematical operation supported by Guava Release 11 is the calculation of the binomial coefficient via IntMath.binomial(int,int).

Guava's IntMath.binomial(int,int) Demonstrated
   /**
    * Demonstrate binomial Coefficient calculation
    * (http://en.wikipedia.org/wiki/Binomial_coefficient).
    */
   public static void demoBinomialCoefficient()
   {
      final int n = 5;
      final int k = 3;
      final int binomialCoefficient = IntMath.binomial(n, k);
      out.println(
           "Binomial Coefficient of n=" + n + " and k=" + k + " is "
         + binomialCoefficient);  
   }

Guava Release 11 also adds a method for calculating the greatest common divisor of two provided integers.

Guava's IntMath.gcd(int,int) Demonstrated
   /**
    * Demonstrate calculation of greatest common factor (GCF) [called greatest
    * common divisor here].
    */
   public static void demoGreatestCommonFactor()
   {
      final int x = 30;
      final int y = 45;
      final int gcf = IntMath.gcd(x, y);
      out.println("GCF of " + x + " and " + y + " is " + gcf);
   }

Guava Release 11 provides a method for calculating an integer square root of a provided integer, rounding to the integer result when needed based on the provided RoundingMode. The fact that the result of the square root function is an integer distinguishes this method from Math.sqrt(double).

Guava's IntMath.sqrt(int) Demonstrated
   /**
    * Demonstrate calculation of square roots.
    */
   public static void demoSquareRoot()
   {
      final int x = 16;
      final int sqrtX = IntMath.sqrt(x, RoundingMode.HALF_EVEN);
      out.println("Square root of " + x + " is " + sqrtX);
      final int y = 25;
      final int sqrtY = IntMath.sqrt(y, RoundingMode.HALF_EVEN);
      out.println("Square root of " + y + " is " + sqrtY);  
   }

Guava Release 11's IntMath provides the method pow(int,int) for the first integer multiplied by itself the number of times expressed by the second integer (similar to Math.pow(double,double), but accepting and returning integers rather than doubles.). The IntMath.pow(int,int) method provides handling for situations in which the operation would normally result in an overflow. In such a situation, according to the method's Javadoc, the method will return a result that "will be equal to BigInteger.valueOf(b).pow(k).intValue()."

Guava's IntMath.pow(int,int) Demonstrated
   /**
    * Demonstrate exponential power calculation.
    */
   public static void demoExponentialPower()
   {
      final int base = 2;
      final int exponent = 5;
      final int result = IntMath.pow(base, exponent);
      out.println(base + " to power of " + exponent + " is " + result);
   }

A particularly interesting method that Guava Release 11 provides that could be especially useful in certain software development and computer contexts is the method IntMath.isPowerOfTwo(int). This method returns a boolean indicating whether the provided integer is evenly divisible (no remainder) by two.

Guava's IntMath.isPowerOfTwo(int) Demonstrated
   /**
    * Demonstrate determination of whether an integer is a power of two.
    */
   public static void demoIsPowerOfTwo()
   {
      final int x = 16;
      out.println(x + (IntMath.isPowerOfTwo(x) ? " IS " : " is NOT " ) + " a power of two.");
      final int y = 31;
      out.println(y + (IntMath.isPowerOfTwo(y) ? " IS " : " is NOT " ) + " a power of two.");
   }

Guava's IntMath class provides two methods for performing logarithmic calculations, specifically focusing on the common logarithm (base 10) and the binary logarithm (base 2). Both of these are demonstrated in the next code listing.

Guava's IntMath.log10(int,RoundingMode) and IntMath.log2(int,RoundingMode) Demonstrated
   /**
    * Demonstrate IntMath.log10 and IntMath.log2.
    */
   public static void demoLogarithmicFunctions()
   {
      final int x = 10000000;
      final int resultX = IntMath.log10(x, RoundingMode.HALF_EVEN);
      out.println("Logarithm (base 10) of " + x + " is " + resultX);
      final int y = 32;
      final int resultY = IntMath.log2(y, RoundingMode.HALF_EVEN);
      out.println("Logarithm (base 2) of " + y + " is " + resultY);
   }

Guava Release 11's IntMath.divide(int,int,RoundingMode) allows for integer division in which the type of rounding used in the division can be specified as part of the call. This is more flexible than direct Java integer division which always rounds the quotient down to the lower integer (floor).

Guava's IntMath.divide(int,int,RoundingMode) Demonstrated
   /**
    * Demonstrate division using IntMath.divide.
    */
   public static void demoDivision()
   {
      final int dividend = 30;
      final int divisor = 10;
      final int quotient = IntMath.divide(dividend, divisor, RoundingMode.HALF_EVEN);
      out.println(dividend + " / " + divisor + " = " + quotient);
   }

I mentioned previously that Guava Release 11's IntMath.pow(int,int) would handle overflow situations by returning "BigInteger.valueOf(b).pow(k).intValue()" where 'b' is the first integer (base) and 'k' is the second integer (power/exponent). In some cases, it may be preferable to have an exception thrown when the overflow situation occurs rather than "hiding" the issue. In such cases, Guava Release 11's IntMath.checkedPow(int,int) is desirable because it will throw an ArithmeticException if the operation results in an overflow.

IntMath.checkedPow(int,int) Demonstrated
   /**
    * Demonstrate Guava Release 11's checked power method and compare it to
    * other common approaches for determining base multiplied by itself exponent
    * number of times.
    */
   public static void demoCheckedPower()
   {
      try
      {
         final int base = 2;
         final int exponent = 4;
         final int result = IntMath.checkedPow(base, exponent);
         out.println("IntMath.checkedPow: " + base + "^" + exponent + " = " + result);

         out.println(
              "IntMath.pow: " + Integer.MAX_VALUE + "^2 = " +
            + IntMath.pow(Integer.MAX_VALUE, 2));
         out.println(
              "Math.pow(int,int): " + Integer.MAX_VALUE + "^2 = "
            + Math.pow(Integer.MAX_VALUE, 2));
         out.println("Multiplied: " + Integer.MAX_VALUE*Integer.MAX_VALUE);
         out.print("IntMath.checkedPow: " + Integer.MAX_VALUE + "^2 = ");
         out.println(IntMath.checkedPow(Integer.MAX_VALUE, 2));
      }
      catch (Exception ex)
      {
         err.println("Exception during power: " + ex.toString());
      }
   }

Guava Release 11's IntMath class provides three more "checked" methods that throw an ArithmeticException when the given mathematical operation results in an overflow condition. These methods are for addition, substraction, and multiplication and are respectively called IntMath.checkedAdd(int,int), IntMath.checkedSubtract(int,int), and IntMath.checkedMultiply(int,int). As discussed in conjunction with IntMath.checkedPow(int,int), the advantage of this occurs in situations where it is better to have an exception and know overflow occurred than to blindly operate on an erroneous value due to an overflow condition.

Guava's checkedAdd(int,int), checkedSubtract(int,int), and checkedMultiply(int,int) Demonstrated
   /**
    * Demonstrate Guava Release 11's checked addition method.
    */
   public static void demoCheckedAddition()
   {
      try
      {
         final int augend = 20;
         final int addend = 10;
         final int sum = IntMath.checkedAdd(augend, addend);
         out.println(augend + " + " + addend + " = " + sum);

         final int overflowSum = IntMath.checkedAdd(Integer.MAX_VALUE, 1);
         out.println(Integer.MAX_VALUE + " + 1 = " + overflowSum);
      }
      catch (Exception ex)
      {
         err.println("Exception during addition: " + ex.toString());
      }
   }

   /**
    * Demonstrate Guava Release 11's checked subtraction method.
    */
   public static void demoCheckedSubtraction()
   {
      try
      {
         final int minuend = 30;
         final int subtrahend = 20;
         final int difference = IntMath.checkedSubtract(minuend, subtrahend);
         out.println(minuend + " - " + subtrahend + " = " + difference);

         final int overflowDifference = IntMath.checkedSubtract(Integer.MIN_VALUE, 1);
         out.println(Integer.MIN_VALUE + " - 1 = " + overflowDifference);
      }
      catch (Exception ex)
      {
         err.println("Exception during subtraction: " + ex.toString());
      }
   }

   /**
    * Demonstrate Guava Release 11's checked multiplication method.
    */
   public static void demoCheckedMultiplication()
   {
      try
      {
         final int factor1 = 3;
         final int factor2 = 10;
         final int product = IntMath.checkedMultiply(factor1, factor2);
         out.println(factor1 + " * " + factor2 + " = " + product);

         final int overflowProduct = IntMath.checkedMultiply(Integer.MAX_VALUE, 2);
         out.println(Integer.MAX_VALUE + " * 2 = " +  overflowProduct);
      }
      catch (Exception ex)
      {
         err.println("Exception during multiplication: " + ex.toString());
      }
   }

The blog post Handling Very Large Numbers in Java talks about issues with large numbers in Java and the overflow that can occur. As this post recommends, use of BigInteger and BigDecimal is often recommended for such situations. However, these new Guava IntMath "checked" methods provide another alternative for the Java developer who wants to deal with integers, but know when overflow has occurred.

I have shown simple examples of using most of the methods of the new IntMath class in Guava Release 11 [I did not discuss or show IntMath.mod(int,int)]. The next code listing ties all of the above examples together and is followed by the output from running that code listing.

UsingIntMath.java
package dustin.examples;

import static java.lang.System.err;
import static java.lang.System.out;

import com.google.common.math.IntMath;
import java.math.RoundingMode;

/**
 * Simple examples of using Guava Release 11's {@code IntMath} class.
 * 
 * @author Dustin
 */
public class UsingIntMath
{
   /**
    * Demonstrate binomial Coefficient calculation
    * (http://en.wikipedia.org/wiki/Binomial_coefficient).
    */
   public static void demoBinomialCoefficient()
   {
      final int n = 5;
      final int k = 3;
      final int binomialCoefficient = IntMath.binomial(n, k);
      out.println(
           "Binomial Coefficient of n=" + n + " and k=" + k + " is "
         + binomialCoefficient);  
   }

   /**
    * Demonstrate factorial calculation.
    */
   public static void demoFactorial()
   {
      final int x = 5;
      final int factorial = IntMath.factorial(x);
      out.println("Factorial of x=" + x + " is " + factorial);
   }

   /**
    * Demonstrate calculation of greatest common factor (GCF) [called greatest
    * common divisor here].
    */
   public static void demoGreatestCommonFactor()
   {
      final int x = 30;
      final int y = 45;
      final int gcf = IntMath.gcd(x, y);
      out.println("GCF of " + x + " and " + y + " is " + gcf);
   }

   /**
    * Demonstrate calculation of square roots.
    */
   public static void demoSquareRoot()
   {
      final int x = 16;
      final int sqrtX = IntMath.sqrt(x, RoundingMode.HALF_EVEN);
      out.println("Square root of " + x + " is " + sqrtX);
      final int y = 25;
      final int sqrtY = IntMath.sqrt(y, RoundingMode.HALF_EVEN);
      out.println("Square root of " + y + " is " + sqrtY);  
   }

   /**
    * Demonstrate determination of whether an integer is a power of two.
    */
   public static void demoIsPowerOfTwo()
   {
      final int x = 16;
      out.println(x + (IntMath.isPowerOfTwo(x) ? " IS " : " is NOT " ) + " a power of two.");
      final int y = 31;
      out.println(y + (IntMath.isPowerOfTwo(y) ? " IS " : " is NOT " ) + " a power of two.");
   }

   /**
    * Demonstrate exponential power calculation.
    */
   public static void demoExponentialPower()
   {
      final int base = 2;
      final int exponent = 5;
      final int result = IntMath.pow(base, exponent);
      out.println(base + " to power of " + exponent + " is " + result);
   }

   /**
    * Demonstrate IntMath.log10 and IntMath.log2.
    */
   public static void demoLogarithmicFunctions()
   {
      final int x = 10000000;
      final int resultX = IntMath.log10(x, RoundingMode.HALF_EVEN);
      out.println("Logarithm (base 10) of " + x + " is " + resultX);
      final int y = 32;
      final int resultY = IntMath.log2(y, RoundingMode.HALF_EVEN);
      out.println("Logarithm (base 2) of " + y + " is " + resultY);
   }

   /**
    * Demonstrate Guava Release 11's checked addition method.
    */
   public static void demoCheckedAddition()
   {
      try
      {
         final int augend = 20;
         final int addend = 10;
         final int sum = IntMath.checkedAdd(augend, addend);
         out.println(augend + " + " + addend + " = " + sum);

         final int overflowSum = IntMath.checkedAdd(Integer.MAX_VALUE, 1);
         out.println(Integer.MAX_VALUE + " + 1 = " + overflowSum);
      }
      catch (Exception ex)
      {
         err.println("Exception during addition: " + ex.toString());
      }
   }

   /**
    * Demonstrate Guava Release 11's checked subtraction method.
    */
   public static void demoCheckedSubtraction()
   {
      try
      {
         final int minuend = 30;
         final int subtrahend = 20;
         final int difference = IntMath.checkedSubtract(minuend, subtrahend);
         out.println(minuend + " - " + subtrahend + " = " + difference);

         final int overflowDifference = IntMath.checkedSubtract(Integer.MIN_VALUE, 1);
         out.println(Integer.MIN_VALUE + " - 1 = " + overflowDifference);
      }
      catch (Exception ex)
      {
         err.println("Exception during subtraction: " + ex.toString());
      }
   }

   /**
    * Demonstrate Guava Release 11's checked multiplication method.
    */
   public static void demoCheckedMultiplication()
   {
      try
      {
         final int factor1 = 3;
         final int factor2 = 10;
         final int product = IntMath.checkedMultiply(factor1, factor2);
         out.println(factor1 + " * " + factor2 + " = " + product);

         final int overflowProduct = IntMath.checkedMultiply(Integer.MAX_VALUE, 2);
         out.println(Integer.MAX_VALUE + " * 2 = " +  overflowProduct);
      }
      catch (Exception ex)
      {
         err.println("Exception during multiplication: " + ex.toString());
      }
   }

   /**
    * Demonstrate Guava Release 11's checked power method and compare it to
    * other common approaches for determining base multiplied by itself exponent
    * number of times.
    */
   public static void demoCheckedPower()
   {
      try
      {
         final int base = 2;
         final int exponent = 4;
         final int result = IntMath.checkedPow(base, exponent);
         out.println("IntMath.checkedPow: " + base + "^" + exponent + " = " + result);

         out.println(
              "IntMath.pow: " + Integer.MAX_VALUE + "^2 = " +
            + IntMath.pow(Integer.MAX_VALUE, 2));
         out.println(
              "Math.pow(int,int): " + Integer.MAX_VALUE + "^2 = "
            + Math.pow(Integer.MAX_VALUE, 2));
         out.println("Multiplied: " + Integer.MAX_VALUE*Integer.MAX_VALUE);
         out.print("IntMath.checkedPow: " + Integer.MAX_VALUE + "^2 = ");
         out.println(IntMath.checkedPow(Integer.MAX_VALUE, 2));
      }
      catch (Exception ex)
      {
         err.println("Exception during power: " + ex.toString());
      }
   }

   /**
    * Demonstrate division using IntMath.divide.
    */
   public static void demoDivision()
   {
      final int dividend = 30;
      final int divisor = 10;
      final int quotient = IntMath.divide(dividend, divisor, RoundingMode.HALF_EVEN);
      out.println(dividend + " / " + divisor + " = " + quotient);
   }

   /**
    * Main function for demonstrating Guava Release 11's {@code IntMath} class.
    * 
    * @param arguments Command-line arguments; none expected.
    */
   public static void main(final String[] arguments)
   {
      demoBinomialCoefficient();
      demoFactorial();
      demoGreatestCommonFactor();
      demoSquareRoot();
      demoIsPowerOfTwo();
      demoExponentialPower();
      demoLogarithmicFunctions();
      demoCheckedAddition();
      demoCheckedSubtraction();
      demoCheckedMultiplication();
      demoCheckedPower();
      demoDivision();
   }
}

This blog post has attempted to demonstrate the usefulness and ease-of-use provided by Guava Release 11's IntMath class. This class provides numerous convenient method for simplifying common mathematical operations on integers. Guava Release 11 provides essentially the same methods in the com.google.common.math package for longs (LongMath) and similar methods for double's (DoubleMath) and BigIntegers (BigIntegerMath).

Tuesday, December 13, 2011

Sneaking a Peek at Guava Release 11

The recent reddit/java thread If you still don't know why Guava is awesome .... references the Guava Explained Wiki page. As I was looking at this, I noticed the page focused on Guava Math Explained. I have blogged several times regarding Guava features, but I was not aware of the referenced math-related classes. That's when I realized that these refer to Guava Release 11. At that point, it seemed interesting to snoop out other enhancements we might expect from the next release of Guava. I briefly cover some of the clues already available as to the enhancements coming with Guava Release 11, but caveat this discussion with the obvious disclaimers that things are subject to change before its final release and that I have no inside knowledge of Guava's inner workings.

The previously referenced Guava Explained Wiki page is only one of several of Guava's Wiki pages that have been updated by wasserman.louis in the past 36 hours (from this post's publication).

Most of the updated Guava Wiki pages appear to be updated to associate the "Explained" label with the entry and tie it in to the "Guava Explained" wiki page. However, the Guava Math Explained Wiki page is definitely new.

The classes in the Guava Math utilities appear to belong to the com.google.common.math package with is an all-new package containing four classes: BigIntegerMath, DoubleMath, IntMath, and LongMath. The class-level Javadoc description for each of these four Guava math classes tell us that each class has "implementations of many methods" which "are based on material from Henry S. Warren, Jr.'s Hacker's Delight, (Addison Wesley, 2002)." As is the case with many of Guava's other classes, these classes supplement the methods provided by Java SDK's Math and other mathematical classes.

Although the main Guava Libraries page currently points to Guava Release 10 Javadoc as its "API docs for the most recent release," the "pre-release API docs in git" currently points to documentation that references Guava Release 11. For example, while com.google.common.math is not available in the standard Guava API documentation (Release 10), the package is documented in the pre-release API documentation (Release 11). The newer, "pre-release" API documentation gives us some of our best clues regarding likely future Guava features.

In the blog post Groovy Script for Comparing Javadoc Versions, I used a Groovy script to compare versions of Java by comparing the constructs they added or removed in each new version. I made some adaptations to that script to compare arbitrary listing of Strings and have applied it to compare the "most recent release" (10) of Guava to the "pre-release" (11). Here is the modified script for comparing the two.

diffVersionsJavadocs.groovy
#!/usr/bin/env groovy
// diffVersionsJavadocs.groovy
//
// Examples using Guava:
//
// Guava Release 10:
//    http://docs.guava-libraries.googlecode.com/git-history/v10.0.1/javadoc/allclasses-noframe.html
// Guava Release 11:
//    http://docs.guava-libraries.googlecode.com/git/javadoc/allclasses-noframe.html

if (args.length < 4)
{
   println "\nUSAGE: diffVersionsJavadocs first_label first_url second_label second_url"
   println "\n\twhere first_label is name of first version"
   println "\t      first_url is URL of Javadoc for first version (usually allclasses)"
   println "\t      second_label is name of second version"
   println "\t      second_url is URL of Javadoc for second version (usually allclasses)"
   System.exit(-1)
}
@Grab(group='org.ccil.cowan.tagsoup', module='tagsoup', version='0.9.7')

def firstXml = new XmlParser(new org.ccil.cowan.tagsoup.Parser()).parse(args[1])
def firstUrls = firstXml.'**'.a.@href
def firstLabel = args[0]

def secondXml = new XmlParser(new org.ccil.cowan.tagsoup.Parser()).parse(args[3])
def secondUrls = secondXml.'**'.a.@href
def secondLabel = args[2]

compareSetsOfStrings(secondUrls, secondLabel, firstUrls, firstLabel)

/**
 * Compare first Collection of Strings to second Collection of Strings by
 * identifying which Strings are in each Collection that are not in the other.
 *
 * @param firstStrings First Collection of Strings to be compared.
 * @param firstLabel Name of first collection of Strings.
 * @param secondStrings Second Collection of Strings to be compared.
 * @param secondLabel Name of second collection of Strings.
 */
def void compareSetsOfStrings(
   Collection<String> firstStrings, String firstLabel,
   Collection<String> secondStrings, String secondLabel)
{
   println "Constructs in ${firstLabel} But Not in ${secondLabel}"
   def firstButNotSecond = firstStrings - secondStrings
   printIndentedStrings(firstButNotSecond)

   println "Constructs in ${secondLabel} But Not in ${firstLabel}"
   def secondButNotFirst = secondStrings - firstStrings
   printIndentedStrings(secondButNotFirst)
}


/**
 * Print the provided Strings one per line indented; prints "None" if the
 * provided List of Strings is empty or null.
 *
 * @param strings The Strings to be printed
 */
def void printIndentedStrings(Collection<String> strings)
{
   if (!strings?.isEmpty())
   {
      new TreeSet(strings).each
      {
         println "\t${it}"
      }
   }
   else
   {
      println "\tNone"
   }   
}

For convenience, I placed the two URLs I provided to the script for comparing Guava 10 to Guava 11 in the script comments at the beginning of the script. The next screen snapshot shows running the script and its output.

The Groovy script comparing Javadoc for Guava Release 10 to Guava Release 11 indicates that four constructs have been removed from Guava Release 11 that were in Guava Release 9. These are EvictionListeners (class), MapEvictionListener (interface), AbstractListenableFuture (class), and UninterruptibleFuture (interface). All of these had been clearly marked in the Javadoc for deprecation in the Release 10 documentation, including specific statements that they were scheduled to be removed in Release 11 and what should be used instead of them. (I had posted in Effective Javadoc Documentation Illustrated in Familiar Projects that this advertised version from which a deprecated construct would be removed is a particularly effective documentation tool.)

Guava Release 11 introduces several entirely new constructs. These were shown in the screen snapshot above but are listed here in bullet form for convenience.

Constructs in Release 11 But Not in Release 10 (per Javadoc comparison)
  • com/google/common/cache/AbstractLoadingCache.html
  • com/google/common/cache/CacheLoader.InvalidCacheLoadException.html
  • com/google/common/cache/ForwardingLoadingCache.SimpleForwardingLoadingCache.html
  • com/google/common/cache/ForwardingLoadingCache.html
  • com/google/common/cache/LoadingCache.html
  • com/google/common/cache/Weigher.html
  • com/google/common/collect/ImmutableTable.Builder.html
  • com/google/common/collect/ImmutableTable.html
  • com/google/common/collect/Queues.html
  • com/google/common/collect/RangeSet.html
  • com/google/common/collect/SortedMultiset.html
  • com/google/common/escape/ArrayBasedCharEscaper.html
  • com/google/common/escape/ArrayBasedEscaperMap.html
  • com/google/common/escape/ArrayBasedUnicodeEscaper.html
  • com/google/common/escape/CharEscaper.html
  • com/google/common/escape/CharEscaperBuilder.html
  • com/google/common/escape/Escaper.html
  • com/google/common/escape/Escapers.Builder.html
  • com/google/common/escape/Escapers.html
  • com/google/common/escape/SourceCodeEscapers.html
  • com/google/common/escape/UnicodeEscaper.html
  • com/google/common/html/HtmlEscapers.html
  • com/google/common/math/BigIntegerMath.html
  • com/google/common/math/DoubleMath.html
  • com/google/common/math/IntMath.html
  • com/google/common/math/LongMath.html
  • com/google/common/net/HttpHeaders.html
  • com/google/common/net/PercentEscaper.html
  • com/google/common/net/UriEscapers.html
  • com/google/common/primitives/UnsignedInteger.html
  • com/google/common/primitives/UnsignedInts.html
  • com/google/common/primitives/UnsignedLong.html
  • com/google/common/util/concurrent/AbstractScheduledService.CustomScheduler.html
  • com/google/common/util/concurrent/AbstractScheduledService.Scheduler.html
  • com/google/common/util/concurrent/AbstractScheduledService.html
  • com/google/common/util/concurrent/AsyncFunction.html
  • com/google/common/util/concurrent/AtomicDouble.html
  • com/google/common/util/concurrent/AtomicDoubleArray.html
  • com/google/common/util/concurrent/AtomicLongMap.html
  • com/google/common/xml/XmlEscapers.html

Besides the new math classes, other Groovy constructs that I look forward to trying out include the new collection classes, the new concurrency classes, the new unsigned primitive classes, and the escapers (com.google.common.escape package, com.google.common.html.HtmlEscapers class, com.google.common.net.PercentEscaper, com.google.common.net.UriEscapers, and com.google.common.xml.XmlEscapers).

The script executed above only showed entirely new constructs to Guava Release 11, but Guava Release 11 will also be getting new methods on existing objects. For example, the highly useful Guava Strings class appears to be getting two new methods [commonPrefix(CharSequence,CharSequence) and commonSuffix(CharSequence,CharSequence).] and Objects.ToStringHelper appears to be getting numerous new convenience methods.

Being an open source project, Guava's new features may be best "peeked" at by looking at the source code itself. Guava's Javadoc documentation is generated such that clicking on a construct's name in its Javadoc page will display that construct's source code. In some cases, this may be quicker than downloading the code, though the advantage of downloading source code is obviously the ability to view it in your favorite IDE.

Friday, November 25, 2011

File Management in Java with Guava's Files Class

Both Groovy and Java SE 7 provide improvements for file management in Java as I discussed in posts here, here, here, and here. However, when a particular Java application is not able to yet use Java SE 7 or Groovy for its file management, an improved file management experience can still be obtained by using Guava's Files class.

File handling has always seemed a little more difficult in Java than in many other programming languages. Java SE 7 certainly improves Java's file handling capabilities dramatically with NIO.2, but not every project can make use of Java SE 7. For those projects not able to use Java SE 7, Guava's Files class is a nice intermediate solution for easier file handling. It is important to note here that Java SE 7 introduces a Files class of its own, so any use of Guava's Files class in Java SE 7 must be fully scoped if the Java version of Files is used in the same code. My examples have been written and built in Java SE 7, but I avoid using Java SE 7's Files class and so don't need to fully scope Guava's same-named class.

File Creation

Guava's Files class includes a couple overloaded write methods for easily writing content to a file. The next code sample demonstrates using Files.write(byte[],File).

Demonstrating Files.write(byte[],File)
   /**
    * Demonstrate writing bytes to a specified file.
    * 
    * @param fileName Name of file to be written to.
    * @param contents Contents to be written to file.
    */
   public void demoFileWrite(final String fileName, final String contents)
   {
      checkNotNull(fileName, "Provided file name for writing must NOT be null.");
      checkNotNull(contents, "Unable to write null contents.");
      final File newFile = new File(fileName);
      try
      {
         Files.write(contents.getBytes(), newFile);
      }
      catch (IOException fileIoEx)
      {
         err.println(  "ERROR trying to write to file '" + fileName + "' - "
                     + fileIoEx.toString());
      }
   }

There are a couple observations that can be from this first code sample that will apply to all other code samples in this post. First, I make use of the statically imported Guava Preconditions class to provide an easy check ensuring that the provided parameters are not null. The second common feature of this code is that it explicitly catches and "handles" the checked exception IOException. All other Files methods demonstrated in this post similarly throw this same checked exception.

File Copying

The next example demonstrates how easy it is to copy files using Guava's Files.copy(File,File) method (one of several overloaded methods with name 'copy').

Demonstrating Files.copy(File,File)
   /**
    * Demonstrate simple file copying in Guava. This demonstrates one of the
    * numerous overloaded copy methods provided by Guava's Files class. The
    * version demonstrated here copies one provided File instance to another
    * provided File instance.
    * 
    * @param sourceFileName Name of file that is to be copied.
    * @param targetFileName Name of file that is result of file copying.
    */
   public void demoSimpleFileCopy(
      final String sourceFileName, final String targetFileName)
   {
      checkNotNull(sourceFileName, "Copy source file name must NOT be null.");
      checkNotNull(targetFileName, "Copy target file name must NOT be null.");
      final File sourceFile = new File(sourceFileName);
      final File targetFile = new File(targetFileName);
      try
      {
         Files.copy(sourceFile, targetFile);
      }
      catch (IOException fileIoEx)
      {
         err.println(
              "ERROR trying to copy file '" + sourceFileName
            + "' to file '" + targetFileName + "' - " + fileIoEx.toString());
      }
   }
File Moving

Moving files with Guava is as easy as copying. The Files.move(File,File) method is demonstrated in the next code snippet.

Demonstrating Files.move(File,File)
  /**
    * Demonstrate moving a file with Guava's Files.move(File,File).
    * 
    * @param sourceFileName Path/name of File to be moved.
    * @param targetFileName Path/name of Destination of file.
    */
   public void demoMove(final String sourceFileName, final String targetFileName)
   {
      checkNotNull(sourceFileName, "Move source file name must NOT be null.");
      checkNotNull(targetFileName, "Move destination name must NOT be null.");
      final File sourceFile = new File(sourceFileName);
      final File targetFile = new File(targetFileName);
      try
      {
         Files.move(sourceFile, targetFile);
      }
      catch (IOException fileIoEx)
      {
         err.println(
              "ERROR trying to move file '" + sourceFileName
            + "' to '" + targetFileName + "' - " + fileIoEx.toString());
      }
   }
Comparing Files

Determining if two files are the same is straightforward with the Gauva Files.equal(File,File) method.

Demonstrating Files.equal(File,File)
   /**
    * Demonstrate using Guava's Files.equal(File,File) to compare contents of
    * two files.
    * 
    * @param fileName1 Name of first file to be compared.
    * @param fileName2 Name of second file to be compared.
    */
   public void demoEqual(final String fileName1, final String fileName2)
   {
      checkNotNull(fileName1, "First file name for comparison must NOT be null.");
      checkNotNull(fileName2, "Second file name for comparison must NOT be null.");
      final File file1 = new File(fileName1);
      final File file2 = new File(fileName2);
      try
      {
         out.println(
             "File '" + fileName1 + "' "
           + (Files.equal(file1, file2) ? "IS" : "is NOT")
           + " the same as file '" + fileName2 + "'.");
      }
      catch (IOException fileIoEx)
      {
         err.println(
              "ERROR trying to compare two files '"
            + fileName1 + "' and '" + fileName2 + "' - " + fileIoEx.toString());
      }
   }
Touching Files

Touching a file to create a new empty file or to update the timestamp on an existing file can be easily accomplished with Guava's Files.touch(File) as shown in the next code sample.

Demonstrating Files.touch(File)
   /**
    * Demonstrate Guava's Files.touch(File) method.
    * 
    * @param fileNameToBeTouched Name of file to be 'touch'-ed.
    */
   public void demoTouch(final String fileNameToBeTouched)
   {
      checkNotNull(fileNameToBeTouched, "Unable to 'touch' a null filename.");
      final File fileToTouch = new File(fileNameToBeTouched);
      try
      {
         Files.touch(fileToTouch);
      }
      catch (IOException fileIoEx)
      {
         err.println(
              "ERROR trying to touch file '" + fileNameToBeTouched
            + "' - " + fileIoEx.toString());
      }
   }
Retrieving File Contents

With a simplicity reminiscent of Groovy's GDK extension File.getText(), Guava's Files.toString(File,Charset) makes it easy to retrieve text contents of a file.

Demonstrating Files.toString(File,Charset)
   /**
    * Demonstrate retrieving text contents of a specified file with Guava's 
    * Files.toString(File) method.
    * 
    * @param nameOfFileToGetTextFrom Name of file from which text is to be
    *    retrieved.
    */
   public void demoToString(final String nameOfFileToGetTextFrom)
   {
      checkNotNull(nameOfFileToGetTextFrom, "Unable to retrieve text from null.");
      final File sourceFile = new File(nameOfFileToGetTextFrom);
      try
      {
         final String fileContents = Files.toString(sourceFile, Charset.defaultCharset());
         out.println(
              "Contents of File '" + nameOfFileToGetTextFrom
            + "' are: " + fileContents);
      }
      catch (IOException fileIoEx)
      {
         err.println(
              "ERROR trying to get text contents of file '"
            + nameOfFileToGetTextFrom + "' - " + fileIoEx.toString());
      }
   }
Temporary Directory Creation

Guava makes it easy to generate a temporary directory with Files.createTempDir().

Demonstrating Files.createTempDir()
   /**
    * Demonstrate Guava's Files.createTempDir() method for creating a temporary
    * directory.
    */
   public void demoTemporaryDirectoryCreation()
   {
      final File newTempDir = Files.createTempDir();
      try
      {
         out.println(
            "New temporary directory is '" + newTempDir.getCanonicalPath() + "'.");
      }
      catch (IOException ioEx)
      {
         err.println("ERROR: Unable to create temporary directory - " + ioEx.toString());
      }
   }

I don't provide a code sample here, but it's worth noting that Guava provides a convenience method for creating a new directory will all necessary new parent directories with its Files.createParentDirs(File) method.

Retrieving Contents of File as Lines

There are times when it is most convenient to get the contents of a file as a series of lines so that each line can be processed. This is commonly done in Groovy with overloaded versions of readLines() and eachLine(). Guava provides similar functionality to Groovy's File.readLines() with its Files.readLines(File, Charset) method. This is demonstrated in the following code sample.

Demonstrating Files.readLines(File,Charset)
   /**
    * Demonstrate extracting lines from file.
    * 
    * @param fileName Name of file from which lines are desired.
    */
   public void demoRetrievingLinesFromFile(final String fileName)
   {
      final File file = new File(fileName);
      try
      {
         final List<String> lines = Files.readLines(file, Charset.defaultCharset());
         for (final String line : lines)
         {
            out.println(">> " + line);
         }
      }
      catch (IOException ioEx)
      {
         err.println(
              "ERROR trying to retrieve lines from file '"
            + fileName + "' - " + ioEx.toString());
      }
   }

The other overloaded version of readLines is interesting because it allows a LineProcessor callback to be specified to terminate returning of lines earlier than the end of the file.

Reading File's First Line

I have run into numerous situations in which it has been useful to read only the first line of file. This first line might tell my code what type of script is being run, provide XML prolog information, or other interesting overview data of a file's contents. Guava makes it easy to retrieve just the first line with the Files.readFirstLine(File,Charset) method. This is demonstrated in the next code listing.

Demonstrating Files.readFirstLine(File,Charset)
   /**
    * Demonstrate extracting first line of file.
    * 
    * @param fileName File from which first line is to be extracted.
    */
   public void demoRetrievingFirstLineFromFile(final String fileName)
   {
      final File file = new File(fileName);
      try
      {
         final String line = Files.readFirstLine(file, Charset.defaultCharset());
         out.println("First line of '" + fileName + "' is '" + line + "'.");
      }
      catch (IOException fileIoEx)
      {
         err.println(
              "ERROR trying to retrieve first line of file '"
            + fileName + "' - " + fileIoEx.toString());
      }
   }
There's Much More

Although I have discussed and demonstrated several useful Files methods in this post, the class has many more to offer. Some of these include the ability to append to an existing file with Files.append(CharSequence,File,Charset), getting a file's checksum with Files.getChecksum(File,Checksum), getting a file's digest with Files.getDigest(File,MessageDigest), accessing a BufferedReader with Files.newReader(File,Charset), accessing a BufferedWriter with Files.newWriter(File,Charset), and accessing a MappedByteBuffer mapped to an underlying file via overloaded Files.map methods.

Conclusion

File handling in Java is much easier and more convenient with Guava's Files class. Guava brings file-handling convenience to Java applications that cannot make use of Groovy's or Java SE 7's file handling conveniences.

Monday, November 21, 2011

Two Generally Useful Guava Annotations

Guava currently (Release 10) includes four annotations in its com.google.common.annotations package: Beta, VisibleForTesting, GwtCompatible, and GwtIncompatible. The last two are specific to use with Google Web Toolkit (GWT), but the former two can be useful in a more general context.

The @Beta annotation is used within Guava's own code base to indicate "that a public API (public class, method or field) is subject to incompatible changes, or even removal, in a future release." Although this annotation is used to indicate at-risk public API constructs in Guava, it can also be used in code that has access to Guava on its classpath. Developers can use this annotation to advertise their own at-risk public API constructs.

The @Beta annotation is defined as a @Documented, which means that it marks something that is part of the public API and should be considered by Javadoc and other source code documentation tools.

The @VisibleForTesting annotation "indicates that the visibility of a type or member has been relaxed to make the code testable." I have never liked having to relax type or member visibility to make something testable. It feels wrong to have to compromise one's design to allow testing to occur. This annotation is better than nothing in such a case because it at least makes it clear to others using the construct that there is a reason for its otherwise surprisingly relaxed visibility.

Conclusion

Guava provides two annotations that are not part of the standard Java distribution, but cover situations that we often run into during Java development. The @Beta annotation indicates a construct in a public API that may be changed or removed. The @VisibleForTesting annotation advertises to other developers (or reminds the code's author) when a decision was made for relaxed visibility to make testing possible or easier.

Wednesday, November 2, 2011

Guava's Strings Class

In the post Checking for Null or Empty or White Space Only String in Java, I demonstrated common approaches in the Java ecosystem (standard Java, Guava, Apache Commons Lang, and Groovy) for checking whether a String is null, empty, or white space only similar to what C# supports with the String.IsNullOrWhiteSpace method. One of the approaches I showed was a Guava-based approach that made use of the Guava class Strings and its static isNullOrEmpty(String) method. In this post, I look at other useful functionality for working with Strings that is provided by Guava's six "static utility methods pertaining to String" that are bundled into the Strings class.

Using Guava's Strings class is straightforward because of its well-named methods. The following list enumerates the methods (all static) on the Strings class with a brief description of what each does next to the method name (these descriptions are borrowed or adapted from the Javadoc documentation).

isNullOrEmpty

Guava's Strings.isEmptyOrNull(String) method makes it easy to build simple and highly readable conditional statements that check a String for null or emptiness before acting upon said String. As previously mentioned, I have briefly covered this method before. Another code demonstration of this method is shown next.

Code Sample Using Strings.isNullOrEmpty(String)
   /**
    * Print to standard output a string message indicating whether the provided
    * String is null or empty or not null or empty. This method uses Guava's
    * Strings.isNullOrEmpty(String) method.
    * 
    * @param string String to be tested for null or empty.
    */
   private static void printStringStatusNullOrEmpty(final String string)
   {
      out.println(  "String '" + string + "' "
                  + (Strings.isNullOrEmpty(string) ? "IS" : "is NOT")
                  + " null or empty.");
   }

   /**
    * Demonstrate Guava Strings.isNullOrEmpty(String) method on some example
    * Strings.
    */
   public static void demoIsNullOrEmpty()
   {
      printHeader("Strings.isNullOrEmpty(String)");
      printStringStatusNullOrEmpty("Dustin");
      printStringStatusNullOrEmpty(null);
      printStringStatusNullOrEmpty("");
   }

The output from running the above code is contained in the next screen snapshot. It shows that true is returned when either null or empty String ("") is passed to Strings.isNullOrEmpty(String).

nullToEmpty and emptyToNull

There are multiple times when one may want to treat a null String as an empty String or wants present a null when an empty String exists. In cases such as these when transformations between null and empty String are desired, The following code snippets demonstrate use of Strings.nullToEmpty(String) and Strings.emptyToNull(String).

nullToEmpty and emptyToNull
   /**
    * Print to standard output a simple message indicating the provided original
    * String and the provided result/output String.
    * 
    * @param originalString Original String.
    * @param resultString Output or result String created by operation.
    * @param operation The operation that acted upon the originalString to create
    *    the resultString.
    */
   private static void printOriginalAndResultStrings(
      final String originalString, final String resultString, final String operation)
   {
      out.println("Passing '" + originalString + "' to " + operation + " produces '" + resultString + "'");
   }

   /** Demonstrate Guava Strings.emptyToNull() method on example Strings. */
   public static void demoEmptyToNull()
   {
      final String operation = "Strings.emptyToNull(String)";
      printHeader(operation);
      printOriginalAndResultStrings("Dustin", Strings.emptyToNull("Dustin"), operation);
      printOriginalAndResultStrings(null, Strings.emptyToNull(null), operation);
      printOriginalAndResultStrings("", Strings.emptyToNull(""), operation);
   }

   /** Demonstrate Guava Strings.nullToEmpty() method on example Strings. */
   public static void demoNullToEmpty()
   {
      final String operation = "Strings.nullToEmpty(String)";
      printHeader(operation);
      printOriginalAndResultStrings("Dustin", Strings.nullToEmpty("Dustin"), operation);
      printOriginalAndResultStrings(null, Strings.nullToEmpty(null), operation);
      printOriginalAndResultStrings("", Strings.nullToEmpty(""), operation);
   }

The output from running the above code (shown in the next screen snapshot) proves that these methods work as we'd expect: converting null to empty String or converting empty String to null.

padStart and padEnd

Another common practice when dealing with Strings in Java (or any other language) is to pad a String to a certain length with a specified character. Guava supports this easily with its Strings.padStart(String,int,char) and Strings.padEnd(String,int,char) methods, which are demonstrated in the following code listing.

padStart and padEnd
   /**
    * Demonstrate Guava Strings.padStart(String,int,char) method on example
    * Strings.
    */
   public static void demoPadStart()
   {
      final String operation = "Strings.padStart(String,int,char)";
      printHeader(operation);
      printOriginalAndResultStrings("Dustin", Strings.padStart("Dustin", 10, '_'), operation);
      /* Do NOT call Strings.padStart(String,int,char) on a null String:
       *    Exception in thread "main" java.lang.NullPointerException
     *         at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:187)
     *         at com.google.common.base.Strings.padStart(Strings.java:97)
       */
      //printOriginalAndResultStrings(null, Strings.padStart(null, 10, '_'), operation);
      printOriginalAndResultStrings("", Strings.padStart("", 10, '_'), operation);      
   }

   /**
    * Demonstrate Guava Strings.padEnd(String,int,char) method on example
    * Strings.
    */
   public static void demoPadEnd()
   {
      final String operation = "Strings.padEnd(String,int,char)";
      printHeader(operation);
      printOriginalAndResultStrings("Dustin", Strings.padEnd("Dustin", 10, '_'), operation);
      /*
       * Do NOT call Strings.padEnd(String,int,char) on a null String:
       *    Exception in thread "main" java.lang.NullPointerException
     *         at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:187)
     *         at com.google.common.base.Strings.padEnd(Strings.java:129)
       */
      //printOriginalAndResultStrings(null, Strings.padEnd(null, 10, '_'), operation);
      printOriginalAndResultStrings("", Strings.padEnd("", 10, '_'), operation);       
   }

When executed, the above code pads the provided Strings with underscore characters either before or after the provided String depending on which method was called. In both cases, the length of the String was specified as ten. This output is shown in the next screen snapshot.

repeat

A final manipulation technique that Guava's Strings class supports is the ability to easily repeat a given String a specified number of times. This is demonstrated in the next code listing and the corresponding screen snapshot with that code's output. In this example, the provided String is repeated three times.

repeat
   /** Demonstrate Guava Strings.repeat(String,int) method on example Strings. */
   public static void demoRepeat()
   {
      final String operation = "Strings.repeat(String,int)";
      printHeader(operation);
      printOriginalAndResultStrings("Dustin", Strings.repeat("Dustin", 3), operation);
      /*
       * Do NOT call Strings.repeat(String,int) on a null String:
       *    Exception in thread "main" java.lang.NullPointerException
     *         at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:187)
       *         at com.google.common.base.Strings.repeat(Strings.java:153)
       */
      //printOriginalAndResultStrings(null, Strings.repeat(null, 3), operation);
      printOriginalAndResultStrings("", Strings.repeat("", 3), operation);
   }
Wrapping Up

The above examples are simple because Guava's Strings class is simple to use. The complete class containing the demonstration code shown earlier is now listed.

GuavaStrings.java
package dustin.examples;

import com.google.common.base.Strings;
import static java.lang.System.out;

/**
 * Simple demonstration of Guava's Strings class.
 * 
 * @author Dustin
 */
public class GuavaStrings
{
   /**
    * Print to standard output a string message indicating whether the provided
    * String is null or empty or not null or empty. This method uses Guava's
    * Strings.isNullOrEmpty(String) method.
    * 
    * @param string String to be tested for null or empty.
    */
   private static void printStringStatusNullOrEmpty(final String string)
   {
      out.println(  "String '" + string + "' "
                  + (Strings.isNullOrEmpty(string) ? "IS" : "is NOT")
                  + " null or empty.");
   }

   /**
    * Demonstrate Guava Strings.isNullOrEmpty(String) method on some example
    * Strings.
    */
   public static void demoIsNullOrEmpty()
   {
      printHeader("Strings.isNullOrEmpty(String)");
      printStringStatusNullOrEmpty("Dustin");
      printStringStatusNullOrEmpty(null);
      printStringStatusNullOrEmpty("");
   }

   /**
    * Print to standard output a simple message indicating the provided original
    * String and the provided result/output String.
    * 
    * @param originalString Original String.
    * @param resultString Output or result String created by operation.
    * @param operation The operation that acted upon the originalString to create
    *    the resultString.
    */
   private static void printOriginalAndResultStrings(
      final String originalString, final String resultString, final String operation)
   {
      out.println("Passing '" + originalString + "' to " + operation + " produces '" + resultString + "'");
   }

   /** Demonstrate Guava Strings.emptyToNull() method on example Strings. */
   public static void demoEmptyToNull()
   {
      final String operation = "Strings.emptyToNull(String)";
      printHeader(operation);
      printOriginalAndResultStrings("Dustin", Strings.emptyToNull("Dustin"), operation);
      printOriginalAndResultStrings(null, Strings.emptyToNull(null), operation);
      printOriginalAndResultStrings("", Strings.emptyToNull(""), operation);
   }

   /** Demonstrate Guava Strings.nullToEmpty() method on example Strings. */
   public static void demoNullToEmpty()
   {
      final String operation = "Strings.nullToEmpty(String)";
      printHeader(operation);
      printOriginalAndResultStrings("Dustin", Strings.nullToEmpty("Dustin"), operation);
      printOriginalAndResultStrings(null, Strings.nullToEmpty(null), operation);
      printOriginalAndResultStrings("", Strings.nullToEmpty(""), operation);
   }

   /**
    * Demonstrate Guava Strings.padStart(String,int,char) method on example
    * Strings.
    */
   public static void demoPadStart()
   {
      final String operation = "Strings.padStart(String,int,char)";
      printHeader(operation);
      printOriginalAndResultStrings("Dustin", Strings.padStart("Dustin", 10, '_'), operation);
      /* Do NOT call Strings.padStart(String,int,char) on a null String:
       *    Exception in thread "main" java.lang.NullPointerException
     *         at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:187)
     *         at com.google.common.base.Strings.padStart(Strings.java:97)
       */
      //printOriginalAndResultStrings(null, Strings.padStart(null, 10, '_'), operation);
      printOriginalAndResultStrings("", Strings.padStart("", 10, '_'), operation);      
   }

   /**
    * Demonstrate Guava Strings.padEnd(String,int,char) method on example
    * Strings.
    */
   public static void demoPadEnd()
   {
      final String operation = "Strings.padEnd(String,int,char)";
      printHeader(operation);
      printOriginalAndResultStrings("Dustin", Strings.padEnd("Dustin", 10, '_'), operation);
      /*
       * Do NOT call Strings.padEnd(String,int,char) on a null String:
       *    Exception in thread "main" java.lang.NullPointerException
     *         at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:187)
     *         at com.google.common.base.Strings.padEnd(Strings.java:129)
       */
      //printOriginalAndResultStrings(null, Strings.padEnd(null, 10, '_'), operation);
      printOriginalAndResultStrings("", Strings.padEnd("", 10, '_'), operation);       
   }

   /** Demonstrate Guava Strings.repeat(String,int) method on example Strings. */
   public static void demoRepeat()
   {
      final String operation = "Strings.repeat(String,int)";
      printHeader(operation);
      printOriginalAndResultStrings("Dustin", Strings.repeat("Dustin", 3), operation);
      /*
       * Do NOT call Strings.repeat(String,int) on a null String:
       *    Exception in thread "main" java.lang.NullPointerException
     *         at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:187)
       *         at com.google.common.base.Strings.repeat(Strings.java:153)
       */
      //printOriginalAndResultStrings(null, Strings.repeat(null, 3), operation);
      printOriginalAndResultStrings("", Strings.repeat("", 3), operation);
   }

   /**
    * Print a separation header to standard output.
    * 
    * @param headerText Text to be placed in separation header.
    */
   public static void printHeader(final String headerText)
   {
      out.println("\n=========================================================");
      out.println("= " + headerText);
      out.println("=========================================================");
   }

   /**
    * Main function for demonstrating Guava's Strings class.
    * 
    * @param arguments Command-line arguments: none anticipated.
    */
   public static void main(final String[] arguments)
   {
      demoIsNullOrEmpty();
      demoEmptyToNull();
      demoNullToEmpty();
      demoPadStart();
      demoPadEnd();
      demoRepeat();
   }
}

The methods for padding and for repeating Strings do not take kindly to null Strings being passed to them. Indeed, passing a null to these three methods leads to NullPointerExceptions being thrown. Interestingly, these are more examples of Guava using the Preconditions class in its own code.

Conclusion

Many Java libraries and frameworks provide String manipulation functionality is classes with names like StringUtil. Guava's Strings class is one such example and the methods it supplies can make Java manipulation of Strings easier and more concise. Indeed, as I use Guava's Strings's methods, I feel almost like I'm using some of Groovy's GDK String goodness.