12. Packages, Libraries, and Modules

Introduction

//----------------------------------------------------------------------------------
// Groovy adopts many of the Java structuring conventions and terminology
// and adds some concepts of its own.
// Code-reuse can occur at the script, class, library, component or framework level.
// Source code including class file source and scripts are organised into packages.
// These can be thought of as like hierarchical folders or directories. Two class
// with the same name can be distinguished by having different packages. Compiled
// byte code and sometimes source code including scripts can be packaged up into
// jar files. Various conventions exist for packaging classes and resources in
// such a way to allow them to be easily reused. Some of these conventions allow
// reusable code to be placed within repositories for easy use by third parties.
// One such repository is the maven repository, e.g.: ibiblio.org/maven2
// When reusing classes, it is possible to compartmentalise knowledge of
// particular packages using multiple (potentially hierarchical) classloaders.
// By convention, package names are all lowercase. Class names are capitalized.
// Naming examples:
// package my.package1.name     // at most one per source file - at top of file
// class MyClass ...            // actually defines my.package1.name.MyClass
// import my.package1.name.MyClass  // allows package to be dropped within current file
// import my.package2.name.MyClass  // if class basenames are the same, can't
//                                  // import both, leave one fully qualified
// import my.package.name.*         // all classes in package can drop package prefix
//----------------------------------------------------------------------------------

Defining a Module's Interface

//----------------------------------------------------------------------------------
// No equivalent export process exists for Groovy.

// If you have some Groovy functionality that you would like others to use
// you either make the source code available or compile it into class files
// and package those up in a jar file. Some subset of your class files will
// define the OO interface to your functionality, e.g. public methods,
// interfaces, etc. Depending on the circumstances, various conventions are
// used to indicate this functionality including Manifest files, javadocs,
// deployment descriptors, project metadata and dependency management files.
// See 12.18 for an example.
//----------------------------------------------------------------------------------

Trapping Errors in require or use

//----------------------------------------------------------------------------------
// Groovy supports both static and dynamic (strong) typing. When trying to
// compile or run files using static typing, the required classes referenced
// must be available. Classes used in more dynamic ways may be loaded (or
// created) at runtime. Errors in loading such dynamic cases are handled
// using the normal exception handling mechanisms.

// attempt to load an unknown resource or script:
try {
    evaluate(new File('doesnotexist.groovy'))
} catch (Exception FileNotFoundException) {
    println 'File not found, skipping ...'
}
// => File not found, skipping ...

// attempt to load an unknown class:
try {
    Class.forName('org.happytimes.LottoNumberGenerator')
} catch (ClassNotFoundException ex) {
    println 'Class not found, skipping ...'
}
// -> Class not found, skipping ...

// dynamicallly look for a database driver (slight variation to original cookbook)
// Note: this hypothetical example ignores certain issues e.g. different url
// formats for configuration when establishing a connection with the driver
candidates = [
    'oracle.jdbc.OracleDriver',
    'com.ibm.db2.jcc.DB2Driver',
    'com.microsoft.jdbc.sqlserver.SQLServerDriver',
    'net.sourceforge.jtds.jdbc.Driver',
    'com.sybase.jdbc3.jdbc.SybDriver',
    'com.informix.jdbc.IfxDriver',
    'com.mysql.jdbc.Driver',
    'org.postgresql.Driver',
    'com.sap.dbtech.jdbc.DriverSapDB',
    'org.hsqldb.jdbcDriver',
    'com.pointbase.jdbc.jdbcUniversalDriver',
    'org.apache.derby.jdbc.ClientDriver',
    'com.mckoi.JDBCDriver',
    'org.firebirdsql.jdbc.FBDriver',
    'sun.jdbc.odbc.JdbcOdbcDriver'
]
loaded = null
for (driver in candidates) {
    try {
        loaded = Class.forName(driver).newInstance()
        break
    } catch (Exception ex) { /* ignore */ }
}
println loaded?.class?.name // => sun.jdbc.odbc.JdbcOdbcDriver
//----------------------------------------------------------------------------------

Delaying use Until Run Time

//----------------------------------------------------------------------------------
// In Groovy (like Java), any static reference to an external class within
// your class will cause the external class to be loaded from the classpath.
// You can dynamically add to the classpath using:
// this.class.rootLoader.addURL(url)
// To delay loading of external classes, use Class.forName() or evaluate()
// the script separately as shown in 12.2.

// For the specific case of initialization code, here is another example:
// (The code within the anonymous { ... } block is called whenever the
// class is loaded.)
class DbHelper {
    def driver
    {
        if (System.properties.'driver' == 'oracle')
            driver = Class.forName('oracle.jdbc.OracleDriver')
        else
            driver = Class.forName('sun.jdbc.odbc.JdbcOdbcDriver')
    }
}
println new DbHelper().driver.name // => sun.jdbc.odbc.JdbcOdbcDriver
// call program with -Ddriver=oracle to swap to other driver

// A slightly related feature: If you want to load a script (typically in a
// server environment) whenever the source file changes, use GroovyScriptEngine()
// instead of GroovyShell() when embedding groovy.
//----------------------------------------------------------------------------------

Making Variables Private to a Module

//----------------------------------------------------------------------------------
// class variables are private unless access functions are defined
class Alpha {
    def x = 10
    private y = 12
}

println new Alpha().x    // => 10
println new Alpha().y    // => 12 when referenced inside source file, error outside
//----------------------------------------------------------------------------------

Determining the Caller's Package

//----------------------------------------------------------------------------------
// You can examine the stacktrace to determine the calling class: see 10.4
// When executing a script from a groovy source file, you can either:
println getClass().classLoader.resourceLoader.loadGroovySource(getClass().name)
// => file:/C:/Projects/GroovyExamples/Pleac/classes/pleac12.groovy
// or for the initially started script when started using the standard .bat/.sh files
println System.properties.'script.name'
//----------------------------------------------------------------------------------

Automating Module Clean-Up

//----------------------------------------------------------------------------------
// For code which executes at class startup, see the initialization code block
// mechanism mentioned in 12.3. For code which should execute during shutdown
// see the finalize() method discussed (including limitations) in 13.2.
//----------------------------------------------------------------------------------

Keeping Your Own Module Directory

//----------------------------------------------------------------------------------
// Each JVM process may have its own classpath (and indeed its own version of Java
// runtime and libraries). You "simply" supply a classpath pointing to different
// locations to obtain different modules.
// Groovy augments the JVM behaviour by allowing individuals to have a ~/.groovy/lib
// directory with additional libraries (and potentially other resources).
//----------------------------------------------------------------------------------

Preparing a Module for Distribution

//----------------------------------------------------------------------------------
// To make your code available to others could involve any of the following:
// (1) make your source code available
// (2) if you are creating a standard class, use the jar tool to package the
//     compiled code into a jar - this is then added to the classpath to use
// (3) if the jar relies on additional jars, this is sometimes specified in
//     a special manifest file within the jar
// (4) if the code is designed to run within a container environment, there
//     might be additional packaging, e.g. servlets might be packaged in a war
//     file - essentially a jar file with extra metadata in xml format.
// (5) you might also supply your package to a well known repository such as the
//     maven repository - and you will add dependency information in xml format
// (6) you may use platform specific installers to produce easily installable
//     components (e.g. windows .exe files or linux rpm's)
// (7) you may spackage up your components as a plugin (e.g. as an eclipse plugin)
//     this is also typically in jar/zip like format with additional metadata
//----------------------------------------------------------------------------------

Speeding Module Loading with SelfLoader

//----------------------------------------------------------------------------------
// Groovy has no SelfLoader. Class loading can be delayed using external scripts
// and by using the Class.forName() approach discussed in 12.2/12.3. If you have
// critical performance issues, you can use these techniques and keep your class
// size small to maximise the ability to defer loading. There are other kinds of
// performance tradeoffs you can make too. Alot of work has been done with JIT
// (just in time) compilers for bytecode. You can pre-compile Groovy source files
// into bytecode using the groovy compiler (groovyc). You can also do this on
// the fly for scripts you know you are going to need shortly.
//----------------------------------------------------------------------------------

Speeding Up Module Loading with Autoloader

//----------------------------------------------------------------------------------
// Groovy has no AutoLoader. See the discussion in 12.9 for some techniques to
// impact program performance. There are many techniques available to speed up
// program performance (and in particular load speed). Some of these utilise
// techniques similar in nature to the technique used by the AutoLoader.
// As already mentioned, when you load a class into the JVM, any statically
// referenced class is also loaded. If you reference interfaces rather than
// concrete implementations, only the interface need be loaded. If you must
// reference a concrete implementation you can use either a Proxy class or
// classloader tricks to delay the loading of a full class (e.g. you supply a
// Proxy class with just one method implemented or a lazy-loading Proxy which
// loads the real class only when absolutely required)
//----------------------------------------------------------------------------------

Overriding Built-In Functions

//----------------------------------------------------------------------------------
// You can use Categories to override Groovy and Java base functionality.
println new Date().time // => 1169019557140

class DateCategory {  // the class name by convention ends with category
    // we can add new functionality
    static float getFloatTime(Date self) {
        return (float) self.getTime()
    }
    // we can override existing functionality (now seconds since 1970 not millis)
    static long asSeconds(Date self) {
        return (long) (self.getTime()/1000)
    }
}

use (DateCategory) {
    println new Date().floatTime    // => 1.1690195E12
    println new Date().asSeconds()  // => 1169019557
}

// We can also use the 'as' keyword
class MathLib {
    def triple(n) { n * 4 }
    def twice(n) { n * 2 }
}
def m = new MathLib()
println m.twice(10)     // => 20
println m.triple(10)    // => 40 (Intentional Bug!)
// we might want to make use of some funtionality in the math
// library but want to later some of its features slightly or fix
// some bugs, we can simply import the original using a different name
import MathLib as BuggyMathLib
// now we could define our own MathLib which extended or had a delegate
// of the BuggyMathLib class
//----------------------------------------------------------------------------------

Reporting Errors and Warnings Like Built-Ins

//----------------------------------------------------------------------------------
// Many Java and Groovy programs emit a stacktrace when an error occurs.
// This shows both the calling and called programs (with line numbers if
// supplied). Groovy pretties up stacktraces to show less noise. You can use -d
// or --debug on the commandline to force it to always produce full stacktraces.
//----------------------------------------------------------------------------------

Referring to Packages Indirectly

//----------------------------------------------------------------------------------
// already have log10, how to create log11 to log100
(11..100).each { int base ->
    binding."log$base" = { int n -> Math.log(n) / Math.log(base) }
}
println log20(400)  // => 2.0
println log100(1000000)  // => 3.0 (displays 2.9999999999999996 using doubles)

// same thing again use currying
def logAnyBase = { base, n -> Math.log(n) / Math.log(base) }
(11..100).each { int base ->
    binding."log$base" = logAnyBase.curry(base)
}
println log20(400)  // => 2.0
println log100(1000000)  // => 3.0 (displays 2.9999999999999996 using doubles)
//----------------------------------------------------------------------------------

Using h2ph to Translate C #include Files

//----------------------------------------------------------------------------------
// Groovy intefaces with C in the same way as Java: using JNI
// For this discussion we will ignoring platform specific options and CORBA.
// This tutorial here describes how to allow Java (and hence Groovy) to
// call a C program which generates UUIDs:
// http://ringlord.com/publications/jni-howto/
// Here's another useful reference:
// http://weblogs.java.net/blog/kellyohair/archive/2006/01/compilation_of_1.html
// And of course, Sun's tutorial:
// http://java.sun.com/developer/onlineTraining/Programming/JDCBook/jni.html

// You might also want to consider SWIG which simplifies connecting
// C/C++ to many scripting languages including Java (and hence Groovy)
// More details: http://www.swig.org/
//----------------------------------------------------------------------------------

Using h2xs to Make a Module with C Code

//----------------------------------------------------------------------------------
// See discussion for 12.14
//----------------------------------------------------------------------------------

Documenting Your Module with Pod

//----------------------------------------------------------------------------------
// The standard documentation system for Java is JavaDoc.
// Documentation for JavaDoc is part of a Java installation.
// Groovy has a GroovyDoc tool planned which expands upon the JavaDoc tool
// but work on the tool hasn't progressed much as yet.
//----------------------------------------------------------------------------------

Building and Installing a CPAN Module

//----------------------------------------------------------------------------------
// Most libraries for Java (and hence Groovy) come precompiled. You simply download
// the jar and place it somewhere on your CLASSPATH.

// If only source code is available, you need to download the source and follow any
// instuctions which came with the source. Most projects use one of a handful of
// build tools to compile, test and package their artifacts. Typical ones are Ant
// and Maven which you need to install according to their respective instructions.

// If using Ant, you need to unpack the source files then type 'ant'.

// If using Maven, you need to unpack the source files then type 'maven'.

// If you are using Maven or Ivy for dependency management you can add
// the following lines to your project description file.
/*
    <dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.2</version>
    </dependency>
*/
// This will automatically download the particular version of the referenced
// library file and also provide hooks so that you can make this automatically
// available in your classpath.
//----------------------------------------------------------------------------------

Example: Module Template

//----------------------------------------------------------------------------------
// example groovy file for a "module"
import org.apache.commons.lang.WordUtils

class Greeter {
    def name
    Greeter(who) { name = WordUtils.capitalize(who) }
    def salute() { "Hello $name!" }
}

// test class
class GreeterTest extends GroovyTestCase {
    def testGreeting() {
        assert new Greeter('world').salute()
    }
}

// Typical Ant build file (could be in Groovy instead of XML):
/*
<?xml version="1.0"?>
<project name="sample" default="jar" basedir=".">
    <property name="src" value="src"/>
    <property name="build" value="build"/>

    <target name="init">
        <mkdir dir="${build}"/>
    </target>

    <target name="compile" depends="init">
        <mkdir dir="${build}/classes"/>
        <groovyc srcdir="${src}" destdir="${build}/classes"/>
    </target>

    <target name="test" depends="compile">
        <groovy src="${src}/GreeterTest.groovy">
    </target>

    <target name="jar" depends="compile,test">
        <mkdir dir="${build}/jar"/>
        <jar destfile="${build}/jar/Greeter.jar" basedir="${build}/classes">
            <manifest>
                <attribute name="Main-Class" value="Greeter"/>
            </manifest>
        </jar>
    </target>
</project>

*/

// Typical dependency management file
/*
<?xml version="1.0" encoding="UTF-8"?>
<project
  xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
          http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>groovy</groupId>
  <artifactId>module</artifactId>
  <name>Greeter</name>
  <version>1.0</version>
  <packaging>jar</packaging>
  <description>Greeter Module/description>
  <dependencies>
    <dependency>
      <groupId>commons-lang</groupId>
      <artifactId>commons-lang</artifactId>
      <version>2.2</version>
    </dependency>
  </dependencies>
</project>
*/
//----------------------------------------------------------------------------------

Program: Finding Versions and Descriptions of Installed Modules

//----------------------------------------------------------------------------------
// Searching available modules in repositories:
// You can browse the repositories online, e.g. ibiblio.org/maven2 or various
// plugins are available for IDEs which do this for you, e.g. JarJuggler for IntelliJ.

// Searching currently "installed" modules:
// Browse your install directory, view your maven POM file, look in your ~/.groovy/lib
// directory, turn on debug modes and watch classloader messages ...
//----------------------------------------------------------------------------------