Friday, March 20, 2015

Displaying Paths in Ant

In the blog posts Java and Ant Properties Refresher and Ant <echoproperties /> Task, I wrote about how being able to see how properties are seen by an Ant build can be helpful in understanding that build better. It is often the case that it'd also be valuable to see various paths used in the build as the build sees them, especially if the paths are composed of other paths and pieces from other build files. Fortunately, as described in the StackOverflow thread Ant: how to echo class path variable to a file, this is easily done with Ant's PathConvert task.

The following XML snippet is a very simple Ant build file that demonstrates use of <pathconvert> to display an Ant path's contents via the normal mechanisms used to display Ant properties.

build-show-paths.xml: Ant build.xml Using pathconvert

<project name="ShowPaths" default="showPaths" basedir=".">

   <path id="classpath">
      <pathelement path="C:\groovy-2.4.0\lib"/>
      <pathelement location="C:\lib\tika-1.7\tika-app-1.7.jar"/>
   </path>
   
   <target name="showPaths">
      <pathconvert property="classpath.path" refid="classpath" />
      <echo message="classpath = ${classpath.path}" />
   </target>

</project>

The simple Ant build file example shown above creates an Ant path named "classpath". It then uses the pathconvert task to create a new property ("classpath.path") that holds the value held in the "classpath" path. With this done, the property "classpath.path" can have its value displayed using Ant's echo task as demonstrated in "Java and Ant Properties Refresher."

When debugging issues with Ant builds, use of Ant's -verbose is often handy. However, sometimes -verbose is a heavier solution than is actually required and often the simple ability to easily identify what properties and paths the Ant build "sees" can be very helpful in diagnosing build issues.

Thursday, March 19, 2015

Validating XML Against XSD(s) in Java

There are numerous tools available for validating an XML document against an XSD. These include operating system scripts and tools such as xmllint, XML editors and IDEs, and even online validators. I have found it useful to have my own easy-to-use XML validation tool because of limitations or issues of the previously mentioned approaches. Java makes it easy to write such a tool and this post demonstrates how easy it is to develop a simple XML validation tool in Java.

The Java tool developed in this post requires JDK 8. However, the simple Java application can be modified fairly easily to work with JDK 7 or even with a version of Java as old as JDK 5. In most cases, I have tried to comment the code that requires JDK 7 or JDK 8 to identify these dependencies and provide alternative approaches in earlier versions of Java. I have done this so that the tool can be adapted to work even in environments with older versions of Java.

The complete code listing for the Java-based XML validation tool discussed in this post is included at the end of the post. The most significant lines of code from that application when discussing validation of XML against one or more XSDs is shown next.

Essence of Validating XML Against XSD with Java
final Schema schema = schemaFactory.newSchema(xsdSources);
final Validator validator = schema.newValidator();
validator.validate(new StreamSource(new File(xmlFilePathAndName)));

The previous code listing shows the straightforward approach available in the standard JDK for validating XML against XSDs. An instance of javax.xml.validation.Schema is instantiated with a call to javax.xml.validation.SchemaFactory.newSchema(Source[]) (where the array of javax.xml.transform.Source objects represents one or more XSDs). An instance of javax.xml.validation.Validator is obtained from the Schema instance via Schema's newValidator() method. The XML to be validated can be passed to that Validator's validate(Source) method to perform the validation of the XML against the XSD or XSDs originally provided to the Schema object created with SchemaFactory.newSchema(Source[]).

The next code listing includes the code just highlighted but represents the entire method in which that code resides.

validateXmlAgainstXsds(String, String[])
/**
 * Validate provided XML against the provided XSD schema files.
 *
 * @param xmlFilePathAndName Path/name of XML file to be validated;
 *    should not be null or empty.
 * @param xsdFilesPathsAndNames XSDs against which to validate the XML;
 *    should not be null or empty.
 */
public static void validateXmlAgainstXsds(
   final String xmlFilePathAndName, final String[] xsdFilesPathsAndNames)
{
   if (xmlFilePathAndName == null || xmlFilePathAndName.isEmpty())
   {
      out.println("ERROR: Path/name of XML to be validated cannot be null.");
      return;
   }
   if (xsdFilesPathsAndNames == null || xsdFilesPathsAndNames.length < 1)
   {
      out.println("ERROR: At least one XSD must be provided to validate XML against.");
      return;
   }
   final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);

   final StreamSource[] xsdSources = generateStreamSourcesFromXsdPathsJdk8(xsdFilesPathsAndNames);

   try
   {
      final Schema schema = schemaFactory.newSchema(xsdSources);
      final Validator validator = schema.newValidator();
      out.println(  "Validating " + xmlFilePathAndName + " against XSDs "
                  + Arrays.toString(xsdFilesPathsAndNames) + "...");
      validator.validate(new StreamSource(new File(xmlFilePathAndName)));
   }
   catch (IOException | SAXException exception)  // JDK 7 multi-exception catch
   {
      out.println(
           "ERROR: Unable to validate " + xmlFilePathAndName
         + " against XSDs " + Arrays.toString(xsdFilesPathsAndNames)
         + " - " + exception);
   }
   out.println("Validation process completed.");
}

The code listing for the validateXmlAgainstXsds(String, String[]) method shows how a SchemaFactory instance can be obtained with the specified type of schema (XMLConstants.W3C_XML_SCHEMA_NS_URI). This method also handles the various types of exceptions that might be thrown during the validation process. As the comment in the code states, the JDK 7 language change supporting catching of multiple exceptions in a single catch clause is used in this method but could be replaced with separate catch clauses or catching of a single more general exception for code bases earlier than JDK 7.

The method just shown calls a method called generateStreamSourcesFromXsdPathsJdk8(String[]) and the next listing is of that invoked method.

generateStreamSourcesFromXsdPathsJdk8(String[])
/**
 * Generates array of StreamSource instances representing XSDs
 * associated with the file paths/names provided and use JDK 8
 * Stream API.
 *
 * This method can be commented out if using a version of
 * Java prior to JDK 8.
 *
 * @param xsdFilesPaths String representations of paths/names
 *    of XSD files.
 * @return StreamSource instances representing XSDs.
 */
private static StreamSource[] generateStreamSourcesFromXsdPathsJdk8(
   final String[] xsdFilesPaths)
{
   return Arrays.stream(xsdFilesPaths)
                .map(StreamSource::new)
                .collect(Collectors.toList())
                .toArray(new StreamSource[xsdFilesPaths.length]);
}

The method just shown uses JDK 8 stream support to convert the array of Strings representing paths/names of XSD files to instances of StreamSource based on the contents of the XSDs pointed to by the path/name Strings. In the class's complete code listing, there is also a deprecated method generateStreamSourcesFromXsdPathsJdk7(final String[]) that could be used instead of this method for code bases on a version of Java earlier than JDK 8.

This single-class Java application is most useful when it's executed from the command line. To enable this, a main function is defined as shown in the next code listing.

Executable main(String[]) Function
/**
 * Validates provided XML against provided XSD.
 *
 * @param arguments XML file to be validated (first argument) and
 *    XSD against which it should be validated (second and later
 *    arguments).
 */
public static void main(final String[] arguments)
{
   if (arguments.length < 2)
   {
      out.println("\nUSAGE: java XmlValidator <xmlFile> <xsdFile1> ... <xsdFileN>\n");
      out.println("\tOrder of XSDs can be significant (place XSDs that are");
      out.println("\tdependent on other XSDs after those they depend on)");
      System.exit(-1);
   }
   // Arrays.copyOfRange requires JDK 6; see
   // http://stackoverflow.com/questions/7970486/porting-arrays-copyofrange-from-java-6-to-java-5
   // for additional details for versions of Java prior to JDK 6.
   final String[] schemas = Arrays.copyOfRange(arguments, 1, arguments.length);
   validateXmlAgainstXsds(arguments[0], schemas);
}

The executable main(String[]) function prints a usage statement if fewer than two command line arguments are passed to it because it expects at least the name/path of the XML file to be validated and the name/path of an XSD to validate the XML against.

The main function takes the first command line argument and treats that as the XML file's path/name and then treats all remaining command lin arguments as the paths/names of one or more XSDs.

The simple Java tool for validating XML against one or more XSDs has now been shown (complete code listing is at bottom of post). With it in place, we can run it against an example XML file and associated XSDs. For this demonstration, I'm using a very simple manifestation of a Servlet 2.5 web.xml deployment descriptor.

Sample Valid Servlet 2.5 web.xml
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5"> 

    <display-name>Sample Java Servlet 2.5 Web Application</display-name>
</web-app>

The simple web.xml file just shown is valid per the Servlet 2.5 XSDs and the output of running this simple Java-based XSD validation tool prove that by not reporting any validation errors.

An XSD-valid XML file does not lead to very interesting results with this tool. The next code listing shows an intentionally invalid web.xml file that has a "title" element not specified in the associated Servlet 2.5 XSD. The output with the most significant portions of the error message highlighted is shown after the code listing.

Sample Invalid Servlet 2.5 web.xml (web-invalid.xml)
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <display-name>Java Servlet 2.5 Web Application</display-name>
    <title>A handy example</title>
</web-app>

As the last output shows, things are more interesting in terms of output when the provided XML is not XSD valid.

There is one important caveat I wish to emphasize here. The XSDs provided to this Java-based tool sometimes need to be specified in a particular order. In particular, XSDs with "include" dependencies on other XSDs should be listed on the command line AFTER the XSD they include. In other words, XSDs with no "include" dependencies will generally be provided on the command line before those XSDs that include them.

The next code listing is for the complete XmlValidator class.

XmlValidator.java (Complete Class Listing)
package dustin.examples.xmlvalidation;

import org.xml.sax.SAXException;

import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import static java.lang.System.out;

/**
 * Validate provided XML against the provided XSDs.
 */
public class XmlValidator
{
   /**
    * Validate provided XML against the provided XSD schema files.
    *
    * @param xmlFilePathAndName Path/name of XML file to be validated;
    *    should not be null or empty.
    * @param xsdFilesPathsAndNames XSDs against which to validate the XML;
    *    should not be null or empty.
    */
   public static void validateXmlAgainstXsds(
      final String xmlFilePathAndName, final String[] xsdFilesPathsAndNames)
   {
      if (xmlFilePathAndName == null || xmlFilePathAndName.isEmpty())
      {
         out.println("ERROR: Path/name of XML to be validated cannot be null.");
         return;
      }
      if (xsdFilesPathsAndNames == null || xsdFilesPathsAndNames.length < 1)
      {
         out.println("ERROR: At least one XSD must be provided to validate XML against.");
         return;
      }
      final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);

      final StreamSource[] xsdSources = generateStreamSourcesFromXsdPathsJdk8(xsdFilesPathsAndNames);

      try
      {
         final Schema schema = schemaFactory.newSchema(xsdSources);
         final Validator validator = schema.newValidator();
         out.println("Validating " + xmlFilePathAndName + " against XSDs "
            + Arrays.toString(xsdFilesPathsAndNames) + "...");
         validator.validate(new StreamSource(new File(xmlFilePathAndName)));
      }
      catch (IOException | SAXException exception)  // JDK 7 multi-exception catch
      {
         out.println(
            "ERROR: Unable to validate " + xmlFilePathAndName
            + " against XSDs " + Arrays.toString(xsdFilesPathsAndNames)
            + " - " + exception);
      }
      out.println("Validation process completed.");
   }

   /**
    * Generates array of StreamSource instances representing XSDs
    * associated with the file paths/names provided and use JDK 8
    * Stream API.
    *
    * This method can be commented out if using a version of
    * Java prior to JDK 8.
    *
    * @param xsdFilesPaths String representations of paths/names
    *    of XSD files.
    * @return StreamSource instances representing XSDs.
    */
   private static StreamSource[] generateStreamSourcesFromXsdPathsJdk8(
      final String[] xsdFilesPaths)
   {
      return Arrays.stream(xsdFilesPaths)
                   .map(StreamSource::new)
                   .collect(Collectors.toList())
                   .toArray(new StreamSource[xsdFilesPaths.length]);
   }

   /**
    * Generates array of StreamSource instances representing XSDs
    * associated with the file paths/names provided and uses
    * pre-JDK 8 Java APIs.
    *
    * This method can be commented out (or better yet, removed
    * altogether) if using JDK 8 or later.
    *
    * @param xsdFilesPaths String representations of paths/names
    *    of XSD files.
    * @return StreamSource instances representing XSDs.
    * @deprecated Use generateStreamSourcesFromXsdPathsJdk8 instead
    *    when JDK 8 or later is available.
    */
   @Deprecated
   private static StreamSource[] generateStreamSourcesFromXsdPathsJdk7(
      final String[] xsdFilesPaths)
   {
      // Diamond operator used here requires JDK 7; add type of
      // StreamSource to generic specification of ArrayList for
      // JDK 5 or JDK 6
      final List<StreamSource> streamSources = new ArrayList<>();
      for (final String xsdPath : xsdFilesPaths)
      {
         streamSources.add(new StreamSource(xsdPath));
      }
      return streamSources.toArray(new StreamSource[xsdFilesPaths.length]);
   }

   /**
    * Validates provided XML against provided XSD.
    *
    * @param arguments XML file to be validated (first argument) and
    *    XSD against which it should be validated (second and later
    *    arguments).
    */
   public static void main(final String[] arguments)
   {
      if (arguments.length < 2)
      {
         out.println("\nUSAGE: java XmlValidator <xmlFile> <xsdFile1> ... <xsdFileN>\n");
         out.println("\tOrder of XSDs can be significant (place XSDs that are");
         out.println("\tdependent on other XSDs after those they depend on)");
         System.exit(-1);
      }
      // Arrays.copyOfRange requires JDK 6; see
      // http://stackoverflow.com/questions/7970486/porting-arrays-copyofrange-from-java-6-to-java-5
      // for additional details for versions of Java prior to JDK 6.
      final String[] schemas = Arrays.copyOfRange(arguments, 1, arguments.length);
      validateXmlAgainstXsds(arguments[0], schemas);
   }
}

Despite what the length of this post might initially suggest, using Java to validate XML against an XSD is fairly straightforward. The sample application shown and explained here attempts to demonstrate that and is a useful tool for simple command line validation of XML documents against specified XSDs. One could easily port this to Groovy to be even more script-friendly. As mentioned earlier, this simple tool requires JDK 8 as currently written but could be easily adapted to work on JDK 5, JDK 6, or JDK 7.

UPDATE (20 March 2015): I have pushed the Java class shown in this post (XmlValidator.java) onto the GitHub repository dustinmarx/xmlutilities.

Tuesday, March 17, 2015

Will Internet Explorer Be Missed?

It is with at least a small amount of nostalgic sadness that I have read about the end of GeoCities, Dr. Dobb's, Codehause, and Google Code. However, I cannot say that I'll miss Internet Explorer and I felt more relief than sorrow when I read today that Microsoft is killing off the Internet Explorer brand. It sounds like the browser in development under the name Project Spartan is designed to be the main web browser for Windows 10.

I have heard more than one user of Internet Explorer not-so-affectionately refer to it as "Internet Exploder" and some of this is deserved from both a user perspective and a web developer perspective. Although the developer experience and user experience associated with Internet Explorer over the years, I still prefer Chrome and Firefox over Internet Explorer both as a user and as a web developer.

It used to be far worse and I still have bad memories of developing web applications and dealing with Internet Explorer's spotty support for standardization of JavaScript, HTML, and CSS. Although most browser vendors have been and are guilty of picking and choosing how well they'll support each standard, Internet Explorer consistently seemed especially slow to adopt new standards. As a user, I was frustrated with the perceived slowness of Internet Explorer when compared to Firefox and then especially when compared to Chrome. I was also frustrated when those addictive tabs seemed to take a long time to make it into Internet Explorer.

Although I won't miss Internet Explorer, I think it's polite to speak well of the dying. In that vein, I'll finish this post by outlining some of Internet Explorer's positive contributions to web development and to the Internet experience.

  • Internet Explorer brought the web to the masses - My first experience with graphical web browsers was using Mosaic and then using Mozilla and Netscape. Many of my fellow college students and contemporary young software developers also used these browsers. However, Microsoft Internet Explorer was the first browser used by many people because it came pre-installed with their Microsoft-provided operating system. For many people who transitioned from a vendor-specific online experience such as provided by America Online (now AOL) at the time to a more general browser-based web experience, Internet Explorer was the browser that allowed this to happen.
  • XMLHttpRequest (Ajax) - Jesse James Garrett's article Ajax: A New Approach to Web Applications changed the way many of who were developing web applications thought about web development. As Garrett pointed out in a follow-up to that article, "XMLHttpRequest is only part of the Ajax equation," but it is the "technical component that makes the asynchronous server communication possible." For me, XMLHttpRequest was the biggest missing piece in making this type of asynchronous communication happen. The post A Brief History of Ajax points out that the XMLHttpRequest was pioneered by Microsoft in Internet Explorer 5. It has since been adopted by all major web browses as a standard.
  • A Starting Point - Related to the first bullet above (ubiquitous nature of Internet Explorer on Windows platforms), it has been pointed out that one advantage of Internet Explorer is that its automatically being available on Windows machines has made it easy for people to download alternative web browsers of their choosing. This is becoming less of an issue as more devices are not Windows platforms and have their own standard browser often provided.

Although I won't particularly miss Internet Explorer, there are those who will. The following are some examples of posts written by people or about people who believe that Internet Explorer has advantages over its competitors.

Goodbye, Internet Explorer!

Monday, March 16, 2015

The End of Google Code

In the 21 January 2014 post Google Code is dead, Evert Pot referenced the post A Change to Google Code Download Service and wrote that "It's been sort of obvious for a while that [Google] stopped caring about their code hosting." The title of Pot's post was borne out with the announcement this past week that Google is Bidding farewell to Google Code.

According to the post Bidding farewell to Google Code on the Google Open Source Blog, "To meet developers where they are, we ourselves migrated nearly a thousand of our own open source projects from Google Code to GitHub." That post also outlines the final days of Google Code. No new projects can be created (as of 12 May 2015) and the site will become read-only on 24 August 2015 with closure of the project hosting on 25 January 2016 (though tarballs will be available throughout 2016).

I recently posted on the fall of Codehaus and mentioned several useful projects that were (or are) hosted there. Google Code also saw its share of important and useful projects in its heyday. These include Google's Guava (now on GitHub), Mockito (now on GitHub), charts4j (now on GitHub), easyb, Google's Go programming language (now at https://golang.org/), Google's Google Web Toolkit (now at http://www.gwtproject.org/), and Google's Chromium(now at http://www.chromium.org/).

Coman Hamilton concludes his article Google Code is dead – but today is a good day for open source with the statement, "Rather than lament the loss of one significant member of the open-source hosting community, we should rejoice in the fact that there are so many other great open-source hosters, that not even Google can compete."

Friday, March 13, 2015

Excellent! Groovy Intends to Join Apache Software Foundation

In the post "Total Bummer: Pivotal Drops Groovy", I briefly wrote about Pivotal's decision to drop Groovy and hoped that Groovy would find a new home. I was pleased to read the announcement that the Groovy project intends to join the Apache Software Foundation. My experience is that some of the best maintained, best supported, and best documented open source projects are those with a corporate sponsor or those associated with the Apache Software Foundation. I have benefited tremendously from several Apache projects over the years including Ant, Struts, Apache HTTP Server, Apache Commons, Camel, Log4J, Lucene, Apache POI, Apache FOP, and Tomcat. The Apache Software Foundation also houses several other highly popular projects including Hadoop, HBase, Apache Cordova, MyFaces, and Solr.

Groovy already enjoys some associations with Apache projects. For example, Groovy bakes in Ant support and Commons CLI support (Groovy's CliBuilder). The Apache page listing projects grouped by programming languages includes a "Groovy" section that lists Apache Camel and Apache OFBiz.

According to Guillaume Laforge, there were discussions about Groovy's next home with several organizations including the Eclipse Foundation, the Software Freedom Conservancy, and the Apache Software Foundation. Matt Raible has provided follow-up to this post in a question-and-answer format with Laforge in the post Groovy Moving to Apache. Of particular interest to me is the expanding on the "gray areas" Laforge alluded to. These "gray areas" include differences and limitations associated with the Apache Software Foundation such as process, repository control, and potential corporate funding of an individual project.

One of the several advantages of using Apache projects is the liberal Apache 2 License. Groovy was already available under this license and obviously will continue to use that license as part of the Apache Software Foundation.

Like all projects introduced to the Apache Software Foundation, Groovy will begin in the Apache Incubator. Grails is not at this time slated for the Apache Software Foundation, though that could come in the future. Cédric Champeau briefly mentions Groovy and Apache in his post Who is Groovy?

Monday, March 9, 2015

JDK 8 Streams and Grouping

I wrote about the powerful features of using JDK 8's Streams with Java collections in the post Stream-Powered Collections Functionality in JDK 8. I did not cover use of the groupingBy Collector reduction operation in that post and so address grouping in this post.

The examples in this post will demonstrate how to combine Collection-backed Streams with groupingBy Collectors to reorganize the underlying Collection's data in groups prescribed by a provided classification. These examples are based on the Movie class and Set of Movie classes described in my earlier post Stream-Powered Collections Functionality in JDK 8.

The next code listing demonstrates how a simple statement can be used to group the provided Set of Movies into a Map of movie ratings (key) to movies with that rating (value). The groupingBy Collector provides this Map as a map of key type (the MpaaRating in this case) to a List of the type of objects being grouped (Movie in this case).

/**
 * Demonstrate use of JDK 8 streams and Collectors.groupingBy to
 * group movies by their MPAA ratings.
 */
private static void demonstrateGroupingByRating()
{
   final Map<MpaaRating, List<Movie>> moviesByRating =
      movies.stream().collect(groupingBy(Movie::getMpaaRating));
   out.println("Movies grouped by MPAA Rating: " + moviesByRating);
}

In the example just shown (and in the examples that follow in this post), statically importing java.util.stream.Collectors.groupingBy allows me to NOT need to scope groupingBy calls with the Collectors class name. This simple code snippet groups the movies by their ratings with the returned Map mapping key of movie rating to Lists of movies associated with each rating. Here is an example of the output when the provided Movie set is the same as in my previously referenced post.

Movies grouped by MPAA Rating: {PG13=[Movie: Inception (2010), SCIENCE_FICTION, PG13, 13], R=[Movie: The Shawshank Redemption (1994), DRAMA, R, 1], PG=[Movie: Raiders of the Lost Ark (1981), ACTION, PG, 31, Movie: Back to the Future (1985), SCIENCE_FICTION, PG, 49, Movie: Star Wars: Episode V - The Empire Strikes Back (1980), SCIENCE_FICTION, PG, 12]}

A specific use of the capability just demonstrated is to generate a Map of unique keys to objects in a Collection to the object of that Collection with that key. This might be useful, for example, when needing to look up objects repeatedly and quickly via map but being provided with the objects of interest in a Set or List instead of a Map. Pretending for the moment that movies have unique titles (they only do for my small set), such functionality can be accomplished as shown in the next code listing.

/**
  * Demonstrate use of JDK 8 streams and Collectors.groupingBy to
  * group movies by their title.
  */
private static void demonstrateGroupingByTitle()
{
   final Map<String, List<Movie>> moviesByTitle =
      movies.stream().collect(groupingBy(Movie::getTitle));
   out.println("Movies grouped by title: " + moviesByTitle);
}

Assuming that title is unique for each movie in the original collection, the code above provides a map of movie title to single-element List containing only the movie for which that title is applicable. Any client wanting to quickly look up a movie by its title could call moviesByTitle.get(String).get(0) to get the full Movie object corresponding to that title. The output of doing this with my simple movie set is shown next.

Movies grouped by title: {The Shawshank Redemption=[Movie: The Shawshank Redemption (1994), DRAMA, R, 1], Star Wars: Episode V - The Empire Strikes Back=[Movie: Star Wars: Episode V - The Empire Strikes Back (1980), SCIENCE_FICTION, PG, 12], Back to the Future=[Movie: Back to the Future (1985), SCIENCE_FICTION, PG, 49], Raiders of the Lost Ark=[Movie: Raiders of the Lost Ark (1981), ACTION, PG, 31], Inception=[Movie: Inception (2010), SCIENCE_FICTION, PG13, 13]}

It is possible to group by two different characteristics. This allows for a Collection to be grouped by one characteristic and then have each of those groups sub-grouped by a second characteristic. For example, the following code listing groups movies by rating and then by genre.

/**
 * Demonstrate use of JDK 8 streams and cascaded groupingBy
 * to group movies by ratings and then by genres within ratings.
 */
private static void demonstrateGroupingByRatingAndGenre()
{
   final Map<MpaaRating, Map<Genre, List<Movie>>> moviesByRatingAndGenre =
      movies.stream().collect(groupingBy(Movie::getMpaaRating, groupingBy(Movie::getGenre)));
   out.println("Movies by rating and genre: " + moviesByRatingAndGenre);
}

The code listing just shown first groups the underlying movies by rating and then groups each movie with a particular group of ratings again, but this time by genre. In other words, we get double-level groups of movies by ratings and genre. Output on my simple set of movies is shown next.

Movies by rating and genre: {PG13={SCIENCE_FICTION=[Movie: Inception (2010), SCIENCE_FICTION, PG13, 13]}, R={DRAMA=[Movie: The Shawshank Redemption (1994), DRAMA, R, 1]}, PG={SCIENCE_FICTION=[Movie: Back to the Future (1985), SCIENCE_FICTION, PG, 49, Movie: Star Wars: Episode V - The Empire Strikes Back (1980), SCIENCE_FICTION, PG, 12], ACTION=[Movie: Raiders of the Lost Ark (1981), ACTION, PG, 31]}}

The groupingBy collector makes it easy to group elements of a List or Set into a map with the grouping characteristic as the key and the objects belonging to each group in a List associated with that grouping characteristic key. This allows one all the advantages of a Map, including use of some of the handy methods on Map that have been introduced with JDK 8.

Monday, March 2, 2015

Codehaus: The Once Great House of Code Has Fallen

It was announced on the Codehaus website this past week that "The time has come to end the era of Codehaus." This page includes a rough timeline of when projects and services will be deactivated. This includes mention that "projects and services will be progressively taken offline from April 2nd 2015 onwards" and "most projects and services will be terminated around May 17th 2015."

Codehaus has played a significant role in the world of Java development. An interesting and brief retrospective on Codehaus's glory days can be found in the one of the comments on the Java sub-reddit thread "Codehaus, birthplace of many Java OSS projects, coming to an end." A bit more history of Codehaus can be found, for now, in Codehaus | About | History. Some Java-related projects hosted on Codehaus include AspectWerkz, Castor, PicoContainer, and XStream. Well-known Java-related projects that were formerly on Codehaus before moving somewhere else include JMock, Mule, Jackson, and XDoclet.

Until recently, Groovy and its documentation were accessed at http://groovy.codehaus.org/ (now accessed via http://groovy-lang.org/). Other notable Groovy-related codehaus-based URLs include http://gpars.codehaus.org/, http://groovy.codehaus.org/GroovyFX (now http://groovyfx.org/), and http://griffon.codehaus.org/.

Just as GeoCities was overtaken by other web hosting sites and social media, and just as Dr. Dobb's was overtaken by a plethora of online content covering everything from low-level details to high-level breadth, Codehaus was overtaken by SourceForge and Google Code and eventually GitHub has overtaken all of them.

Although Codehaus's influence has been waning in recent years, it housed several significant projects during its heyday. It seemed inevitable that its time would come as SourceForge and then GitHub took developers' mind share, but it still brings a bit of sadness to see a well-known and once-proud House meeting its end. Thanks to all those who contributed to Codehaus and to the influential projects housed on Codehaus.