Saturday, January 25, 2014

Book Review: Mastering HTML5 Forms

When I accepted Packt Publishing's offer to review the recently released book Mastering HTML5 Forms, I was curious about what an entire book would cover related to HTML5 forms. Although they are relatively basic when compared to some of HTML5's other features, I've always felt that HTML5 forms have much to offer consumers of HTML5 applications.

The subtitle of Mastering HTML5 Forms is "Create dynamic and responsive web forms with this in-depth, hands-on guide." Mastering HTML5 Forms is written by Gaurav Gupta, has five chapters, and is a bit over 125 pages long.

As is the case with most of the Packt Publishing books I have read, Mastering HTML5 Forms's Preface introduce's the book's objectives, describes the book's overall approach ("step-by-step, example driven, and visual-based approach to learning"), summarizes the book's chapters, explains what readers should have when reading the book (text editor, Wamp, and phpMyAdmin), and describes the book's intended audience ("those who are interested in learning how HTML5, CSS3, and PHP can be used to build responsive, beautiful, and dynamic web forms").

Chapter 1: Forms and Their Significance

The first three chapters of Mastering HTML5 Forms's five chapters cover traditional HTML5 forms topics. The initial chapter introduces HTML5 forms, briefly discusses the history and appeal of forms in web pages, outlines benefits of web forms in general, contrasts HTML5 web forms with traditional HTML web forms, and an introduction to common <input> elements and the browsers that support them. Chapter 1 also introduces HTML5 form elements (<datalist>, <keygen>, and <output>) and HTML5 form attributes (including autocomplete, autofocus, placeholder, min and max, list, formnovalidate, required, and pattern).

After introducing HTML5 form elements and attributes, the initial chapter looks at complete web page code listings using some of these with CSS and shows some screen snapshots of the web pages generated by the HTML5/CSS code. Before closing with a Summary, the first chapter provides a list of bullets highlighting guidelines for obtaining "better results" when using HTML5 web forms.

Chapter 2: Validation of Forms

The second chapter of Mastering HTML5 Forms is focused on form validation. After contrasting client-side validation and server-side validation including discussion of the advantages and disadvantages of each, the second chapter moves to more detailed coverage of HTML5 form validation. This is done by first introducing client-side validation with a traditional pre-HTML5 JavaScript approach that helps to demonstrate the advantage of the HTML5 form-based approach that follows.

A good portion of Chapter 2 covers HTML5 constraint validation. This coverage includes descriptions and examples of constraint validation objects, properties, attributes, and methods such as checkValidity() and customError. These are appealing because they require minimal JavaScript.

Chapter 3: Styling the Forms

Chapter 3 of Mastering HTML5 Forms focuses on styling HTML5 forms with CSS. The chapter provides an overview of using CSS with HTML5, discusses vendor-specific prefixes for CSS3 properties, and begins introducing specifics of CSS3 such as selectors and pseudo-selectors and the substring matching attribute selectors (begins with, ends with, contains). The chapter also provides a table listing "new pseudo-classes."

Other CSS3 coverage in the third chapter of Mastering HTML5 Forms covers background, borders, text effects, fonts, and gradients. With the CSS3 introduction made, the chapter then covers applying CSS3 to HTML5 forms. The chapter concludes with bullets spelling out "CSS3 guidelines for effective styling of the forms."

Chapter 4: Connection with Database

The fourth chapter of Mastering HTML5 Forms leaves core HTML5 forms concepts behind and shifts focus to storing the information provided via web forms. Specifically, Chapter 4 covers "how to store the user's input data into a database using PHP and MySQL." The chapter briefly introduces both PHP and MySQL before demonstrating how to apply this language and database to storing data provides via forms.

Much of the fourth chapter is highly specific to PHP, MySQL, and the use of PHP with MySQL and most of Chapter 4 is not generally applicable to HTML5 forms. However, there are some general principles of HTML5 forms that can be glenaed from the PHP/MySQLs-specific descriptions and examples. Specifically, the concepts of handling potentially spoofed forms submissions and linking forms to the server side are illustrated with these PHP/MySQL examples.

Chapter 5: Responsive Web Forms

Mastering HTML5 Forms's fifth and final chapter focuses on one of the trends that I recently highlighted in my post Significant Software Development Developments of 2013 (#6): responsive design. Gupta begins the chapter by referencing Ethan Marcotte's Responsive Web Design and defining responsive web design as, "An approach by which a website or a particular page dynamically adjusts itself according to particular screen resolution to give the best user experience." I also like the author's overall description of the approach, "Using fluid, proportion-based grids, flexible images, and CSS3 media queries, a site designed with responsive web design automatically adapts the layout to the particular device resolution."

Mastering HTML5 Forms's chapter on responsive design delineates advantages of responsive design before moving onto discussion on "how responsive design works" using HTML, CSS, and JavaScript. The chapter introduces use of the <meta> tag, media queries, media features, fluid grids, and fluid grid frameworks (960 Grid System and Bootstrap), and adaptive images. As with most of the other chapters, Chapter 5 includes a "Guidelines" section that includes "guidelines for responsive design so as to make our forms more effective."

General Observations
  • The first three chapters are exactly what one would expect from a book on HTML5 forms with deep focus specifically on HTML5 forms. These are the most important chapters for those wanting a comprehensive introduction to HTML5 forms.
  • Chapter 4 and Chapter 5 are less about HTML5 forms directly and more about database connectivity and responsive web design and how those two concepts affect form design.
    • Chapter 4 will be mostly of interest to PHP developers.
    • Chapter 5 is generally interesting in terms of what responsive design is; responsive designs can make forms more effective but responsive design is interesting from a broader perspective than forms. Although Chapter 5 was likely not necessary in a book on HTML5 forms, I like its inclusion in the book for completeness.
  • Most of the chapters of Mastering HTML5 Forms include "guidelines" and "best practices" related to the material covered in the chapter. Most of these guidelines/best practices were only tangentially covered or not covered at all in the chapter, but provided a starting point for someone wanting to take the next step in that area. There were a few instances in which I would have liked a particular guideline or best practice to come with additional explanation regarding why it is recommended.
  • Mastering HTML5 Forms is generally well-written. There were one or two sentences that I had difficulty deciphering, but the majority of the book is clearly written. There were some minor editing issues where lines of ext ended after only a couple of words and then the same sentence continued on the next line, but there edit issues are minor and don't take much away from the substance of the book.
  • The electronic (PDF) version of Mastering HTML5 Forms includes color screen snapshots. I wish that more screen snapshots were included. There were some screen snapshots shown of some of the HTML5 forms features, but it would have been nice to have small screen snapshots of each feature rather than of a subset of the features.
  • My only reservation in recommending purchase of Mastering HTML5 Forms has nothing to do with book itself. Rather, the one caveat I would offer to those considering this book is that its content is largely covered online in resources such as The Current State of HTML5 Forms, Dive Into HTML5 Forms, Making Forms Fabulous with HTML5, Rethinking Forms in HTML5, Bring Your Forms Up to Date With CSS3 and HTML5 Validation, and A List Apart. However, there are advantages to having all of these items covered in a single book with consistent coverage.
Conclusion

Mastering HTML5 Forms provides a comprehensive introduction and overview of HTML5 forms in its first three chapters. It adds coverage of using HTML5 forms with PHP and MySQL in Chapter 4 for those who are interested in those technologies. Its final chapter introduces responsive design and demonstrates how responsive design techniques can make forms more useful and efficient for users. Although most of the topics covered in Mastering HTML5 Forms are readily available online, the book does a nice job of providing all of this information in a single place and demonstrating how the various concepts it covers can be used together.

Monday, January 20, 2014

Something to Consider as Java Tops the Programming Charts

The following is a contributed article from Dennis Chu of Coverity:


Something to Consider as Java Tops the Programming Charts

By Dennis Chu, Senior Product Manager, Coverity

For development teams, it may be obvious: Java is one of the top programming languages today. Approximately 9 million developers are currently working in Java; it’s said to be running on three billion devices and the language continues to evolve almost as quickly as the changing technology landscape. But, as the story goes, the rise to the top isn’t always easy.

While Java continues to grow in popularity, it has also been linked to a number of vulnerabilities over the years - due in large part to hackers capitalizing on its widespread use. So much so that Apple moved to pull Java entirely from its Mac OS X and its products at the end of 2012.

Further, during the summer of 2013, flaws in Java were linked to growing security threats for some Android device users who owned the much-hyped digital currency Bitcoin. The vulnerability enabled hackers to tap into the digital wallets of these Bitcoin owners, exposing a serious risk for both the new monetary system and the Android operating system.

One of the most recent blows for Java came from its link to HealthCare.gov, the website that continues to make headlines as developers work to fix the programming errors that caused the site to come to a crawl - only about 5 percent of the expected 500,000 health insurance plan enrollments were able to occur in the first month of the site’s launch. HealthCare.gov was developed with Java on top of Tomcat, and while the causes of its errors are many and complex, coding and architecture design flaws were no doubt part of the problem.

Despite the shortcomings exposed over the years, Java has a large number of effective testing and development tools. But even so, given the persistence of issues, it’s become clear that these tools are not being leveraged properly. This is presumably due to poor development testing discipline or weak processes in place within organizations.

After reviewing a number of open source Java projects via our Coverity Scan service – which helps the open source development community evaluate and improve the quality and security of their software – we found similar levels of quality and security issues for Java relative to other languages, such as C and C++. So it turns out that just because Java is one of the most widely used computer languages, it doesn’t guarantee higher quality software.

Some advice for developers coding in Java, or any other computer programming language for that matter: be vigilant. Make an emphasis to select the right tools that will provide the right framework and process to allow your organization to test early and often. This will enable your organization to avoid potential nightmares down the road – for example after it’s been released to customers, when it’s too late.

Using the right technologies and best practices are still the best safeguards to ensure high-quality software. Fixing a flaw during the development process will cost only a small fraction of what it will cost to fix a defect after the product has been released – and that’s not including the damage to your brand and reputation.

On the road ahead, no matter what language tops the charts, it’s important to view testing as a critical investment rather than an unintended expense.


The article above was contributed by Dennis Chu of Coverity. I have published this contributed article because I think it brings up some interesting points of discussion. No payment or remuneration was received for publishing this article.

Saturday, January 18, 2014

Specifying Gradle Build Properties

Properties are a valuable tool for easily customizing Gradle builds and the Gradle environment. I demonstrate some of these approaches for specifying properties used in a Gradle build in this post.

Gradle supports both project properties and system properties. The main difference between the two that is of interest in this post is how each is accessed. Project properties are more conducive to direct access by name while system properties are accessed via normal Java/Groovy system properties access methods.

Passing Project Properties from Command-Line with -P

One of the easiest approaches for passing properties to a Gradle build is to specify project properties with -P from the command line. Properties passed into the build with -P are readily accessible in the build as project properties and, if their naming structure allows, can be accessed directly like variables.

Passing System Properties from Command-Line with -D

As is the case with other Java applications, one can pass system properties to Gradle builds with -D. Although these system properties provided to the Gradle build via the -D option are always available to the Gradle build via the normal Java mechanism for obtaining system properties, Gradle makes it possible to specify Project Properties as system properties. This is done by placing the prefix org.gradle.project. before the name of the property desired in the build. For example, if one wanted to specify a system property named name.first with -D that would be available to the Gradle build as if it was provided with -P, the person could provide it to the Gradle build on the command line as org.gradle.project.name.first and the Gradle build would see it as a project property named name.first.

Passing System Properties via Environmental Variable

Any Java or Groovy application (including a Gradle build) can access environmental variables via System.getenv(String). However, Gradle allows environment variables to be accessed within the build like other project properties if the environmental variable is prefixed with ORG_GRADLE_PROJECT_. For example, if one wanted a project property in the Gradle build to be called name.last and wanted to supply it to the build via environment variable, that person could declare the environment variable ORG_GRADLE_PROJECT_name.last and its value would be available to the Gradle build as a project property with name name.last.

gradle.properties

Properties can also be provided to a Gradle build via a properties file named gradle.properties. Any properties specified with systemProp. at the beginning of their property name are seen as system properties in the Gradle build and other properties (without their names beginning with "systemProp.") are seen as Gradle project properties. For example, if my gradle.properties file had a property name.last=Marx and a property systemPropr.name.first=Dustin, the name.last property would be seen and accessed in the Gradle build like any project property while the name.first property would be seen and accessed in the Gradle build like any system property.

Demonstration / Example

Each of these types of properties-specifying mechanisms can be demonstrated with a simple example. The Gradle build shown next attempts to print out various properties that are specified in different ways.

build-properties.gradle
task displayProperties << {
   displaySystemProperties()
   displayGradleProjectProperties()
}

def displaySystemProperties()
{
   println "\n=== System Properties ==="
   println "Favorite Movie (1994): ${System.properties['movie.favorite.1994']}"
   println "Favorite Movie (1996): ${System.properties['movie.favorite.1996']}" 
   println "Favorite Movie (1997): ${System.properties['movie.favorite.1997']}"
   println "Favorite Movie (1981): ${System.properties['movie.favorite.1981']}"
   println "Favorite Movie (2012): ${System.properties['movie.favorite.2012']}"
   println "Favorite Movie (2013): ${System.properties['movie.favorite.2013']}"
}

def displayGradleProjectProperties()
{
   println "\n=== Gradle Project Properties ==="
   println "Favorite Movie (1994): ${getProjectProperty('movie.favorite.1994')}"
   println "Favorite Movie (1996): ${getProjectProperty('movie.favorite.1996')}"
   println "Favorite Movie (1997): ${getProjectProperty('movie.favorite.1997')}"
   println "Favorite Movie (1981): ${getProjectProperty('movie.favorite.1981')}"
   println "Favorite Movie (2012): ${getProjectProperty('movie.favorite.2012')}"
   println "Favorite Movie (2013): ${getProjectProperty('movie.favorite.2013')}"
}

def String getProjectProperty(String propertyName)
{
   String movieTitle = "null"
   if (hasProperty(propertyName))
   {
      movieTitle = this.properties[propertyName]
   }
   return movieTitle
}

Some of the properties to be passed to this script will be provided on the command-line with -P, some will be provided on the command line with -D, one will be provided via environment variable, and two will be provided via gradle.properties file in the same directory as the build. That gradle.properties file is shown next.

gradle.properties
movie.favorite.2013=Star Trek into Darkness
systemProp.movie.favorite.2012=Skyfall

With the gradle.properties file in place, the other two interesting parts of the example are the setting of the environment variable. The example here is in DOS, but the same thing could be done with slightly different syntax in Linux environments. The DOS/Windows command is: set ORG_GRADLE_PROJECT.movie.favorite.1981="Raiders of the Lost Ark"

For this demonstration, I'll run the Gradle build script with -D and -P parameters: gradle -b build-properties.gradle displayProperties -Pmovie.favorite.1996="Independence Day" -Dmovie.favorite.1997=Gattaca -Dorg.gradle.project.movie.favorite.1994="Shawshank Redemption"

When running the above-listed Gradle build script with the indicated gradle.properties file in place, with the indicated environment variable specified, and with the command just shown, the output looks that shown in the next screen snapshot.

The screen snapshot indicates how properties are seen/accessed in the Gradle build depending on their source and naming convention. In short, the output demonstrates the following "rules" of property availability in a Gradle build:

  • Command-line -P properties are "project properties"
  • Command-line -D properties are, with one exception, "system properties"
  • Command-line -D properties that begin with org.gradle.project. are "project properties"
  • Properties specified in gradle.properties are, with one exception, "project properties"
  • Properties specified in gradle.properties that begin with systemProp. are "system properties"
  • Properties specified via environment variable are, with one exception, "system properties"
  • Properties specified via environment variables that begin with ORG_GRADLE_PROJECT_ are "project properties"
Conclusion

Gradle provides numerous approaches for specifying properties that can be used to customize the Gradle build.

Thursday, January 16, 2014

The Gradle Interface: Gradle Build Metadata

As I've shown in previous posts such as "Identifying Gradle Conventions" and "Evolving Gradle Build from Ant Build: Importing Ant Build File", significant information about a Gradle build can be gleaned by accessing Gradle's APIs via Groovy. In this post, I look demonstrate accessing basic Gradle build details via the Gradle interface.

The org.gradle.api.invocation.Gradle interface is accessible in the Gradle build file via simply "gradle" [which implicitly corresponds to getGradle() in Groovy parlance]. The next Gradle build script listing shows a subset of the metadata information available via the Gradle interface.

build-gradle-interface.gradle
// build-gradle-interface.gradle
apply plugin: 'java'

println "Class: ${this.getClass().canonicalName}"
println "Gradle: ${gradle.getClass().canonicalName}"
println "Ant: ${ant.getClass().canonicalName}"
println "Root Project: ${rootProject.getClass().canonicalName}"

println "\n=== Gradle ==="
println "\tgradleVersion = ${gradle.gradleVersion}"
println "\tgradleHomeDir = ${gradle.gradleHomeDir}"
println "\tgradleUserHomeDir = ${gradle.gradleUserHomeDir}"

println "\n=== Gradle.startParameter ==="
def startParameter = gradle.startParameter
println "\tcurrentDir = ${startParameter.currentDir}"
println "\tprojectDir = ${startParameter.projectDir}"
println "\tgradleUserHomeDir = ${startParameter.gradleUserHomeDir}"
println "\tbuildFile = ${startParameter.buildFile}"
println "\tprojectProperties = ${startParameter.projectProperties}"
println "\tsystemPropertiesArgs = ${startParameter.systemPropertiesArgs}"
println "\ttaskNames = ${startParameter.taskNames}"

When I run the Gradle build above and specify that the "jar" task (a task provided by the Java plugin) should be run, the output appears as follows:

The above build listing and associated screen snapshot indicate that the Gradle interface provides metadata such as version of Gradle, the Gradle installation's home directory, and the Gradle user's directory. The Gradle interface also provides access to an instance of StartParameter that provides further build start-up metadata details such as project directory, current directory, the name of the build file, project properties, system properties, and names of tasks that were specified for execution.

There is significantly more information that can be gleaned about a Gradle build from the Gradle interface and this post only shows a subset of that information.

Monday, January 13, 2014

Differentiating Ant Target-Based Gradle Tasks

In my blog post Evolving Gradle Build from Ant Build: Importing Ant Build File, I demonstrated using Gradle's built-in AntBuilder-based Ant support to import Ant targets in a Gradle build. These Ant targets can then be accessed as Gradle tasks and appear the same as tasks introduced directly by the Gradle build. In this post, I look at using Groovy to differentiate between Gradle tasks based on imported Ant targets and Gradle-defined tasks.

The Ant build file imported by the Gradle builds in my examples of this post was introduced in my previous post. That Ant build file included the targets "-init", "clean", "compile", "jar", "javadoc", "all", and "output". The Gradle build script file that follows imports that build.xml and its Ant targets.

build-ant-targets-and-gradle-tasks.gradle (Version 1)
// build-ant-targets-and-gradle-tasks.gradle
//
// Gradle build script demonstrating Gradle Tasks associated with this project
// that are not provided by an imported Ant build file.

// ant is a DefaultAntBuilder instance
ant.importBuild 'build.xml'

def antTargetsNames = ant.references.get("ant.targets").collect{ it.name }
println "\nAnt Targets: ${antTargetsNames}\n"

def taskNames = rootProject.tasks.collect{ it.name }
println "\nGradle Task Names: ${taskNames}\n"

def tasksThatAreNotAntTargets = taskNames - antTargetsNames
println "\nGradle Tasks that are NOT Ant Targets: ${tasksThatAreNotAntTargets}\n"

The implicitly available "ant" variable (default AntBuilder) is used first to get all Ant-provided targets via the call ant.references.get("ant.targets"). Groovy's handy Collection.collect(Closure) method is invoked upon that collection to return a collection of the "names" of the Ant targets.

The implicitly available "rootProject" can also be used to get the tasks at the root project level using rootProject.tasks. The same Groovy Collection.collect(Closure) method is used on this collection to get the names of the Gradle tasks. Finally, Groovy's subtraction operator is used to easily determine which Gradle Tasks are not Ant Targets. When run as shown above all the Gradle Tasks are Ant Targets and so the subtraction operator returns nothing. This is shown in the next screen snapshot.

To make the example more interesting, I add a couple of Gradle-introduced Tasks to the Gradle build file shown above. The new version with two new Gradle-introduced Tasks is shown next.

build-ant-targets-and-gradle-tasks.gradle (Version 2)
// build-ant-targets-and-gradle-tasks.gradle
//
// Gradle build script demonstrating Gradle Tasks associated with this project
// that are not provided by an imported Ant build file.

// ant is a DefaultAntBuilder instance
ant.importBuild 'build.xml'

task(helloWorld) << {
   println "Hello, World!"
}

task(currentDateTime) << {
   println new Date()
}

def antTargetsNames = ant.references.get("ant.targets").collect{ it.name }
println "\nAnt Targets: ${antTargetsNames}\n"

def taskNames = rootProject.tasks.collect{ it.name }
println "\nGradle Task Names: ${taskNames}\n"

def tasksThatAreNotAntTargets = taskNames - antTargetsNames
println "\nGradle Tasks that are NOT Ant Targets: ${tasksThatAreNotAntTargets}\n"

This revised version of the Gradle build script introduces two Tasks of its own ("helloWorld" and "currentDateTime"). The output from running this script includes these two new Gradle tasks as Gradle tasks that are not Ant-introduced, Target-based Gradle tasks. This output is shown next.

The examples in this post provide additional examples of the advantages of being able to use Groovy code to better understand Gradle builds. It is straightforward to access the default Ant Builder instance ("ant") and the "rootProject " to get the names of all Ant-based Gradle tasks as well as all tasks (Ant-based or Gradle-introduced).

Wednesday, January 8, 2014

Evolving Gradle Build from Ant Build: Importing Ant Build File

Changing the build system on a large project can be difficult and a lot of work. Fortunately for those migrating Ant builds to Gradle builds, Gradle provides particularly convenient mechanisms to facilitate this migration. Because Gradle is built on Groovy and Groovy includes built-in Ant support via AntBuilder, Gradle builds can use AntBuilder to call Ant tasks and run Ant targets. However, Gradle provides an even easier mechanism for referencing existing Ant targets from a Gradle build with Gradle's support for importing an Ant build via DefaultAntBuilder and that is the subject of this post.

Being able to call existing Ant targets from a new Gradle build is advantageous because it allows the migration to take place over time. One can start using Gradle almost immediately with all the real work delegated to the existing Ant build. Then, as time and priorities allow, different Ant tasks can be replaced with Gradle tasks.

To demonstrate how easy it is to import an Ant build in a Gradle build, I first provide the code listing for a simplified Ant build.

Ant Build File: build.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="JavaArrays" default="all" basedir=".">
   <description>Java Array Utility Functions</description>

   <property name="javac.debug" value="true" />
   <property name="src.dir" value="src" />
   <property name="dist.dir" value="dist" />
   <property name="classes.dir" value="classes" />
   <property name="javadoc.dir" value="${dist.dir}/javadoc" />

   <property name="jar.name" value="javaArrays.jar" />
   <property name="jar.filesonly" value="true" />

   <path id="classpath">
   </path>

   <target name="-init">
      <mkdir dir="${classes.dir}" />
      <mkdir dir="${dist.dir}" />
   </target>

   <target name="compile"
           description="Compile the Java code."
           depends="-init">
      <javac srcdir="${src.dir}"
             destdir="${classes.dir}"
             classpathref="classpath"
             debug="${javac.debug}"
             includeantruntime="false" />
   </target>

   <target name="jar"
           description="Package compiled classes into JAR file"
           depends="compile">
      <jar destfile="${dist.dir}/${jar.name}"
           basedir="${classes.dir}"
           filesonly="${jar.filesonly}">
      </jar>
   </target>

   <target name="all"
           description="Compile Java source, assemble JAR, and generate documentation"
           depends="jar, javadoc" />

   <target name="javadoc" description="Generate Javadoc-based documentation">
      <mkdir dir="${javadoc.dir}" />
      <javadoc doctitle="Examples of Java Array Utility Functions"
               destdir="${javadoc.dir}"
               sourcepath="${src.dir}"
               classpathref="classpath"
               private="true"
               author="Dustin" />
   </target>

   <target name="clean" description="Remove generated artifacts.">
      <delete dir="${classes.dir}" />
      <delete dir="${dist.dir}" />
   </target>

</project>

The above Ant build file has some fairly typical targets with names like "compile", "jar", "javadoc", and "clean". All of this functionality can be imported into a Gradle build file. The next code listing is the complete Gradle build file that does this.

Gradle build.gradle that imports Ant build.xml
ant.importBuild 'build.xml'

The one-line Gradle build file shown above imports the Ant build file shown earlier. The effects of this can be easily seen in the following screen snapshots. The initial screen snapshot shows that the single line Gradle build file makes the "arrays" project available to the Gradle build as well as "other tasks" of "all" and "clean" with the descriptions associated with those Ant targets.

One can use gradle tasks --all to see all Ant targets, including the dependent targets such as "compile", "jar", and "javadoc". This is demonstrated in the next screen snapshot.

The next screen snapshot demonstrates running the default "all" target in the Ant build from the Gradle build.

As the build listings and images have demonstrated, importing an existing Ant build in a Gradle build is a straightforward process.

Monday, January 6, 2014

Identifying Gradle Conventions

Configuration by convention has many advantages, especially in terms of conciseness because developers do not need to explicitly configure things that are implicitly configured through convention. When taking advantage of configuration by convention, however, one needs to be aware of the conventions. These conventions might be documented, but I always like it when I can programmatically determine the conventions because documentation can become outdated (same principle behind code always being correct and comments only sometimes being correct). I begin this post by looking at how to identify the specific conventions associated with the Gradle Java Plugin. I then generalize this approach to identify all properties associated with all tasks associated with the root project of a Gradle build.

The Gradle documentation on Gradle Plugins states the following regarding the importance of Gradle Plugins and what they add to a Gradle build:

Gradle at its core intentionally provides little useful functionality for real world automation. All of the useful features, such as the ability to compile Java code for example, are added by plugins. Plugins add new tasks (e.g. JavaCompile), domain objects (e.g. SourceSet), conventions (e.g. main Java source is located at src/main/java) as well as extending core objects and objects from other plugins.

This post looks at some of the tasks, domain objects, and conventions that the Java Plugin brings to a Gradle build. To start, I need a very simple Gradle build file. It consists solely of a single line that applies the Java plugin. It is shown next in the Gradle build file build-java-plugin.gradle.

build-java-plugin.gradle
apply plugin: 'java'

With that single-line Gradle build file in place, it's easy to see which Gradle tasks the plugin-provides by running the command gradle -b build-java-plugin.gradle tasks. The next two screen snapshots show the output of running an empty Gradle build file followed by the output of running the Gradle build file with only the application of the Java plugin.

By comparing the output from running Gradle "tasks" against an empty build file to the output from running Gradle "tasks" against the build file with the Java plugin applied, we can see that the Gradle has the same set of "Build Setup tasks" and "Help tasks" whether the plugin is applied or not. More significantly, we see that the Java plugin adds many new tasks categorized as "Build tasks" (assemble, build, buildDependents, buildNeeded, classes, clean, jar, testClasses), "Documentation tasks" (javadoc), "Verification tasks" (check, test), and "Rules".

One feature I enjoy in Gradle 1.10 that Gradle 1.8 (the previous version I used) did not have is the ability on the command line to ask for details on a specific Gradle task. This is demonstrated in the next screen snapshot for Java Plugin tasks compileJava, jar, and javadoc. All three tasks have details written to standard output by using the help --task <task_name> command on the command line. These details on the Java Plugin tasks can also be found in the Gradle User Guide.

Because Gradle is built on Groovy, it's fairly easy to determine characteristics of the Java Plugin using "brute force." The next code listing, for build-java-plugin-properties.gradle, demonstrates using Groovy to determine the Gradle properties (those which can be specified with -P as opposed to system properties specified with -D) available to the build script before and after applying the Java plugin and then uses Groovy's highly convenient overridden subtraction operator to find the differences. The names and values of all of the properties added to the Gradle script by the Java Plugin (except the property "properties") are presented in alphabetical order.

// build-java-plugin-properties.gradle
//
// Displays properties that Gradle Java Plugin adds beyond properties already
// specified for any Gradle build.

def propertiesBefore = this.properties

apply plugin: 'java'

def propertiesAfter = this.properties

def extraProperties = propertiesAfter - propertiesBefore

def extraPropertiesKeys = new TreeSet<String>()
extraProperties.each
{ property ->
   if (property.key != "properties")
   {
      extraPropertiesKeys.add(property.key)
   }
}

extraPropertiesKeys.each
{ key ->
   println "${key} : ${extraProperties.get(key)}"
}

The next image shows a screen snapshot with the output from running this script. The screen snapshot does not show the full output, but a larger piece of the output (all the properties) is shown in text after the image.

Output from Running Above Gradle Script to See Java Plugin Properties

apiDocTitle : gradleExample API
archivesBaseName : gradleExample
assemble : task ':assemble'
binaries : [classes 'main', classes 'test']
build : task ':build'
buildDependents : task ':buildDependents'
buildNeeded : task ':buildNeeded'
buildTasks : [build]
check : task ':check'
classes : task ':classes'
clean : task ':clean'
compileJava : task ':compileJava'
compileTestJava : task ':compileTestJava'
defaultArtifacts : org.gradle.api.internal.plugins.DefaultArtifactPublicationSet_Decorated@bc80d8
dependencyCacheDir : C:\java\examples\groovyExamples\gradleExample\build\dependency-cache
dependencyCacheDirName : dependency-cache
distsDir : C:\java\examples\groovyExamples\gradleExample\build\distributions
distsDirName : distributions
docsDir : C:\java\examples\groovyExamples\gradleExample\build\docs
docsDirName : docs
inheritedScope : org.gradle.api.internal.ExtensibleDynamicObject$InheritedDynamicObject@c10304
jar : task ':jar'
javadoc : task ':javadoc'
libsDir : C:\java\examples\groovyExamples\gradleExample\build\libs
libsDirName : libs
manifest : org.gradle.api.java.archives.internal.DefaultManifest@1ad3677
metaInf : []
module : org.gradle.api.internal.artifacts.ProjectBackedModule@d2eead
processResources : task ':processResources'
processTestResources : task ':processTestResources'
rebuildTasks : [clean, build]
reporting : org.gradle.api.reporting.ReportingExtension_Decorated@33ab8f
reportsDir : C:\java\examples\groovyExamples\gradleExample\build\reports
reportsDirName : reports
runtimeClasspath : file collection
sourceCompatibility : 1.7
sourceSets : [source set 'main', source set 'test']
sources : [[source set 'main:java', source set 'main:resources'], [source set 'test:java', source set 'test:resources']]
status : integration
targetCompatibility : 1.7
test : task ':test'
testClasses : task ':testClasses'
testReportDir : C:\java\examples\groovyExamples\gradleExample\build\reports\tests
testReportDirName : tests
testResultsDir : C:\java\examples\groovyExamples\gradleExample\build\test-results
testResultsDirName : test-results

Gradle makes it easy to see all the Gradle properties using the command "gradle properties", but this command line action shows all properties regardless of their source (Gradle itself or a plugin).

Each Gradle task that the Java Plugin adds to the build has its own set of properties. These properties can be identified in the Gradle Build Language Reference. The Task Types section of that document has links to each task type. The linked-to pages on each task type have details on the properties supported by that task type. For example, the Task Type JavaCompile is listed on its page as having properties such as classpath, destinationDir, and source.

The following rather extensive script displays the settings for the properties of the compileJava, jar, and javadoc Gradle Java Plugin tasks. This script demonstrates how powerful it can be to apply Groovy to identifying Gradle build settings. The script could be shorter if more reflection was used, but calling the tasks' properties out explicitly does have advantages in terms of readability and as a reference for what properties are available on each task.

build-java-plugin-metadata.gradle
// build-java-plugin-metadata.gradle
//
// Displays the properties associated with the Gradle Java Plugin tasks
// of "compileJava", "jar", and "javadoc".

import groovy.transform.Field

apply plugin: 'java'

@Field int MAX_COLUMNS = 80
@Field String headerSeparator = "=".multiply(MAX_COLUMNS)

printCompileJavaProperties()
printJarProperties()
printJavadocProperties()

def printCompileJavaProperties()
{
   printHeader("compileJava Task")
   println "compileJava.classpath:\n${extractStringRepresentation(compileJava.classpath)}"
   println "compileJava.destinationDir:\n${extractStringRepresentation(compileJava.destinationDir)}"
   println "compileJava.source:\n${extractStringRepresentation(compileJava.source)}"
   println "compileJava.options:\n${extractStringRepresentation(compileJava.options)}"
   println "compileJava.includes:\n${extractStringRepresentation(compileJava.includes)}"
   println "compileJava.excludes:\n${extractStringRepresentation(compileJava.excludes)}"
   println "compileJava.sourceCompatibility:\n${extractStringRepresentation(compileJava.sourceCompatibility)}"
   println "compileJava.targetCompatibility:\n${extractStringRepresentation(compileJava.targetCompatibility)}"
}

def printJarProperties()
{
   printHeader("jar Task")
   println "jar.appendix:\n${extractStringRepresentation(jar.appendix)}"
   println "jar.archiveName:\n${extractStringRepresentation(jar.archiveName)}"
   println "jar.archivePath:\n${extractStringRepresentation(jar.archivePath)}"
   println "jar.baseName:\n${extractStringRepresentation(jar.baseName)}"
   println "jar.caseSensitive:\n${extractStringRepresentation(jar.caseSensitive)}"
   println "jar.classifier:\n${extractStringRepresentation(jar.classifier)}"
   println "jar.destinationDir:\n${extractStringRepresentation(jar.destinationDir)}"
   println "jar.dirMode:\n${extractStringRepresentation(jar.dirMode)}"
   println "jar.duplicatesStrategy:\n${extractStringRepresentation(jar.duplicatesStrategy)}"
   println "jar.entryCompression:\n${extractStringRepresentation(jar.entryCompression)}"
   println "jar.excludes:\n${extractStringRepresentation(jar.excludes)}"
   println "jar.extension:\n${extractStringRepresentation(jar.extension)}"
   println "jar.fileMode:\n${extractStringRepresentation(jar.fileMode)}"
   println "jar.includeEmptyDirs:\n${extractStringRepresentation(jar.includeEmptyDirs)}"
   println "jar.includes:\n${extractStringRepresentation(jar.includes)}"
   println "jar.manifest:\n${extractStringRepresentation(jar.manifest)}"
   println "jar.source:\n${extractStringRepresentation(jar.source)}"
   println "jar.version:\n${extractStringRepresentation(jar.version)}"
}

def printJavadocProperties()
{
   printHeader("javadoc Task")
   println "javadoc.classpath:\n${extractStringRepresentation(javadoc.classpath)}"
   println "javadoc.destinationDir:\n${extractStringRepresentation(javadoc.destinationDir)}"
   println "javadoc.excludes:\n${extractStringRepresentation(javadoc.excludes)}"
   println "javadoc.executable:\n${extractStringRepresentation(javadoc.executable)}"
   println "javadoc.failOnError:\n${extractStringRepresentation(javadoc.failOnError)}"
   println "javadoc.includes:\n${extractStringRepresentation(javadoc.includes)}"
   println "javadoc.maxMemory:\n${extractStringRepresentation(javadoc.maxMemory)}"
   println "javadoc.options:\n${extractStringRepresentation(javadoc.options)}"
   println "javadoc.source:\n${extractStringRepresentation(javadoc.source)}"
   println "javadoc.title:\n${extractStringRepresentation(javadoc.title)}"
}

def String extractStringRepresentation(Object object)
{
   String returnString
   if (object in String)
   {
      returnString = "\t${object}\n"
   }
   else if (object in File)
   {
      returnString = "\t${object.canonicalPath}\n"
   }
   else if (object in FileCollection)  // FileTree is a FileCollection
   {
      StringBuilder filesStr = new StringBuilder()
      def files = object.files
      files.each
      { file ->
         filesStr << "\t" << file.canonicalPath << "\n" 
      }
      returnString = filesStr.toString()
   }
   else if (object in CompileOptions)
   {
      StringBuilder compileOptionsStr = new StringBuilder()
      def compileProperties = object.properties
      compileProperties.each
      { compileProperty ->
         if (compileProperty.value in DebugOptions)
         {
            compileOptionsStr << "\t" << compileProperty.key << ": " << extractStringRepresentation(compileProperty.value) << "\n"
         }
         else if (compileProperty.value in DependOptions)
         {
            compileOptionsStr << "\t" << compileProperty.key << ": " << extractStringRepresentation(compileProperty.value) << "\n"
         }
         else if (compileProperty.value in ForkOptions)
         {
            compileOptionsStr << "\t" << compileProperty.key << ": " << extractStringRepresentation(compileProperty.value) << "\n"
         }
         else if (compileProperty.key != "class")
         {
            compileOptionsStr << "\t" << compileProperty.key << ": " << compileProperty.value << "\n"
         } 
      }
      returnString = compileOptionsStr.toString()
   }
   else if (object in DebugOptions)
   {
      returnString = "\t${object.debugLevel}"
   }
   else if (object in DependOptions)
   {
      returnString = "\t${object.classpath}"
   }
   else if (object in ForkOptions)
   {
      returnString = "\t${object.executable} executable with ${object.tempDir} temp directory" 
   }
   else if (object in Set || object in Boolean || object in Number || object in Enum)
   {
      returnString = "\t${object.toString()}\n"
   }
   else if (object in Manifest)
   {
      StringBuilder manifestStr = new StringBuilder()
      def manifestAttributes = object.getAttributes()
      manifestAttributes.each
      { manifestAttribute ->
         manifestStr << "\t" << manifestAttribute.key << ": " << manifestAttribute.value << "\n" 
      }
      returnString = manifestStr.toString()
   }
   else if (object in MinimalJavadocOptions)
   {
      returnString = extractJavadocOptionsAsString(object)
   }
   else if (object == null)
   {
      returnString = "\tnull\n"
   }
   else
   {
      returnString = "\t${object?.class} was unexpected type.\n"
   }
   return returnString
}

def String extractJavadocOptionsAsString(MinimalJavadocOptions javadocOptions)
{
   StringBuilder javadocOptionsStr = new StringBuilder()

   javadocOptionsStr << "\tjavadoc.bootClasspath:"
   def bootClasspathFiles = javadocOptions.bootClasspath
   bootClasspathFiles.each
   { bootClasspathFile ->
      javadocOptionsStr << "\t\t" << bootClasspathFile.canonicalName << "\n" 
   }
   javadocOptionsStr << "\n"

   javadocOptionsStr << "\tjavadocOptions.classpath:"
   def classpathFiles = javadocOptions.classpath
   classpathFiles.each
   { classpathFile ->
      javadocOptionsStr << "\t\t" << classpathFile.canonicalName << "\n"
   }
   javadocOptionsStr << "\n"

   javadocOptionsStr << "\tjavadocOptions.destinationDirectory: " << javadocOptions.destinationDirectory?.canonicalName << "\n"

   javadocOptionsStr << "\tjavadocOptions.doclet: " << javadocOptions.doclet << "\n"

   javadocOptionsStr << "\tjavadocOptions.docletpath:"
   def docletpath = javadocOptions.docletpath
   docletpath.each
   { docletEntry ->
      javadocOptionsStr << "\t\t" << docletEntry.canonicalName << "\n"
   }
   javadocOptionsStr << "\n"

   javadocOptionsStr << "\tjavadocOptions.encoding: " << javadocOptions.encoding << "\n"

   javadocOptionsStr << "\tjavadocOptions.extDirs:"
   def extDirs = javadocOptions.extDirs
   extDirs.each
   { extDir ->
      javadocOptionsStr << "\t\t" << extDir.canonicalName << "\n"
   }
   javadocOptionsStr << "\n"

   javadocOptionsStr << "\tjavadocOptions.header: " << javadocOptions.header << "\n"

   javadocOptionsStr << "\tjavadocOptions.JFlags:"
   def jflags = javadocOptions.JFlags
   jflags.each
   { jflag ->
      javadocOptionsStr << "\t\t" << jflag << "\n"
   }
   javadocOptionsStr << "\n"

   javadocOptionsStr << "\tjavadocOptions.locale: " << javadocOptions.locale << "\n"

   javadocOptionsStr << "\tjavadocOptions.memberLevel: " << javadocOptions.memberLevel << "\n"

   javadocOptionsStr << "\tjavadocOptions.optionFiles:"
   def optionFiles = javadocOptions.optionFiles
   optionFiles.each
   { optionFile ->
      javadocOptionsStr << "\t\t" << optionFile.canonicalName << "\n"
   }
   javadocOptionsStr << "\n"

   javadocOptionsStr << "\tjavadocOptions.outputLevel: " << javadocOptions.outputLevel << "\n"

   javadocOptionsStr << "\tjavadocOptions.overview: " << javadocOptions.overview << "\n"

   javadocOptionsStr << "\tjavadocOptions.source: " << javadocOptions.source << "\n"

   javadocOptionsStr << "\tjavadocOptions.sourceNames:"
   def sourceNames = javadocOptions.sourceNames
   sourceNames.each
   { sourceName ->
      javadocOptionsStr << "\t\t" << sourceName << "\n"
   }
   javadocOptionsStr << "\n"

   javadocOptionsStr << "\tjavadocOptions.windowTitle: " << javadocOptions.windowTitle << "\n"

   return javadocOptionsStr.toString()
}

def printHeader(String headerText)
{
   println headerSeparator
   println "= ${headerText.center(MAX_COLUMNS-4)} ="
   println headerSeparator
}

I used the Groovy @Field annotation in this build file to make the variable to which it was applied available to methods in the build file. The @Field annotation was not available until Groovy 1.8 and this reminded me of something else significant to point out about Gradle and Groovy here: Gradle uses its own prepackaged Groovy rather than any other version of Groovy that might be installed on one's machine. You can determine which version of Groovy that is with the gradle --version command. The next screen snapshot demonstrates that my version of Groovy (2.1.6) is different than the version of Groovy (1.8.6) used by my installation of Gradle (Gradle 1.10). Because Gradle 1.10 comes with Groovy 1.8.6, I had the @Field annotation at my disposal.

Because the output from the last script is so lengthy, I show it here as text rather than in an image.

Output of Running Gradle on build-java-plugin-metadata.gradle
================================================================================
=                               compileJava Task                               =
================================================================================
compileJava.classpath:

compileJava.destinationDir:
 C:\java\examples\groovyExamples\gradleExample\build\classes\main

compileJava.source:
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\Main.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\Main2.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\Main3.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\Main4.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\Temperature.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\TemperatureScale.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\TemperatureUnit.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\TemperatureUnit2.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\TemperatureUnit3.java

compileJava.options:
 bootClasspath: null
 fork: false
 encoding: null
 deprecation: false
 warnings: true
 forkOptions:  null executable with null temp directory
 failOnError: true
 useDepend: false
 includeJavaRuntime: false
 useAnt: false
 compilerArgs: []
 debug: true
 extensionDirs: null
 compiler: null
 debugOptions:  null
 verbose: false
 optimize: false
 dependOptions:  
 listFiles: false

compileJava.includes:
 []

compileJava.excludes:
 []

compileJava.sourceCompatibility:
 1.7

compileJava.targetCompatibility:
 1.7

================================================================================
=                                   jar Task                                   =
================================================================================
jar.appendix:
 null

jar.archiveName:
 gradleExample.jar

jar.archivePath:
 C:\java\examples\groovyExamples\gradleExample\build\libs\gradleExample.jar

jar.baseName:
 gradleExample

jar.caseSensitive:
 true

jar.classifier:
 

jar.destinationDir:
 C:\java\examples\groovyExamples\gradleExample\build\libs

jar.dirMode:
 null

jar.duplicatesStrategy:
 INCLUDE

jar.entryCompression:
 DEFLATED

jar.excludes:
 []

jar.extension:
 jar

jar.fileMode:
 null

jar.includeEmptyDirs:
 true

jar.includes:
 []

jar.manifest:
 Manifest-Version: 1.0

jar.source:
 C:\java\examples\groovyExamples\gradleExample\build\tmp\jar\MANIFEST.MF

jar.version:
 null

================================================================================
=                                 javadoc Task                                 =
================================================================================
javadoc.classpath:
 C:\java\examples\groovyExamples\gradleExample\build\classes\main
 C:\java\examples\groovyExamples\gradleExample\build\resources\main

javadoc.destinationDir:
 C:\java\examples\groovyExamples\gradleExample\build\docs\javadoc

javadoc.excludes:
 []

javadoc.executable:
 null

javadoc.failOnError:
 true

javadoc.includes:
 []

javadoc.maxMemory:
 null

javadoc.options:
 javadoc.bootClasspath:
 javadocOptions.classpath:
 javadocOptions.destinationDirectory: null
 javadocOptions.doclet: null
 javadocOptions.docletpath:
 javadocOptions.encoding: null
 javadocOptions.extDirs:
 javadocOptions.header: null
 javadocOptions.JFlags:
 javadocOptions.locale: null
 javadocOptions.memberLevel: null
 javadocOptions.optionFiles:
 javadocOptions.outputLevel: QUIET
 javadocOptions.overview: null
 javadocOptions.source: null
 javadocOptions.sourceNames:
 javadocOptions.windowTitle: null

javadoc.source:
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\Main.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\Main2.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\Main3.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\Main4.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\Temperature.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\TemperatureScale.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\TemperatureUnit.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\TemperatureUnit2.java
 C:\java\examples\groovyExamples\gradleExample\src\main\java\dustin\examples\TemperatureUnit3.java

javadoc.title:
 gradleExample API

:help

Welcome to Gradle 1.10.

To run a build, run gradle  ...

To see a list of available tasks, run gradle tasks

To see a list of command-line options, run gradle --help

BUILD SUCCESSFUL

Total time: 14.041 secs

The example shown above works well for identifying specific properties associated with the Java Gradle plugin. This works fine, but its limitations include the need to write explicit code for each property whose value is desired. This implies further limitations of not necessarily knowing all the properties that are available (I used the documentation to explicitly print out values in the example above). A further implied limitation is that the script above will not display any properties values that are added to those tasks in the future. The next Gradle build example is based on the previous example, but this example does not explicitly state the tasks and properties to display. Instead, it finds all Tasks associated with the root project and then prints all properties associated with each of those Tasks.

build-java-plugin-metadata-reflection.gradle
// build-java-plugin-metadata-reflection.gradle
//
// Displays the properties associated with the tasks associated with the Gradle
// root project.
//

import groovy.transform.Field

apply plugin: 'java'

@Field int MAX_COLUMNS = 80
@Field String headerSeparator = "=".multiply(MAX_COLUMNS)

def rootProject = getRootProject()
def tasks = rootProject.tasks
tasks.each
{ task ->
   printTaskProperties(task)
}


def printTaskProperties(Task task)
{
   printHeader("Task " + task.name)
   def taskProperties = task.properties
   taskProperties.each
   { taskProperty ->
      println "${task.name}.${taskProperty.key}=${extractStringRepresentation(taskProperty.value)}"
   }
}

def String extractStringRepresentation(Object object)
{
   String returnString
   if (object in String)
   {
      returnString = "\t${object}\n"
   }
   else if (object in File)
   {
      returnString = "\t${object.canonicalPath}\n"
   }
   else if (object in FileCollection)  // FileTree is a FileCollection
   {
      StringBuilder filesStr = new StringBuilder()
      def files = object.files
      files.each
      { file ->
         filesStr << "\t" << file.canonicalPath << "\n" 
      }
      returnString = filesStr.toString()
   }
   else if (object in CompileOptions)
   {
      StringBuilder compileOptionsStr = new StringBuilder()
      def compileProperties = object.properties
      compileProperties.each
      { compileProperty ->
         if (compileProperty.value in DebugOptions)
         {
            compileOptionsStr << "\t" << compileProperty.key << ": " << extractStringRepresentation(compileProperty.value) << "\n"
         }
         else if (compileProperty.value in DependOptions)
         {
            compileOptionsStr << "\t" << compileProperty.key << ": " << extractStringRepresentation(compileProperty.value) << "\n"
         }
         else if (compileProperty.value in ForkOptions)
         {
            compileOptionsStr << "\t" << compileProperty.key << ": " << extractStringRepresentation(compileProperty.value) << "\n"
         }
         else if (compileProperty.key != "class")
         {
            compileOptionsStr << "\t" << compileProperty.key << ": " << compileProperty.value << "\n"
         } 
      }
      returnString = compileOptionsStr.toString()
   }
   else if (object in DebugOptions)
   {
      returnString = "\t${object.debugLevel}"
   }
   else if (object in DependOptions)
   {
      returnString = "\t${object.classpath}"
   }
   else if (object in ForkOptions)
   {
      returnString = "\t${object.executable} executable with ${object.tempDir} temp directory" 
   }
   else if (object in Set || object in List || object in Boolean || object in Number || object in Enum || object in Class)
   {
      returnString = "\t${object.toString()}\n"
   }
   else if (object in Manifest)
   {
      StringBuilder manifestStr = new StringBuilder()
      def manifestAttributes = object.getAttributes()
      manifestAttributes.each
      { manifestAttribute ->
         manifestStr << "\t" << manifestAttribute.key << ": " << manifestAttribute.value << "\n" 
      }
      returnString = manifestStr.toString()
   }
   else if (object in MinimalJavadocOptions)
   {
      returnString = extractJavadocOptionsAsString(object)
   }
   else if (object in Convention)
   {
      StringBuilder conventionStr = new StringBuilder()
      object.plugins.each?.keyset
      { plugin ->
         conventionStr << "\t" << plugin << "\n"
      }
      returnString = conventionStr.toString()
   }
   else if (object in LoggingManager)
   {
      returnString = "\n\tCurrent Log Level: ${object.level}\n\tStandard Error: ${object.standardErrorCaptureLevel}\n\tStandard Output: ${object.standardOutputCaptureLevel}\n"
   }
   else if (object == null)
   {
      returnString = "\tnull\n"
   }
   else
   {
      returnString = "\t${object?.class} was unexpected type with value of ${object}.\n"
   }
   return returnString
}

def String extractJavadocOptionsAsString(MinimalJavadocOptions javadocOptions)
{
   StringBuilder javadocOptionsStr = new StringBuilder()

   javadocOptionsStr << "\tjavadoc.bootClasspath:"
   def bootClasspathFiles = javadocOptions.bootClasspath
   bootClasspathFiles.each
   { bootClasspathFile ->
      javadocOptionsStr << "\t\t" << bootClasspathFile.canonicalName << "\n" 
   }
   javadocOptionsStr << "\n"

   javadocOptionsStr << "\tjavadocOptions.classpath:"
   def classpathFiles = javadocOptions.classpath
   classpathFiles.each
   { classpathFile ->
      javadocOptionsStr << "\t\t" << classpathFile.canonicalName << "\n"
   }
   javadocOptionsStr << "\n"

   javadocOptionsStr << "\tjavadocOptions.destinationDirectory: " << javadocOptions.destinationDirectory?.canonicalName << "\n"

   javadocOptionsStr << "\tjavadocOptions.doclet: " << javadocOptions.doclet << "\n"

   javadocOptionsStr << "\tjavadocOptions.docletpath:"
   def docletpath = javadocOptions.docletpath
   docletpath.each
   { docletEntry ->
      javadocOptionsStr << "\t\t" << docletEntry.canonicalName << "\n"
   }
   javadocOptionsStr << "\n"

   javadocOptionsStr << "\tjavadocOptions.encoding: " << javadocOptions.encoding << "\n"

   javadocOptionsStr << "\tjavadocOptions.extDirs:"
   def extDirs = javadocOptions.extDirs
   extDirs.each
   { extDir ->
      javadocOptionsStr << "\t\t" << extDir.canonicalName << "\n"
   }
   javadocOptionsStr << "\n"

   javadocOptionsStr << "\tjavadocOptions.header: " << javadocOptions.header << "\n"

   javadocOptionsStr << "\tjavadocOptions.JFlags:"
   def jflags = javadocOptions.JFlags
   jflags.each
   { jflag ->
      javadocOptionsStr << "\t\t" << jflag << "\n"
   }
   javadocOptionsStr << "\n"

   javadocOptionsStr << "\tjavadocOptions.locale: " << javadocOptions.locale << "\n"

   javadocOptionsStr << "\tjavadocOptions.memberLevel: " << javadocOptions.memberLevel << "\n"

   javadocOptionsStr << "\tjavadocOptions.optionFiles:"
   def optionFiles = javadocOptions.optionFiles
   optionFiles.each
   { optionFile ->
      javadocOptionsStr << "\t\t" << optionFile.canonicalName << "\n"
   }
   javadocOptionsStr << "\n"

   javadocOptionsStr << "\tjavadocOptions.outputLevel: " << javadocOptions.outputLevel << "\n"

   javadocOptionsStr << "\tjavadocOptions.overview: " << javadocOptions.overview << "\n"

   javadocOptionsStr << "\tjavadocOptions.source: " << javadocOptions.source << "\n"

   javadocOptionsStr << "\tjavadocOptions.sourceNames:"
   def sourceNames = javadocOptions.sourceNames
   sourceNames.each
   { sourceName ->
      javadocOptionsStr << "\t\t" << sourceName << "\n"
   }
   javadocOptionsStr << "\n"

   javadocOptionsStr << "\tjavadocOptions.windowTitle: " << javadocOptions.windowTitle << "\n"

   return javadocOptionsStr.toString()
}

def printHeader(String headerText)
{
   println headerSeparator
   println "= ${headerText.center(MAX_COLUMNS-4)} ="
   println headerSeparator
}

Because this output is for all properties associated with all Tasks associated with the Gradle build's root project, the output is too lengthy to include here. Not all of the property value instances have classes that the extractStringRepresentation(Object object) method is prepared to handle, but those cases could be added to the if-else if structure of that method to handle them. This version of the Gradle build is more generic than the earlier one and prints out properties associated with Tasks that are grouped by Task.

Because a Gradle build is tightly coupled to Groovy, Groovy syntax and features can be used to learn more about the Gradle build. The examples in this post took advantage of numerous Groovy niceties. The reason that the Gradle build code above is so verbose is because most of the Gradle classes used for property values do NOT have overridden toString() methods and so no really useful output is shown without special code to call specific methods to get useful representations. I didn't do it in this post's examples, but another option to deal with lack of overridden toString() methods would be to use Groovy's interception capabilities (metaClass.invokeMethod) to intercept calls to toString() and provide an overridden version. That would be essentially the same code as used above, but would be encapsulated in the intercepting objects rather than contained in the script code.

Conclusion

Gradle has really nice documentation (especially the Gradle User Guide and the Gradle Build Language Reference) and most of the tasks and properties associated with the Java Plugin for Gradle (and other plugins) are easily accessible from that documentation. However, I like to know how to programmatically identify important conventions in case the documentation is ever mistaken or I use a version different than the documentation supports. Another objective of this post has been to demonstrate how useful it can be to know Groovy when working with Gradle. It is for this reason that I believe that the rising prominence of Gradle cannot help but increase interest in Groovy.

Friday, January 3, 2014

Book Review: Groovy 2 Cookbook

I was excited to review Groovy 2 Cookbook (Packt Publishing, 2013) by Andrey Adamovich and Luciano Fiandesio because I have been very happy with two other cookbook-style Groovy books (Groovy Recipes: Greasing the Wheels of Java [Pragmatic Programmers, 2008] and Groovy and Grails Recipes [Apress, 2008]) and because this is the first cookbook-style Groovy book covering Groovy 2 (the other two were written well before Groovy 2). In fact, the only two Groovy books currently available in print that I'm aware of that cover Groovy 2 are this book and Programming Groovy 2: Dynamic Productivity for the Java Developer [Pragmatic Bookshelf, 2013] (the next edition of Groovy in Action is likely heavy on Groovy 2 topics). The subtitle of Groovy 2 Cookbook is "Over 90 recipes that provide solutions to everyday programming challenges using the powerful features of Groovy 2."

The Preface of Groovy 2 Cookbook starts the book with this quote (and I agree with the authors' stated opinion about Groovy's flexibility):

Groovy 2 Cookbook consists of problem-solving recipes for Groovy, one of the most flexible programming languages we have ever used. This book contains solutions to more than 100 common problems, shown with more than 500 Groovy code snippets.

Groovy 2 Cookbook contains 11 chapters in roughly 370 pages with numerous screen snapshots and code listings.

Intended Audience and Prerequisite Background

The Preface states that Groovy 2 Cookbook "covers Groovy 2.0 and 2.1." It also describes the intended audience of the book as "Java and Groovy developers who have an interest in discovering new ways to quickly get the job done using the Groovy language." The Preface states that "no extensive Groovy experience is required to understand and use the code and the explanations accompanying the examples" because "book's recipes start simple" and the assumed knowledge includes "general knowledge of computer science, data structures, complexity, and concurrent programming."

General Content

The Preface has a brief description of each chapter, but very little description is needed as the names of the recipes outlined in the Table of Contents gives a pretty good idea of the topics covered in Groovy 2.0 Cookbook. For example, Chapter 1 is called "Getting Started with Groovy" and that chapter's recipes' titles give a pretty clear description of what is covered. The eleventh chapter of Groovy 2 Cookbook is not actually contained in the book itself, but a link to it online is available in the brief summary of Chapter 11 that exists in the Preface.

In a sign of the maturation of Groovy and increased awareness of Groovy, this chapter does not focus on basics of the Groovy language and does not spend pages comparing Groovy to Java (a short section in this Preface covered that very briefly.) Instead, the recipes of the first chapter focus on installation of Groovy on different operating systems, using Groovy from the command line, using Groovy with groovysh, and configuring IDEs Eclipse and IntelliJ IDEA (but not NetBeans) to work with Groovy. Note that both Groovy Recipes and Groovy and Grails Recipes devote considerable pages and a number of recipes each to an introduction to the Groovy language and contrasting and comparing it with Java.

The second chapter focuses on integrating Groovy and Java, which I consider one of Groovy's biggest advantages when compared to other scripting languages and when compared even to other JVM-based languages. The chapter includes coverage of the Groovy Adaptable (Advanced) Packaging Engine (Grape) and integrating Groovy with Ant and Maven (GMaven), Gradle, and Groovydoc.

The recipes in the first two chapters introduced Groovy features in an incidental manner as recipes were covered. Chapter 3 amplifies this effect and really shows off Groovy features via its recipes. As part of this, the advantages of Groovy compared to Java for certain applications are more clearly seen. Features covered include regular expression support, easier JavaBean-style classes with Groovy (including use of @Canonical), using Groovy's DSL capabilities, and using Groovy's logging support.

It could be argued that my primary use of Groovy is as a scripting language to read and manipulate files. Many of the types of things I like to do with Groovy are covered in Chapter 4 of Groovy 2 Cookbook. The chapter covers use of Groovy to read and write files and to manipulate text content of a file. The chapter also covers reading a file in Zip format, an Excel file (using Apache POI), and a PDF file (using iText).

Chapter 5 ("Working with XML in Groovy") and Chapter 6 ("Working with JSON in Groovy") contains recipes detailing some of the feature I most miss from Groovy when writing code in Java: XML and JSON writing, parsing, and manipulation.

After file reading/writing/manipulation (including XML/JSON), database access is probably the thing I do next most with Groovy and that is the subject of Chapter 7 ("Databases in Groovy"). The majority of the recipes in this chapter on Groovy and the database address SQL/relational databases (HyperSQL 2.3.0 specifically), but the final three recipes of the chapter look at using Groovy with NoSQL databases Redis (using Jedis), MongoDB (using GMongo), and Apache Cassandra (using Hector).

The eighth chapter of Groovy 2 Cookbook is on working with web services in Groovy with recipes cover SOAP-based and REST-based web services implemented with Groovy.

Chapter 9 ("Metaprogramming and DSLs in Groovy") covers subjects that may be more mind-bending for traditional Java developers than other Groovy features covered so far. These are the "heavy-hitting" features that make many of the seemingly smaller features and conciseness of Groovy possible. One recipe introduced in this chapter demonstrated ImportCustomizer, a Groovy class I had not previously used or even been aware of. Discovering this reminded me that although this book has "Groovy 2" in its title, it also covers Groovy 1.x features that came after other cookbook-style Groovy books were published. For example, ImportCustomizer was introduced with Groovy 1.8 and so Groovy 2 Cookbook can cover it when other cookbook-style Groovy books published before Groovy 1.8 obviously could not.

I was pleased to see that Groovy 2 Cookbook's tenth chapter is devoted to "concurrent programming in Groovy." The introduction to this chapter explains that "most of the recipes in this chapter will use the awesome GPars (Groovy Parallel Systems) framework." Chapter 10 is the final chapter in the printed or electronic books.

The eleventh chapter, on testing with Groovy, is accessible online instead; the link to it is found in the section of the Preface that briefly summarizes each chapter of the book. This chapter includes recipes covering unit testing of Java with Groovy, testing web services (SOAP-based and REST-based),

Groovy 2 Features

As would be expected, there is a lot of overlap between the three cookbook-style Groovy books in terms of types of recipes covered. The biggest differentiating factor between Groovy 2 Cookbook and the other two cookbook-oriented Groovy books I referenced earlier is that Groovy 2 Cookbook includes Groovy 2 features instead of covering Java-to-Groovy transition as much as the other two. Some of the Groovy 2 features covered in recipes in Groovy 2 Cookbook include application of invokedynamic for compiling and running Groovy code and static type checking with @groovy.transform.TypeChecked.

As stated above, Groovy 2 Cookbook not only demonstrates features new to Groovy 2 that aren't covered in older Groovy books but also covers features introduced in the later 1.x releases that came out after many of the most popular Groovy books. In particular, I have found Groovy 1.8 to be the "minimally acceptable" version of Groovy for my uses because so many nice features were added in that release and this book covers many of them.

Miscellaneous Observations Who I Recommend This Book For

Groovy 2 Cookbook is a fine book that covers many of the most important Groovy features in a problem-solving approach expected for cookbook-style books. It is up against stiff competition as Groovy Recipes and Groovy and Grails Recipes are both fine books as well. If a developer could only buy one of these books, factors to consider would be how familiar the developer is with Groovy already and whether the developer would rather having the book focus on introduction to Groovy with a focus on Groovy 1.0 (Groovy Recipes or Groovy and Grails Recipes might be favored in that case) or would rather have less introduction to and background on Groovy and have more focus on Groovy 2 and later Groovy 1.x features (Groovy 2 Cookbook might be favored in this latter case). All three books have their own advantages and disadvantages and I've enjoyed and learned something from all three. One other distinguishing feature of Groovy 2 Cookbook is that it seems to me to incorporate third-party Java and Groovy libraries, frameworks, and toolkits in its recipes more than the other two cookbook-style cookbooks.

Although I have learned things about Groovy and its ecosystem from all three cookbook-style books on Groovy, there's no question that there is significant overlap among the three books. One of the best ways for a developer to decide which to purchase might be to browse the table of contents of each book because their respective recipes' titles indicate what is covered in each book.

For any Java developer out there with little or no Groovy experience who wants to learn Groovy from a cookbook-style book, I'd probably recommend Groovy Recipes, but that might be because that's where most of my early Groovy learning came from. Groovy 2 Cookbook is approachable for those new to Groovy, but will probably be even more useful to someone with a minimum amount of Groovy experience (at least read an article or blog or two on Groovy). For any developer who wants a cookbook that heavily covers Grails, then the aptly titled Groovy and Grails Recipes is the best choice.

Conclusion

Of the three cookbook-style Groovy books, Groovy 2 Cookbook is understandably the best of the three at covering "modern Groovy." As its title suggests, it covers Groovy 2, but it also covers later versions of Groovy 1.x that were significant and came out after the first two Groovy cookbook-style books were released. In addition, it covers the broader Groovy ecosystem and related products available in 2013 (year of its publication) better than the other two Groovy cookbook-style books.