Friday, November 24, 2017

Too Many PreparedStatement Placeholders in Oracle JDBC

There are multiple causes of the ORA-01745 ("invalid host/bind variable name error") error when using an Oracle database. The Oracle 9i documentation on errors ORA-01500 through ORA-02098 provides more details regarding ORA-01745. It states that the "Cause" is "A colon in a bind variable or INTO specification was followed by an inappropriate name, perhaps a reserved word." It also states that the "Action" is "Change the variable name and retry the operation." In the same Oracle 12g documentation, however, there is no description of "cause" or "action" for ORA-01745, presumably because there are multiple causes and multiple corresponding actions associated with this message. In this post, I will focus on one of the perhaps less obvious causes and the corresponding action for that cause.

Some of the common causes for ORA-01745 that I will NOT be focusing on in this post include using an Oracle database reserved name (reserved word) as an identifier, extraneous or missing colon or comma, or attempting to bind structure names (rather than variables) to the placeholders.

In addition to the causes just listed and likely in addition to other potential causes of ORA-01745, another situation that can cause the ORA-01745 error is using too many ? placeholders in a JDBC PreparedStatement with the Oracle database. I will demonstrate in this post that the number of ? placeholders in a PreparedStatement that cause this ORA-01745 is 65536 (216).

I have blogged previously on the ORA-01795 error that occurs when one attempts to include more than 1000 values in an Oracle SQL IN condition. There are multiple ways to deal with this limitation and one of the alternative approaches might be to use multiple ORs to "OR" together more than 1000 values. This will typically be implemented with a PreparedStatement and with a ? placeholder placed in the SQL statement for each value being OR-ed. This PreparedStatement-based alternate approach employing ? placeholders will only work as long as the number of vales being OR-ed together is smaller than 65536.

The code listing that follows demonstrates how a SQL query against the Oracle HR schema can be generated to make it easy to reproduce the ORA-01745 error with too many ? placeholders (full code listing is available on GitHub).

Building Up Prepared Statement with Specified Number of ? Placeholders

/**
 * Constructs a query using '?' for placeholders and using
 * as many of these as specified with the int parameter.
 *
 * @param numberPlaceholders Number of placeholders ('?')
 *    to include in WHERE clause of constructed query.
 * @return SQL Query that has provided number of '?" placeholders.
 */
private String buildQuery(final int numberPlaceholders)
{
   final StringBuilder builder = new StringBuilder();
   builder.append("SELECT region_id FROM countries WHERE ");
   for (int count=0; count < numberPlaceholders-1; count++)
   {
      builder.append("region_id = ? OR ");
   }
   builder.append("region_id = ?");
   return builder.toString();
}

The next code listing demonstrates building a PreparedStatement based on the query constructed in the last code listing and setting its placeholders with a number of consecutive integers that match the number of ? placeholders.

Configuring PreparedStatement's ? Placeholders

/**
 * Execute the provided query and populate a PreparedStatement
 * wrapping this query with the number of integers provided
 * as the second method argument.
 * 
 * @param query Query to be executed.
 * @param numberValues Number of placeholders to be set in the
 *    instance of {@code PreparedStatement} used to execute the
 *    provided query.
 */
private void executeQuery(final String query, final int numberValues)
{
   try (final Connection connection = getDatabaseConnection();
        final PreparedStatement statement = connection.prepareStatement(query))
   {
      for (int count = 0; count < numberValues; count++)
      {
         statement.setInt(count+1, count+1);
      }
      final ResultSet rs = statement.executeQuery();
      while (rs.next())
      {
         out.println("Region ID: " + rs.getLong(1));
      }
   }
   catch (SQLException sqlException)
   {
      out.println("ERROR: Unable to execute query - " + sqlException);
   }
}

The next screen snapshot shows the ORA-01745 error occurring when the number of ? placeholders applied is 65536.

This example shows that there is a maximum number of ? placeholders that can be used in an Oracle SQL statement. Fortunately, there are other ways to accomplish this type of functionality that do not have this ORA-01475 limit of 65536 ? placeholders or the 1000 IN elements limit that causes an ORA-01795 error

Wednesday, November 22, 2017

Log Unexpected Switch Options

There are many things a Java developer can do to make his or her own life and the lives of others maintaining that code easier. In this post, I'm going to look at a very easy approach a developer can take to make things easier for everyone. The point of this post will likely seem obvious to everyone reading it, but I see this not done far more often than I would have expected. In short, developers should typically log the value they are switch-ing on when that value is not represented by any of the explicit case statements within that switch.

Before moving to specifics, I will add a few caveats. There are times when it may not make sense to log the value being switch-ed on that was not explicitly matched to a case. Some of these are listed here.

  • The value being switched on is sensitive and should not be logged for security reasons.
  • The value being switched on has numerous cases in which no match is expected and so the developer doesn't want to log unnecessarily.
  • A default can be provided that will always work well for any values that don't have matching case blocks (this seems rare).

In the cases I've seen that are the cause of this being one of my main pet peeves, none of the above caveats applied. In fact, in most of these cases, the developer has provided a logged message in the default block warning that the value was unexpected, but that same developer has failed to provide the candidate value that was not matched. A contrived example of this is shown in the next code listing.

Enum default That Logs Without switch Candidate Value

/**
 * Provides the Roman numeral equivalent of the
 * provided integer.
 * 
 * @param integer Integer for which Roman numeral
 *    equivalent is desired.
 * @return Roman numeral equivalent of the provided
 *    integer or empty string ("") if I'm not aware of
 *    the Roman numeral equivalent.
 */
public String getRomanNumeralEquivalent(final int integer)
{
   String romanNumeral;
   switch (integer)
   {
      case 0:
         romanNumeral = "nulla";
         break;
      case 1:
         romanNumeral = "I";
         break;
      case 2:
         romanNumeral = "II";
         break;
      case 3:
         romanNumeral = "III";
         break;
      case 4:
         romanNumeral = "IV";
         break;
      case 5:
         romanNumeral = "V";
         break;
      case 6:
         romanNumeral = "VI";
         break;
      case 7:
         romanNumeral = "VII";
         break;
      case 8:
         romanNumeral = "VIII";
         break;
      case 9:
         romanNumeral = "IX";
         break;
      case 10:
         romanNumeral = "X";
         break;
      default:
         out.println("Unexpected integer was provided.");
         romanNumeral = "";
         break;
   }
   return romanNumeral;
}

The issue here is really a specific example of a more general issue developers should avoid: logging without sufficient context. In some cases, it might be difficult or computationally expensive to provide the type of context that makes log messages more useful. That's typically not the case with switch statements, however, where we can easily log the value that we were trying to switch on. In the above code listing, developers supporting runtime issues in deployment will only be told that an "Unexpected integer was provided." Without any context, it's difficult to know what that provided integer was and, without knowing the candidate integer, it's difficult to trace what happened or even reproduce it.

Only very small effort is required to make this default logging statement useful and this is shown in the next code listing.

Constructing a Better default Log Statement

default:
   out.println("Unexpected integer (" + integer
      + ") was provided, so empty String being returned for Roman Numeral.");
   romanNumeral = "";

The "enhanced" log message indicates which integer was being switched on and adds what is being returned because of that not being an expected integer. The second part is not as necessary for the developer because the static code will show the developer what is returned in that "default" case. However, the logging of the integer that was being switched on is highly valuable because there's no good way to access this information later unless a different log message somewhere else made it clear that's what was being switched on.

I have been the victim numerous times of developers not providing this simple context. It has made what would have likely been an easy diagnosis much more difficult. In extreme cases, I've had to add this context to the log message and wait for it to be encountered again. Had the developer added that simple context information at the time of writing of the code, the issue could have been resolved much more readily.

I like to take this concept a little further when writing my own switch statements. I typically add a default block even when my switch covers all possible (current) cases explicitly. This default block is unnecessary at the time of writing and will "never be called," but I add it to future-proof the switch statement (unit tests can be used to implement similar protections). I add the logging of the unexpected candidate value provided to the switch statement so that if another case is added "upstream" in the code, my switch will quickly tell me when it runs into an unexpected value and tell me what that unexpected value is.

It often turns out that having a candidate value for a switch statement without matching case is an exceptional circumstance. In such cases, it's likely more appropriate to thrown an exception than to simply log the exceptional situation. A standard exception such as IllegalArgumentException works well for this (it is, in a way, an illegal argument to the switch statement), but I have occasionally also written a custom exception to help with this. When I've decided to implement and use this custom exception, part of the reason for making that decision is that throwing of that exception encourages developers to provide the object being switched upon as part of the exception's constructor. A representative example of this type of custom exception is shown next (source code is also available on GitHub).

SwitchOptionNotExpectedException.java

package dustin.utilities.exceptions;

/**
 * Exception used to communicate a candidate value for
 * a {@code switch} statement not being matched by any
 * of the explicitly provided {@code case} blocks.
 */
public class SwitchOptionNotExpectedException extends RuntimeException
{
   /**
    * Object being switched on for which no matching
    * {@code case} clause existed.
    */
   private final Object switchedObject;

   /**
    * Constructor accepting exception message and the instance
    * upon which the {@code switch} was being attempted when no
    * matching {@code case} was found.
    *
    * @param newMessage Exception summary message.
    * @param newSwitchedObject Object being switched on for
    *    which there was no explicitly specifed {@code case}.
    */
   public SwitchOptionNotExpectedException(
      final String newMessage, final Object newSwitchedObject)
   {
      super(newMessage + " (unable to switch on '" + String.valueOf(newSwitchedObject) + "')");
      switchedObject = newSwitchedObject;
   }

   /**
    * Constructor accepting the instance upon which the {@code switch}
    * was being attempted when no matching {@code case} was found.
    *
    * @param newSwitchedObject Object being switched on for
    *    which there was no explicitly specified {@code case}.
    */
   public SwitchOptionNotExpectedException(final Object newSwitchedObject)
   {
      super(
           "Switch statement did not expect '" + String.valueOf(newSwitchedObject)
         + "'.");
      switchedObject = newSwitchedObject;
   }

   /**
    * Provides String representation of the object being
    * switched upon.
    *
    * @return String representation of the object being
    *    switched upon.
    */
   public String getSwitchedObjectString()
   {
      return String.valueOf(switchedObject);
   }

   /**
    * Provides type of object being switched upon.
    *
    * @return Type of the object being switched upon or
    *    {@code null} if that switched upon object is null.
    */
   public Class getSwitchedObjectType()
   {
      return switchedObject != null ? switchedObject.getClass() : null;
   }
}

Whether the developer is simply logging the switch candidate not being found or throws an exception is response to that, the value being switched on should typically be logged or included in the exception to make it easier to diagnose the issue. The custom exception above will provide that message automatically regardless of the constructor used as long as the developer provides the switched-on object. A developer would have to go out of his or her way to not provide that object in this case rather than simply neglecting or forgetting to include it.

After ruling out cases where it's not appropriate to log or write out the value being switched on that has no match, the mostly likely reason a developer fails to indicate the value is simply not thinking about it. It can be "obvious" to the developer at time of writing the code that any unexpected case "will never happen" or that it would be obvious what the value was if it did happen. Another likely reason for not including context in these types of messages (or any log messages for that matter) is being rushed or being lazy. A developer might know that it would be best to provide these details, but doesn't want to take the time to do it. It's this latter reason that sometimes encourages me to write a custom exception like that shown above.

Debugging and maintaining production software is a valuable experience for developers because it helps them to better understand how their actions (or lack thereof) make others' jobs more difficult in the future. In general, the conscientious developer can help others (and possibly himself or herself) by providing context information in logged messages, especially for warning, error, and exception conditions. In particular, adding the context of what value was being switch-ed on when no match is found is easy to do and could save yourself, other developers, and customers quite a bit of time in the future.

Tuesday, November 21, 2017

Third Edition of Effective Java Coming Soon

With the significant changes that have been made to the Java programming language since Java 6 (for which the Second Edition of Effective Java was published), Java developers have frequently asked about a third edition of the well-known and often-cited Java book Effective Java. When the forthcoming 3rd Edition of Effective Java was announced, there was a rush to pre-order it; as of this writing, it's the #1 New Release in the Java Programming category (it's also #7 overall in Books|Computers and Technology|Programming|Languages and Tools|Java and #49 in Books|Textbooks|Computer Science|Programming Languages). According to its Amazon page and its Pearson Store page, Effective Java, Third Edition, is expected to be released in late December of this year (December 19 advertised on Pearson page and December 29 advertised on Amazon.com page).

The third edition is anticipated to have significant new and updated content given the changes to the language with Java 7, Java 8, and Java 9. The Table of Contents available on the Pearson page does not yet show the individual items in each chapter, but it does show that the third edition of Effective Java will have an entire new chapter on and called "Lambdas and Streams." This new chapter will be the seventh of twelve chapters in the book and will immediately follow the two chapters on significant features introduced in Java 5 (enums, annotations, and generics).

I imagine there will be new items and updated items in the other eleven chapters that previously existed in the first two editions as later versions of Java will impact many of those chapters. In fact, both the Pearson page and the Amazon page provide the same list of some of the "new coverage" items in this book and many of these will likely fall into one of the chapters other than the one on lambdas and streams.

I have never purchased three editions of the same book before and the ~$50 USD is not cheap, but I'll likely be purchasing the third edition of Effective Java as a Christmas gift for myself.

Humble Book Bundle: Java - A Great Deal for Java Developers

The Humble Book Bundle: Java is presented by O'Reilly Media and is available for a little less than two weeks from publication of this post. Purchasers of the bundle can select which "level" of bundle they'd like to purchase. The smallest bundle requires a minimum payment of $1, the middle bundle requires a minimum payment of $8, and the largest bundle requires a minimum payment of $15 (all currencies expressed here in U.S. dollars). These bundles are of Java-oriented electronic books published by O'Reilly and include some impressive titles. Portions of the proceeds go to charity, the electronic books are DRM-free, and the electronic books are available in multiple formats (PDF, EPUB, MOBI).

Tier 1 Bundle ($1 Minimum)

The first tier bundle can be acquired for as little as one dollar and includes the five books Think Java, Java Generics and Collections, Java in a Nutshell, 6th Edition (covers Java 8), Java Pocket Guide, 4th Edition (covers Java 8 and 9), and Java Message Service, 2nd Edition (covers JMS 1.1).

Tier 2 Bundle ($8 Minimum)

For at least $8, the second tier adds the following books to the books available in the first tier: Learning Java, 4th Edition (covers Java 7), Java Threads, Third Edition, Java 8 Lambdas, RESTful Java with JAX-RS 2.0, Second Edition, and Minecraft Modding with Forge.

Tier 3 Bundle ($15 Minimum)

For a minimum payment of $15, all three tiers are "unlocked," providing access to the ten electronic books associated with the first two tiers and with these five electronic books added: Java Network Programming, 4th Edition, Java Web Services: Up And Running, 2nd Edition, Java Cookbook, Third Edition (covers Java 8), Java Performance: The Definitive Guide, and Jenkins: The Definitive Guide.

I already have the printed editions of some of these books and they are excellent. There is also a nice mix of books here with some designed for beginners (such as Think Java and Learning Java), some designed for more experienced Java developers (such as Java Generics and Collections and Java Performance: The Definitive Guide), some designed for general Java (such as Java Cookbook and Java in a Nutshell) and some designed with focus on recent Java (such as Java 8 Lambdas).

It could be argued that any one or two of these electronic books are worth $15 USD, but getting all 15 electronic books for that price is a great deal. When one considers that these electronic books are DRM-free and come in multiple formats and that a portion of the proceeds goes to charity, it's even better. This Humble Book Bundle: Java is a good opportunity to acquire these titles.

Monday, November 20, 2017

Simple String Representation of Java Decimal Numbers without Scientific Notation

The primary types/objects used for decimal numbers in Java are float/Float, double/Double, and BigDecimal. Each of these has cases in which its "default" string representation is "computerized scientific notation." This post demonstrates some simple approaches to provide a string representation of the decimal number in these cases without scientific notation.

Examples in this post will demonstrate the "default" scientific notation String representations of these Java numeric types using a range of numbers for each type that demonstrate approximately where the "default" representation for each type becomes scientific notation. The next three code listings show the code for constructing general ranges for floats, doubles, and BigDecimals. The full source code listing for these examples is available on GitHub.

Constructing the Example Range of Floats

/**
 * Writes floats in the provided format and in the
 * provided range to standard output.
 *
 * @param start Float to start writing.
 * @param threshold Float past which to not write anymore.
 * @param delta Delta for each increment of floats to be written.
 * @param label Label for header.
 * @param format Format for print out.
 */
private static void writeFloatsToOutput(
   final float start,
   final float threshold,
   final float delta,
   final String label,
   final Format format)
{
   out.println(generateHeader(label));
   float floatValue = start;
   do
   {
      out.println("= " + format.fromFloat(floatValue));
      floatValue += delta;
   }
   while (floatValue < threshold);
}

Constructing the Example Range of Doubles

/**
 * Writes doubles in the provided format and in the
 * provided range to standard output.
 *
 * @param start Double to start writing.
 * @param threshold Double past which to not write anymore.
 * @param delta Delta for each increment of doubles to be written.
 * @param label Label for header.
 * @param format Format for print out.
 */
private static void writeDoublesToOutput(
   final double start,
   final double threshold,
   final double delta,
   final String label,
   final Format format)
{
   out.println(generateHeader(label));
   double doubleValue = start;
   do
   {
      out.println("= " + format.fromDouble(doubleValue));
      doubleValue += delta;
   }
   while (doubleValue < threshold);
}

Constructing the Example Range of BigDecimals

/**
 * Writes BigDecimals in the provided format and in the
 * provided range to standard output.
 *
 * @param start BigDecimal to start writing.
 * @param threshold BigDecimal past which to not write anymore.
 * @param delta Delta for each increment of BigDecimals to be written.
 * @param label Label for header.
 * @param format Format for print out.
 */
private static void writeBigDecimalsToOutput(
   final BigDecimal start,
   final BigDecimal threshold,
   final BigDecimal delta,
   final String label,
   final Format format)
{
   out.println(generateHeader(label));
   BigDecimal decimal = start;
   do
   {
      out.println("= " + format.fromBigDecimal(decimal));
      decimal = decimal.add(delta);
   }
   while (decimal.compareTo(threshold) < 0);
}

The three methods shown above can be called with ranges specified to demonstrate when scientific notation is automatically employed for String representations of the Java decimal types. The output from running the above with "default" format for each numeric type is shown in the next three output listings.

The default representation of very small and very large floats does include scientific notation for the smallest numbers shown and for the largest numbers shown. These numbers demonstrate what is discussed in the Float.toString(Float) documentation: numbers "less than 10-3 or greater than or equal to 107" are "represented in so-called 'computerized scientific notation.'"

==========================
= Small Floats (DEFAULT) =
==========================
= 8.5E-4
= 9.5E-4
= 0.00105
= 0.0011499999
= 0.0012499999
= 0.0013499998
= 0.0014499997
= 0.0015499997
= 0.0016499996
= 0.0017499996
= 0.0018499995
= 0.0019499995
==========================
= Large Floats (DEFAULT) =
==========================
= 9999995.0
= 9999996.0
= 9999997.0
= 9999998.0
= 9999999.0
= 1.0E7
= 1.0000001E7
= 1.0000002E7
= 1.0000003E7
= 1.0000004E7

The default representation of very small and very large doubles does include scientific notation for the smallest numbers shown and for the largest numbers shown. These numbers demonstrate what is discussed in the Javadoc documentation for Double.toString(double): numbers "less than 10-3 or greater than or equal to 107" are "represented in so-called 'computerized scientific notation.'"

===========================
= Small Doubles (DEFAULT) =
===========================
= 8.5E-4
= 9.5E-4
= 0.00105
= 0.00115
= 0.00125
= 0.00135
= 0.0014500000000000001
= 0.0015500000000000002
= 0.0016500000000000002
= 0.0017500000000000003
= 0.0018500000000000003
= 0.0019500000000000003
===========================
= Large Doubles (DEFAULT) =
===========================
= 9999995.0
= 9999996.0
= 9999997.0
= 9999998.0
= 9999999.0
= 1.0E7
= 1.0000001E7
= 1.0000002E7
= 1.0000003E7
= 1.0000004E7

While float and double had their smallest and largest numbers expressed in scientific notation, BigDecimal only does this by default for smaller numbers. This is described in the BigDecimal.toString() Javadoc documentation: "If the scale is greater than or equal to zero and the adjusted exponent is greater than or equal to -6, the number will be converted to a character form without using exponential notation. ... if ... the adjusted exponent is less than -6, the number will be converted to a character form using exponential notation."

===============================
= Small BigDecimals (DEFAULT) =
===============================
= 8.5E-7
= 9.5E-7
= 0.00000105
= 0.00000115
= 0.00000125
= 0.00000135
= 0.00000145
= 0.00000155
= 0.00000165
= 0.00000175
= 0.00000185
= 0.00000195
===============================
= Large BigDecimals (DEFAULT) =
===============================
= 99999950000000000000000000000000000000000000000000
= 99999960000000000000000000000000000000000000000000
= 99999970000000000000000000000000000000000000000000
= 99999980000000000000000000000000000000000000000000
= 99999990000000000000000000000000000000000000000000
= 100000000000000000000000000000000000000000000000000
= 100000010000000000000000000000000000000000000000000
= 100000020000000000000000000000000000000000000000000
= 100000030000000000000000000000000000000000000000000
= 100000040000000000000000000000000000000000000000000
private static void writeFormattedValues(final Format format)
{
   writeFloatsToOutput(
      0.00085f, 0.002f, 0.0001f, "Small Floats (" + format + ")", format);
   writeFloatsToOutput(
      9_999_995f, 10_000_005f, 1f, "Large Floats (" + format + ")", format);

   writeDoublesToOutput(
      0.00085d, 0.002d, 0.0001d, "Small Doubles (" + format + ")", format);
   writeDoublesToOutput(
      9_999_995d, 10_000_005d, 1d, "Large Doubles (" + format + ")", format);

   writeBigDecimalsToOutput(
      new BigDecimal("0.00000085"),
      new BigDecimal("0.000002"),
      new BigDecimal("0.0000001"),
      "Small BigDecimals (" + format + ")",
      format);
   writeBigDecimalsToOutput(
      new BigDecimal("99999950000000000000000000000000000000000000000000"),
      new BigDecimal("100000050000000000000000000000000000000000000000000"),
      new BigDecimal("10000000000000000000000000000000000000000000"),
      "Large BigDecimals (" + format + ")",
      format);
}

The representation of very small and very large numbers in the code above can be presented in default format or in a format the precludes use of scientific notation. The code listing for the Format enum is shown next and this enum demonstrates approaches that can be used with float, double, and BigDecimal to render them without scientific notation.

Format.java

/**
 * Supports rendering of Java numeric types float, double,
 * and BigDecimal in "default" format and in format that
 * avoids use of scientific notation.
 */
public enum Format
{
   DEFAULT
   {
      @Override
      public String fromFloat(final float floatValue)
      {
         return String.valueOf(floatValue);
      }
      @Override
      public String fromDouble(final double doubleValue)
      {
         return String.valueOf(doubleValue);
      }
      @Override
      public String fromBigDecimal(final BigDecimal bigDecimalValue)
      {
         return bigDecimalValue.toString();
      }
   },
   NO_EXPONENT
   {
      @Override
      public String fromFloat(final float floatValue)
      {
         return getFormatter().format(floatValue);
      }
      @Override
      public String fromDouble(final double doubleValue)
      {
         return getFormatter().format(doubleValue);
      }
      @Override
      public String fromBigDecimal(final BigDecimal bigDecimalValue)
      {
         return bigDecimalValue.toPlainString();
      }
   };

   /**
    * Present provided float as a String formatted per my format.
    *
    * @param floatValue Float-typed number to be rendered.
    */
   public abstract String fromFloat(final float floatValue);
   /**
    * Present provided double as a String formatted per my format.
    *
    * @param doubleValue Double-typed number to be rendered.
    */
   public abstract String fromDouble(final double doubleValue);
   /**
    * Present provided BigDecimal as a String formatted per my format.
    *
    * @param bigDecimalValue BigDecimal object to be rendered.
    */
   public abstract String fromBigDecimal(final BigDecimal bigDecimalValue);

   /**
    * Provide an instance of {@code NumberFormat} configured to
    * display the maximum fractional digits and to not group.
    *
    * @return Instance of {@code NumberFormat} to be used to provide
    *    a representation of the decimal number without scientific notation.
    */
   private static NumberFormat getFormatter()
   {
      // Used to prevent numbers from being represented in scientific notation.
      final NumberFormat numberFormat = NumberFormat.getInstance();
      numberFormat.setMaximumFractionDigits(Integer.MAX_VALUE);
      numberFormat.setGroupingUsed(false);
      return numberFormat;
   }
}

The Format enum uses an instance of NumberFormat with grouping disabled and with the maximum fraction digits set to Integer.MAX_VALUE to ensure that floats and doubles are rendered without scientific notation. It's even easier to accomplish this with BigDecimal using its toPlainString() method.

The output from running the code with the Format.NO_EXPONENT is shown next (and there's no exponents or scientific notation in sight).

==============================
= Small Floats (NO_EXPONENT) =
==============================
= 0.0008500000112690032
= 0.0009500000160187483
= 0.0010499999625608325
= 0.0011499999091029167
= 0.001249999855645001
= 0.0013499998021870852
= 0.0014499997487291694
= 0.0015499996952712536
= 0.0016499996418133378
= 0.001749999588355422
= 0.0018499995348975062
= 0.0019499994814395905
==============================
= Large Floats (NO_EXPONENT) =
==============================
= 9999995
= 9999996
= 9999997
= 9999998
= 9999999
= 10000000
= 10000001
= 10000002
= 10000003
= 10000004
===============================
= Small Doubles (NO_EXPONENT) =
===============================
= 0.00085
= 0.00095
= 0.00105
= 0.00115
= 0.00125
= 0.00135
= 0.0014500000000000001
= 0.0015500000000000002
= 0.0016500000000000002
= 0.0017500000000000003
= 0.0018500000000000003
= 0.0019500000000000003
===============================
= Large Doubles (NO_EXPONENT) =
===============================
= 9999995
= 9999996
= 9999997
= 9999998
= 9999999
= 10000000
= 10000001
= 10000002
= 10000003
= 10000004
===================================
= Small BigDecimals (NO_EXPONENT) =
===================================
= 0.00000085
= 0.00000095
= 0.00000105
= 0.00000115
= 0.00000125
= 0.00000135
= 0.00000145
= 0.00000155
= 0.00000165
= 0.00000175
= 0.00000185
= 0.00000195
===================================
= Large BigDecimals (NO_EXPONENT) =
===================================
= 99999950000000000000000000000000000000000000000000
= 99999960000000000000000000000000000000000000000000
= 99999970000000000000000000000000000000000000000000
= 99999980000000000000000000000000000000000000000000
= 99999990000000000000000000000000000000000000000000
= 100000000000000000000000000000000000000000000000000
= 100000010000000000000000000000000000000000000000000
= 100000020000000000000000000000000000000000000000000
= 100000030000000000000000000000000000000000000000000
= 100000040000000000000000000000000000000000000000000

The standard Java floating types and BigDecimal class render some numbers in scientific notation, but it's easy to ensure that this default presentation of scientific notation is not used when it is not desired.

Saturday, November 4, 2017

Java Command-Line Interfaces (Part 30): Observations

This series on parsing command line arguments in Java has consisted of 29 posts published over four months and covering 28 distinct open source libraries available for parsing command line arguments in Java. This post collects some observations that can be made from the first 29 posts in this series and provides some general considerations to make when selecting one of the 28 libraries or deciding to roll one's own command-line argument parsing code. Although no one library will be the best fit for every situation, this post will also look at how some libraries may be a better fit than others for specific situations. The post will end with a subset of the original 28 libraries that may be the most generally appealing of the covered libraries based on some criteria covered in the post.

General Observations

There are several observations that can be made after looking at the 28 libraries covered in this series on parsing command line arguments in Java.

  • For most Java developers in most situations, there appears to be very little reason to write custom command line parsing code.
  • The plethora of Java-based libraries for parsing command line arguments in indicative of the vastness of the Java ecosystem.
  • The fact that all 28 covered libraries are open source is a reminder of how fundamental open source is in the Java culture.
  • There are some interesting differences between the libraries covered in this series and the various different approaches are a reminder that there's often more than one way to implement even relatively minor functionality in Java.
  • The large number of libraries for parsing command line arguments in Java, many of which are associated with author statements saying something about the existing libraries not satisfying their needs, is evidence that it's unlikely there will ever be a single language, framework, or library that will be "best" to everyone. If something as simple as a command line parsing library cannot be written to be everyone's favorite, it seems impossible to ever have a larger library, a framework, or a programming language be everyone's favorite. "One size doesn't fit all" when it comes to libraries, frameworks, and programming languages.
  • It's not just technical strength that must be considered when evaluating and selecting a library; its license, distribution mechanism, currency, provider support, and community support also all weigh in on the decision. Even the version of Java it will run on plays a role in the decision.

Evaluation Criteria

These are several criteria that may be important to a Java developer when selecting between so many libraries and when weighing whether to use a library or implement one's own command line argument functionality.

  • Is it open source?
    • My simple definition of open source in this context is "source code can be legally viewed by developers using the library." Wikipedia articulates a similar but slightly stricter definition, "[open source code] is source code [that is] made available with a license in which the copyright holder provides the rights to study, change, and distribute the software to anyone and for any purpose."
    • All 28 libraries covered in this series make source code available to developers using the library and so are "open source" by my simple definition and also generally meet the slightly stricter definition on Wikipedia.
  • What is its license?
    • The license under which each library is issued can be significant in determining whether to choose that library. Most users will be most comfortable with open source licenses that are clearly defined and that are most liberal in what they allow.
    • Many of the libraries covered in the series are released under liberal open source licenses, but some are released under less liberal licenses or do not have an explicitly specified license at all.
  • What is its size?
    • Use of a library typically means an additional JAR on the classpath and it may be important in some situations to keep the size of these additional libraries as small as possible for a particular deployment environment.
    • None of these command line parsing libraries are large when compared to libraries such as Spring and Hibernate, but the relative differences in size among these libraries can be large.
  • Are there third-party dependencies?
    • Third-party libraries add to the overall increase in library size and mean more dependencies to manage.
    • Most of the libraries covered in this series do not have additional dependencies, but some of them do.
  • What is the distribution mechanism?
    • Availability as a single JAR via Maven repository is probably the easiest mechanism for most Java developers to acquire a library.
    • There are JARs available in the Maven repository for many of the covered libraries, but some of the libraries require downloading the JAR from a project site or associated article site.
    • The 28 libraries covered in this series tend to be distributed via Maven repository, via project page download (GitHub, SourceForge, library author's site, etc.), and even copy-and-paste in a couple of cases where the "library is a single Java source code file.
  • Documentation
    • The libraries covered in this series are documented in a variety of ways including project documentation, Javadoc documentation, unit tests, and in-code comments.
    • Many of the libraries have the equivalent of a "Quick Start" tutorial, but some have relatively little documentation other than that. Some have no or very few Javadoc comments and others have significant Javadoc-based API documentation. Many of the libraries make their Javadoc-generated documentation available online, but some require downloading the library to see its Javadoc-based documentation.
  • Community
    • With open source projects, it's often advantageous to have a large community that uses the product because a large community means more implicit testing and potentially more blog posts, articles, and forum messages on how to use that project.
    • The sizes of the communities of the libraries covered in this series vary dramatically and it can be difficult to ascertain the size of any given community. However, the number of libraries dependent on a given library and the number of online resources talking about a given library give us an idea of community involvement.
  • Age of Library / Most Recent Update
    • Newer is not always better, but it generally is more compelling to use an open source product that receives current and recent updates than to use a product that has not been updated or changed in many years. It's a bit less of a concern with a small and simple library such as a command line parsing library, but currently supported libraries are still advantageous over potentially abandoned projects.
  • What features does it offer?
    • This is where the libraries covered in the series really differentiate themselves, but it's the criterion that is most difficult to compare between libraries as it really depends on which particular feature is desired.
    • Most of the covered libraries provided most of the features covered in the simple examples in this series. However, some of the libraries provided significant features that were beyond those used in each library's example.
    • For the simple examples used throughout this series, the ease of use of the API provided by the parsing library was probably as important of a feature as any.

The CLI Comparison page on the picocli GitHub page compares and contrasts many of the libraries covered in this series and some libraries not covered in this series. The page compares the libraries in table format by listing each library's respective attributes such as license, minimum Java version supported, style of API, and parsing options supported.

This series has covered 28 different libraries for parsing command line arguments from Java. It's impossible to designate any one of these as the "best" library for this purpose for all people in all situations. Each library is an investment of time and effort by its developer (or developers), but I attempt here to narrow down the list of libraries to the subset that I believe is most likely to appeal to general situations and developers.

Voted Most Likely to Succeed

The following libraries are listed in alphabetical order rather than in my order of preference.

  • Apache Commons CLI
    • In my opinion, Apache Commons CLI offers the least aesthetically appealing API of this narrowed down subset of recommended libraries.
    • Apache Commons CLI benefits from name recognition, from being frequently used by other libraries and products, and from being around for a long time.
      • In environments where it is difficult to justify installation of new libraries, there is better chance of having Apache Commons CLI already available than most of the other libraries.
    • Apache Commons CLI is built into Groovy and so is especially easy for someone to use moving between Groovy and Java.
    • Quality documentation.
    • The Apache License, Version 2, is a well-known, liberal, and corporation-friendly license.
  • args4j
    • args4j offers numerous features and is highly extensible.
    • Command-line arguments are typed.
    • Quality documentation.
    • args4j is currently supported by a familiar name in the open source Java community.
    • The MIT license is a well-known, liberal, and corporation-friendly license.
  • JCommander
    • API consists of easy-to-use combination of annotations and builders.
    • Command-line arguments are typed.
    • Quality documentation.
    • JCommander is currently supported by a familiar name in the open source Java community.
    • The Apache License, Version 2, is a well-known, liberal, and corporation-friendly license.
  • JewelCli
    • The annotated interface approach of JewelCli appeals to me.
    • Command-line arguments are typed.
    • Quality documentation.
    • The Apache License, Version 2, is a well-known, liberal, and corporation-friendly license.
  • picocli
    • Highly readable annotation-based API.
    • Quality documentation.
    • Command-line arguments are typed.
    • One of the more feature-rich libraries covered in this series.
    • Currently supported (has been enhanced with several new features since I started this series of posts).
    • The Apache License, Version 2, is a well-known, liberal, and corporation-friendly license.

Although I listed a subset of five libraries out of the 28 covered libraries, there are reasons that a developer might choose to use one of the 23 libraries not on this narrowed-down list. Several of the libraries not on this list offer unique features that, if important enough to the Java developer, would make those libraries preferable to the 5 listed above.

The next listing associates some of the covered libraries with some of their relatively unique strengths. One of these might be selected, even if it's not in the list of five I just highlighted, if it is something that it's particularly and uniquely strong in and is one of the most important considerations for the relevant application. Many of the listed "traits" are a matter of preference or taste, meaning a library having the listed trait may be seen as a positive by one developer and as a negative by another developer.

TraitDescription / BenefitLibraries with Desired Trait
Color Syntax Color syntax (select environments) picocli
Command Completion Autocompletion of commands (select environments) picocli
Configuration (Annotations) Uses annotations primarily to define command-line options. Airline 2
args4j
cli-parser
CmdOption
Commandline
google-options
jbock
JCommander
JewelCli
MarkUtils-CLI
picocli
Rop
Configuration (API) Uses programmatic APIs (traditional and/or builder) to define command-line options. Apache Commons CLI
Argparse4j
argparser
CmdLn
getopt4j
Jargo
JArgp
JArgs
JCLAP
jClap
JOpt Simple
JSAP
jw-options
parse-cmd
Configuration (Reflection) Uses reflection (but not annotations) to define command-line options. CLAJR
Configuration (XML) Uses or supports use of XML to define command-line options. JCommando
JSAP
Single File Source Enables easy inclusion of "library" in one's project as a source code file that is compiled rather than as a JAR that source is compiled against. CLAJR
picocli
Small JAR Libraries providing minimally required JAR of less than 25 KB in size (applies to version covered in this series). CLAJR
cli-parser
getopt4j
JArgp
JArgs
jClap
jw-options
Rop

There are numerous other characteristics that one might desire in a Java-based command-line parsing library that might narrow down the number of appropriate candidates. These include flexibility of command styles (long and/or short names, styles [GNU, POSIX, Java, etc.]), applicable license, availability of current support, new releases and updates, size of user community, and minimum version of Java that is supported. The tables provided in the previously referenced Java Command Line Parsers Comparison make it easy to compare some of these characteristics for most of the libraries covered in this series.

This series on parsing command line arguments with Java has demonstrated 28 libraries and there are several more publicly available libraries not yet covered in this series. With over 30 libraries available, most developers should be able to find an external library to meet one's needs.

Additional References