Wednesday, October 31, 2012

If, Else, and Match in Scala

Scala improves upon basic conditional statements in a number of ways. Here are a few of the main points in understanding how to write conditional statements in Scala.

If Statements

If expressions have values. This is actually a very useful mechanism. Similar to the last statement in a function, an if-else statement returns the value of the expression that follows the if-else. For example, the following if-statement has a value of true or false depending on the value of x.

if (x < 10) true else false

You can actually initialize a variable with the result of this expression if you want.

val result = if (x < 10) {
   true
}
else {
   false
}


Match Statements


We don't have traditional switch-case statements in Scala, but to no surprise, there's something even better. Scala's "match" expressions are an improved switch statement. Here's an example of a "match" expression that sets the value of a variable "n" based on the value of a variable named "number". If number is 0, then n becomes "zero". If number is 1, then n becomes "one". If number is any other value, then n becomes "some other value".

val n = number match {
   case 0 => "zero"
   case 1 => "one"
   case _ => "some other value"
}

There's no ability for fall-through to occur, like can often happen in a traditional switch statement. Rarely, do you want fall-through, but if you do need to have a range of values evaluate to the same result, you can do that with a "guard". For example, we can modify our match expression to match values of 0, 1, 10-20, and 30-50. Anything else is caught with our "case _" and will result in a value of "some other value". The guard can be any Boolean condition.

val n = number match {
  case 0 => "zero"
  case 1 => "one"
  case num if 10 until 20 contains num => "between ten and twenty"
  case num if 30 until 50 contains num => "between thirty and fifty"
  case _ => "some other value"

}

Objects in Scala

The more I develop in Scala, the more I love it. One of the many things that I love about what Martin did with Scala is to create the "object" construct to easily manage singletons in our applications. Many people frown at singletons and they certainly can be misused. However, there are times when they're the right design for the task at hand, and Scala gives us a way to create singletons in a very clean way.

In Java, you'd most likely implement the singleton design pattern by making the default constructor private and then providing a public static method that returns the sole instance of the class. Well, Scala has no static methods or fields. Instead, we have the "object" construct. With "object", Scala takes care of instantiating a single instance of our object with the features that are defined within the object. Take the following object defined in Scala.

object TemperatureConverter {
    def celsiusToFahrenheit(celsius : Double) = {
        celsius * 9 / 5 + 32
    }
  
    def fahrenheitToCelsius(fahrenheit : Double) = {
        (fahrenheit - 32) * 5 / 9
    }

}

When you need to convert fahrenheit to celsius, you can write this.

TemperatureConverter.fahrenheitToCelsius(82)



Scala takes care of creating the object the first time it's used, so we don't have to worry about it. Scala objects make short work of implementing singletons. This can be very handy when writing utility methods or the case when you need to guarantee that only a single instance of an object exists in your application.

What about when you need to add "static" methods to your class? Since Scala doesn't have "static" methods, we can create a companion object for our class. For example, we might have the following Customer class with a companion object for creating a unique customer number.

class Customer {
    val customerNumber = Customer.getNextCustomerNumber()
    ....
}

object Customer {
    private var uniqueNumber = 0
    private def getNextCustomerNumber() = {
        uniqueNumber += 1
        uniqueNumber
    }
}

Saturday, October 27, 2012

UI Mediator Design Patten

This blog post is an introduction to the UI Mediator SOA design pattern designed by [Utschig, Maier, Trops, Normann, Winterberg] and described in the book "SOA Patterns" by Thomas Erl. The book is a great read and I recommend it to any SOA implementor.

One of the main design goals of a Service Oriented Architecture is to promote flexibility and de-coupling of systems and applications via services. This often comes in the form of asynchronous messaging at the cost of super-fast performance. We choose a message-oriented middleware because it provides flexibility, but often times these messages are asynchronous and aren't guaranteed to return timely responses. The problem manifests itself in the form of a UI that is constantly waiting for a message response. That's where the UI Mediator pattern saves us.

The UI Mediator pattern solves this problem by placing a new "mediator" service between the caller and the service endpoint that does two things:

  1. Invokes the destination service we need to consume
  2. Returns status updates to the UI

Conceptually, the UI Mediator pattern looks like this. The Mediator Service returns updates to the caller while the background services are processing the request.




More often than not, it's acceptable to let the user continue working in another area of the application while a large operation is being processed. We don't want to block the user from working while we wait for a message response, but we need to notify them of progress and alert them when the process has completed successfully (or potentially returned an error at some point in the processing). The UI Mediator pattern does just that and it's a useful implementation when the use case allows background processing, but requires progress updates to the caller.

Thursday, October 25, 2012

Writing an Annotation Based Processor in Java

The code for this tutorial is on GitHub: https://github.com/travisdazell/Annotation-Based-Processor

Annotations have been around since Java 5. They provide a way to mark (i.e. annotate) Java code in a clean and concise manner. With Java 6 and 7, we can do even more with annotations. The most exciting of which, in my opinion, is the ability to write our own annotations and even process our own annotations to detect code issues or even generate source code at compile time.

In this example, we'll create an annotation named @Metrics. We'll be able to mark Java classes with @Metrics and at compile-time, generate a report of all methods defined in the class. To create your own annotation and its corresponding processor, follow these steps.

  1. Define the annotation. This is done by defining the annotation with @interface.
     public @interface Metrics {
     }

  
     2. We can now annotate any class in our project with our new annotation.

     import net.travisdazell.annotations.Metrics;

     @Metrics
     public class CustomerDaoImpl implements CustomerDao {
        public Customer getCustomer(int customerId) {
           // return Customer record
        }
     }


     3. The next step is to write a processor that, at compile time, will report all of the methods defined in CustomerDaoImpl.java. Here's our processor in its entirety.


package net.travisdazell.annotations.processors;

import java.lang.reflect.Method;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;

import net.travisdazell.annotations.Metrics;

@SupportedAnnotationTypes("net.travisdazell.annotations.Metrics")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class MetricsProcessor extends AbstractProcessor {
   
    @Override
    public boolean process(Set<? extends TypeElement> arg0,
            RoundEnvironment roundEnv) {

        StringBuilder message = new StringBuilder();

        for (Element elem : roundEnv.getElementsAnnotatedWith(Metrics.class)) {
            Metrics implementation = elem.getAnnotation(Metrics.class);

            message.append("Methods found in " + elem.getSimpleName().toString() + ":\n");

            for (Method method : implementation.getClass().getMethods()) {
                message.append(method.getName() + "\n");
            }

            processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message.toString());
        }

        return false; // allow others to process this annotation type
    }
}


    4. Next, we need to package our annotation processor and annotation in a JAR file. We must also include a META-INF\services directory in our JAR file. Within the services directory, include a file named javax.annotation.processing.Processor. In that file, type the fully qualified name of the annotation processor.



Here's what my JAR file looks like when exploded in Eclipse.



5. Next, to test our annotation processor, create a class and annotate it with @Metrics.

package net.travisdazell.projects.testproject.dao.impl;

import net.travisdazell.annotations.Metrics;

@Metrics
public class CustomerDaoImpl {
}



6.When we compile this class, we'll get a message displaying the list of methods in the class. As expected, we see what's been inherited from java.lang.Object.




Writing Basic Language Constructs in an ANTLR Grammar

When designing a new general purpose programming language, or anything beyond a simple DSL, you'll encounter the need to write grammar rules for basic language constructs such as branch operations and loops. Here are a few grammar snippets that I re-use just about every time I design a DSL in ANTLR. Of course, the syntax is up to you to define how you want, but this helps as a general template for frequently occurring language constructs.

Credit is due entirely to Scott Stanchfield. I'd recommend checking out his videos as they are excellent tutorials on writing ANTLR grammars. Most of these constructs are taken directly from his tutorials. I usually find myself copying and pasting these basic grammar rules and then modifying the syntax as I need for the language I'm designing.

Expressions

term
    :   
        IDENT
        | '(' expression ')'
        | INTEGER
    ;
   
negation
    :    'not'* term
    ;
   
unary
    :    ('+' | '-')* negation
    ;
   
mult
    :    unary (('*' | '/' | 'mod') unary)*
    ;
   
add
    :    mult (('+' | '-') mult)*
    ;
   
relation
    :    add (('<' | '<=' | '>' | '>=' | '=' | '!=') add)*
    ;
   
expression
    :    relation (('and' | 'or') relation)*
    ;



Assignment Statements


assignmentStatement
    :    IDENT ':=' expression ';'
    ;



If-Else Statements

ifStatement
    :
        'if' '(' expression ')' statement+
        ('else if' '(' expression ')' statement+)*
        ('else' statement+)*
        'endif'
    ;



Loops

exitStatement
    :
        'exit' 'when' expression ';'
    ;

loopStatement
    :
        'loop'
        (statement|exitStatement)*
        'endloop'
    ;
   
whileStatement
    :
        'while' '(' expression
        (statement|exitStatement)*
        'endloop'
    ;


Fragments

fragment LETTER
    :
        ('a'..'z' | 'A'..'Z')
    ;
   
fragment DIGIT
    :
        '0'..'9'
    ;
   
STRING_LITERAL
    :
        '"' ~('\r' | '\n' | '"')* '"'
    ;
   
INTEGER
    :    ('1'..'9')DIGIT*
    ;

IDENT: LETTER (LETTER | DIGIT)*;

WS: (' ' | '\t' | '\r' | '\n' | '\f')+ {$channel = HIDDEN;};

COMMENT
    :    '//' .* ('\r' | '\n') {$channel = HIDDEN;};
   
MULTILINE_COMMENT
    :    '/*' .* '*/' {$channel = HIDDEN;};



Thursday, October 18, 2012

Implementing a DSL in Java Using the Builder Pattern

DSLs are really just expressive APIs. There are a variety of ways to implement DSLs. The simplest DSL, although limited in its capabilities, is an internal DSL (i.e. a DSL written in an existing programming language). In Java, we can use a builder pattern to design an internal DSL very easily.

Let's say, for example, we want a DSL that we can use to create customer records. In addition to a customer POJO, creating a customer instance would probably look something like what's shown below.

Customer customerOne = new Customer();

customerOne.setFirstName("John");
customerOne.setLastName("Doe");

DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");

try {
    Date dateOfBirth = (Date)(formatter.parse("1971-01-26"));
    customerOne.setDateOfBirth(dateOfBirth);
} catch (ParseException e) {
    System.err.println(e.getMessage());
}


This code doesn't look too bad to a Java developer, but for somebody who isn't overly familiar with Java, this looks cryptic. Not only that, but there is quite a bit of code here that doesn't have anything to do with the customer domain. It's just there because that's what Java requires. We can make this quite a bit better by building a fluent DSL using a builder pattern.

Here's what the code looks like for our builder pattern. Remember, this is the code we developers would write to provide a fluent API for creating customer records. This is the DSL. So, don't be overwhelmed by the amount of code. Our goal here is to provide an easy to use API for creating customer records that hides some of the inherent complexity of the Java code we saw above.

public class Customer {

   static class Builder {

      private String firstName;
      private String lastName;
      private Date dateOfBirth;

      public Builder() {}
     
      public Builder withFirstName(String firstName) {
         this.firstName = firstName;
         return this;
      }

      public Builder withLastName(String lastName) {
         this.lastName = lastName;
         return this;
      }

      public Builder bornOn(String dateOfBirthValue) {
         DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
         try {
            Date dateOfBirth = (Date)(formatter.parse(dateOfBirthValue));
            this.dateOfBirth = dateOfBirth;
         } catch (ParseException e) {
            System.err.println(e.getMessage());
         }

         return this;
      }

      public Customer build() {
         return new Customer(this);
      }
   }

   private final String firstName;
   private final String lastName;
   private final Date dateOfBirth;

   private Customer(Builder b) {
      firstName = b.firstName;
      lastName = b.lastName;
      dateOfBirth = b.dateOfBirth;
   }

   public String toString() {
      return "First Name = " + this.firstName +
             ",Last Name = " + this.lastName +
             ",Date of Birth = " + this.dateOfBirth.toString();
   }

}


Now to create a customer record, we can use the builder pattern we constructed. The result is an expressive API that focuses on the domain (customer records) and hides the innate complexities of Java.


Customer c = new Customer.Builder()
   .withFirstName("John")
   .withLastName("Doe")
   .bornOn("1971-01-26")
   .build();


This type of implementation still comes with some of the nuances of its implementation language. Languages like Groovy and Scala do a better job of creating internal DSLs because of their support for higher-order functions (functions that take other functions as parameters). JDK 8 promises to do that for us and I look forward to being able to take advantage of them. In the meantime, Groovy and Scala are great languages for internal DSL development. I'll cover those examples in a later post. But, there's still alot you can do with internal DSLs in Java 7 and a builder pattern is an easy way to do it.

Monday, October 15, 2012

Calling C++ from Java Using JNI

JNI is part of the Java SDK and we can use it to call native code (e.g. C++) from within our Java applications. This can be very useful when you have existing C++ code that contains business logic that you don't have the time or resources to migrate into your Java application. Let me start by saying that I don't think this should be your first course of action in this scenario. If possible, I think a cleaner approach would be to wrap the C++ implementation in a web service and then call the web service from within your Java application. However, if your Java app will be deployed to the same server as your native code and/or your native code is running on some legacy mainframe that makes it difficult to expose as a web service, JNI can be a very good choice.

Our first step is to code-up our Java application and compile it. Our Java class will look something like this.

public class TestApplication {
    public native int getNumber();

    public static void main(String args[]) {
        System.loadLibrary("TestApplication");
        TestApplication test = new TestApplication();

        System.out.println(test.getNumber());
    }
}

The native keyword in the method declaration simply tells the Java compiler that the getNumber() method is implemented in some native library outside of this Java class. The code inside the main method simply loads the external library (which we'll create in a later step) and calls the native getNumber() method that is implemented in our C++ code.

Next, we need to create a C++ header file that will define our native functions. This header file is not the same header file you may already have in your C++ code. This header file will be created against our TestApplication.class file. To generate this header file, run the following command at the command line from within the same directory as your TestApplication.class file.


>  javah TestApplication

The auto-generated .h file will look like this.

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class TestApplication */

#ifndef _Included_TestApplication
#define _Included_TestApplication
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     TestApplication
 * Method:    getNumber
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_TestApplication_getNumber
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif


The next step is to create the .cpp file that will contain the implementation of our native getNumber() method. Typcially, with JNI development, this C++ code is a wrapper around existing C++ code that we just expose using JNI. In other words, we're not editing the existing C++ library, but instead creating an adapter that will then call our legacy C++ code. Here's the .cpp source file in its entirety.

#include "TestApplication.h"

JNIEXPORT jint JNICALL Java_TestApplication_getNumber
 (JNIEnv *env, jobject obj) {
  return 10;
}


All I'm doing is including the header file that we automatically generated in the previous step and our native method is returning an integer value of 10. This is a very trivial example, but I want to keep it simple for this tutorial. In a later post, I'll talk about the details of JNIEnv, how to pass C++ object instances around, why the JNI methods are named the way they are, etc.. But that deserves a discussion of its own.

Next, we need to create the shared library that we referenced in our Java application. Depending on which C++ compiler you're using, this may vary a little, but the following command will automatically create the shared library if you're using the cl compiler on Windows.

Here are a few things to keep in mind:

  • The -I flag is just telling the C++ compiler to include those directories as include directories.You'll need to modify those to your Java install directory.
  • Make sure you have write permission to the directory where you're trying to create the DLL. If you don't, you'll get an error. Here, I'm just creating it in the local directory
  • Make sure that the C++ compiler you're using is the same architecture as your JVM (i.e. 32-bit or 64-bit).

cl -I"C:\jdk1.6.0_24\include" -I"C:\jdk1.6.0_24\include\win32" -LD TestApplication.cpp -FeTestApplication.dll


Now, if we run our Java program, we'll see the value from our native method being returned to our Java application and the value of 10 being printed to the console. In a later post, I'll talk more about the details of JNI and look at some examples that involve passing objects to our C++ application.

Sunday, October 14, 2012

Is Your Java Code Optimized the Way You Expect?


After you compile your Java code to bytecode, it's interpreted by the JVM when you run the application. During execution of your program, the JVM can, and often will, optimize pieces of your code by compiling it down to native code so that it runs faster. It does this by gathering statistics of your code as it interprets the bytecode and decides if/when it should compile it down to native code. This process is called Just In Time (JIT) compilation.

When fine-tuning your application or trying to debug why a new feature is causing your application to suffer an unexpected performance loss, JIT profiling can be very helpful. Note that all of the examples here are done with HotSpot. Sorry, but I'm not sure what options are available for other JVMs.

The first place to start when wanting information on if and how your code is JITing, is to use the -XX:+PrintCompilation option when running your application. Take for example the following Java program.

class HelloWorld {
    public static int incrementNumber(int number, int value) {
        return number + value;
    }

    public static void main(String args[]) throws InterruptedException {
        int result = 0;
   
        for (int i = 0; i < 100; i++) {
            for (int j = 0; j < 100; j++) {
                result = incrementNumber(i, j);
            }
        }

        System.out.println("Result = " + result);
    }
}


When I run this with the -XX:+PrintCompilation option, I see that the call to "incrementNumber" is being compiled to native code because it's listed in the output. One interesting thing to note is if you change the inner loop from "j < 100" to "j < 99", it won't JIT and that's because in every case that I've seen, HotSpot won't optimize a method call until it's been called 10,000 times. This is because after 10,000 calls to the same method, HotSpot determines that the code is "hot" and optimizes it (hence the name HotSpot).



Here are a few of the optimizations that the HotSpot JVM will utilize to optimize code.

Method Inlining

This is when the JVM replaces a method call with the actual code of the method that's being called. This will usually only occur when a method is small enough to be inlined. Very large methods aren't likely to be inlined. I'm not sure on the exact specifics of HotSpot's implementation there, but that's the gist of it. Here's an example of Java code before-and-after it's inlined. You can see how the call to the method is actually replaced with the body of the method that's being called.

Before:

public int incrementNumber(int number, int value) {
    return number + value;
}

public static void main(String args[]) {
        int result = 0;
   
        for (int i = 0; i < 100; i++) {
            for (int j = 0; j < 100; j++) {
                result = incrementNumber(i, j);
            }
        }

        System.out.println("Result = " + result);
}

After:

public int incrementNumber(int number, int value) {
    return number + value;
}

public static void main(String args[]) {
        int result = 0;
   
        for (int i = 0; i < 100; i++) {
            for (int j = 0; j < 100; j++) {
                result = i + j;
            }
        }

        System.out.println("Result = " + result);
}


If you want to see where HotSpot is inlining your code, run your application using the "-XX:+PrintInlining" option. Here's an example. Notice that you have to use the -XX:+UnlockDiagnosticVMOptions command to utilize -XX:+PrintInlining.




Loop Unrolling

Loop unrolling (sometimes called loop unwinding) occurs when you have a loop that doesn't have a large number of iterations, so the JVM optimizes it by replacing the loop with the series of instructions. Here's an example of a loop before and after it's been unrolled.

Before:
String[] myArray = String[]{"abc", "def", "ghi"};

for (int i = 0; i < myArray.length; i++) {
    System.out.println(myArray[i]);
}

After:
String[] myArray = String[]{"abc", "def", "ghi"};

System.out.println(myArray[0]);
System.out.println(myArray[1]);
System.out.println(myArray[2]);


Eliminating Unnecessary Code

This can often be seen when you perform a bunch of work on a particular object or variable, but never actually do anything with the result of those operations. The JVM may optimize the code by removing it altogether.

Intrinsics

Typically, the JVM will not inline core Java methods. However, for core methods that the JVM knows about and it has an optimized version for the hardware it's running on, it will replace it with the optimized native code that it already has on hand. You'll see Math operations inlined this way. To see it at work, we'll use another option -XX:+LogCompilation. This also requires the -XX:+UnlockDiagnosticVMOptions. Here's some example code:

class HelloWorld {
    public static double calculateExponent(double number, double value) {
        return Math.pow(number, value);
    }

    public static void main(String args[]) throws InterruptedException {
        double result = 0;
   
        for (double i = 0; i < 100; i++) {
            for (double j = 0; j < 100; j++) {
                result = calculateExponent(i, j);
            }
        }

        System.out.println("Result = " + result);
    }
}


When we run this application with the -XX:+LogCompilation example, a hotspot.log file is created in the same directory as our .java file. This is an XML file that dumps out everything that the compiler is doing. If we look at a snippet of the file for the above example, we see that Math.pow is being inlined intrinsically.


This is just a few of the many optimizations that the JVM takes advantage of when interpreting code. For most enterprise applications, super-fast performance isn't usually that critical. However, it is important to have a basic understanding of the JIT compiler, the basic optimizations it does, and how to see when your code is compiling to native code. To summarize, the following options are useful for JVM logging and tuning.

FlagWhat it does
-XX:+PrintCompilationPrints out any calls that are compiled by the JIT
-XX:+PrintInliningPrints out any methods that are inlined
-XX:+LogCompilationCreates a hotspot.log file that reports all of the compilation activities by the JVM

Friday, October 5, 2012

Interpreting an ANTLR Grammar Using a Syntax-Directed Interpreter

There are various types of interpreters we can implement in ANTLR. For very simple DSLs, we can use a syntax-directed interpreter to interpret our language. Note that this only works for simple DSLs. If you're designing a general programming language, you'll need a much more sophisticated interpreter, and I'll cover that in later posts. But, to start with, let's look at a syntax-directed interpreter for a simple DSL that we'll design.

Let's create a DSL that will process customer records. The purpose of a DSL is to create an expressive language, much like a very easy to use API. For this example, our customer DSL will look like this.

Create Customer
With
FirstName "Joe"
LastName "Somebody"
DateOfBirth  "1974-05-04"


Create Customer
With
FirstName "John"
LastName "Doe"
DateOfBirth "1980-01-19"


We've defined how we want our Customer processing DSL to look. In ANTLR, we can create the following grammar to define our language.

grammar CustomerDSL;

options {
  language = Java;
}

rule
    :
        customer*
    ;
   
customer
    :
        'Create' 'Customer'
        'With'
        firstName
        lastName
        dateOfBirth
    ;
   
firstName
    :
        'FirstName' '"' NAME '"'
    ;

lastName
    :
        'LastName' '"' NAME '"'
    ;

dateOfBirth
    :
        'DateOfBirth' '"' date '"'
    ;
   
date
    :
        DIGIT DIGIT DIGIT DIGIT '-' DIGIT DIGIT '-' DIGIT DIGIT
    ;
   
DIGIT
    :
        '0'..'9'
    ;

NAME
    :
        ('A'..'Z') ('a..z' | 'A..Z')*
    ;

WS
    :
        (' ' | '\t' | '\n' | '\r') {$channel = HIDDEN;}
    ;


Next, we need to interpret our DSL script and do something with it. For a simple DSL like this, we can use a sytax-directed interpreter by adding Java instructions at the end of any grammar rule to process the DSL input. After adding the syntax-directed interpreter, our grammar looks like this.


grammar CustomerDSL;

options {
  language = Java;
}

@header {
    import java.util.ArrayList;
    import java.util.Date;
    import java.text.SimpleDateFormat;
    import java.text.DateFormat;
    import java.text.ParseException;
}

@members {
    ArrayList<Customer> customers = new ArrayList<Customer>();
    ArrayList<Customer> getCustomers() {
        return customers;
    }
}

rule
    :
        customer*
    ;
  
customer
    :
        'Create' 'Customer'
        'With'
        f=firstName
        l=lastName
        d=dateOfBirth
        {
            DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
            try {
                Date dateOfBirth = (Date)(formatter.parse($d.dateOfBirthValue));
                customers.add(new Customer($f.firstNameValue, $l.lastNameValue, dateOfBirth));
            } catch (ParseException e) {
                System.err.println(e.getMessage());
            }
        }
    ;

firstName returns [String firstNameValue]
    :
        'FirstName' '"' f=NAME '"' {$firstNameValue = $f.text;}
    ;

lastName returns [String lastNameValue]
    :
        'LastName' '"' l=NAME '"' {$lastNameValue = $l.text;}
    ;

dateOfBirth returns [String dateOfBirthValue]
    :
        'DateOfBirth' '"' d=date '"' {$dateOfBirthValue = $d.text;}
    ;
  
date
    :
        DIGIT DIGIT DIGIT DIGIT '-' DIGIT DIGIT '-' DIGIT DIGIT
    ;
  
DIGIT
    :
        '0'..'9'
    ;

NAME
    :
        ('A'..'Z') ('a'..'z' | 'A'..'Z')*
    ;

WS
    :
        (' ' | '\t' | '\n' | '\r') {$channel = HIDDEN;}
    ;


Within the @header {...} section, we include any libraries we need. Within the @members {...} section, we declare any global variables, etc. Throughout the grammar, wherever we need to run any Java code, we just wrap it in {...} and ANTLR knows that it's custom action code to execute at that point while parsing the grammar.

We're missing 2 things to tie this all together. The first is to create a Customer class so that we can create POJOs for each customer record. Note that we could actually do whatever we want when we interpret our DSL script. For this example, we'll just create a POJO for each customer defined in our script. From there, you could marshal the records to send to some service, insert the records into a database, etc. Here's a simple Customer class.

import java.util.Date;

public class Customer {
    private String firstName;
    private String lastName;
    private Date dateOfBirth;
   
    public Customer() {
    }
   
    public Customer(String firstName, String lastName, Date dateOfBirth) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.dateOfBirth = dateOfBirth;
    }
   
    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public Date getDateOfBirth() {
        return dateOfBirth;
    }

    public void setDateOfBirth(Date dateOfBirth) {
        this.dateOfBirth = dateOfBirth;
    }
   
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("First Name : " + firstName + ", ");
        sb.append("Last Name : " + lastName + ", ");
        sb.append("Date of Birth : " + dateOfBirth.toString());

        return sb.toString();
    }
}


The next step is to create a simple processor/driver program that will read a file containing customer records, run our lexer over our input, pass the results to our parser, and then do something with our Customer instances that are created by our interpreter. Here's a processor that will process our DSL script and create a POJO for each customer that's defined.

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;

import org.antlr.runtime.ANTLRReaderStream;
import org.antlr.runtime.CharStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.TokenStream;


public class CustomerDSLProcessor {
    public static void main(String args[]) {
        String filePath = "scripts\\Customers.dsl";
        Reader reader = null;
      
        try {
            reader = new FileReader(filePath);
        } catch (FileNotFoundException e) {
            System.err.println("Error: Unable to find source code file at " + filePath);
            return;
        }

        try {
            CharStream charStream = new ANTLRReaderStream(reader);
            CustomerDSLLexer lexer = new CustomerDSLLexer(charStream);
            TokenStream tokenStream = new CommonTokenStream(lexer);
            CustomerDSLParser parser = new CustomerDSLParser(tokenStream);

            parser.rule();
            ArrayList customers = parser.getCustomers();
            for (Customer customer : customers) {
                System.out.println(customer.toString());
            }
        } catch (IOException e) {
            System.err.println(e.getMessage());
        } catch (RecognitionException e) {
            e.printStackTrace();
        }
    }
}


Syntax-directed interpreters are very simple and easy to write, but they're also not very practical for any large or complex languages you design. They work just fine for simple external DSLs where you have the ability to interpret the grammar as you're parsing it. However, there are times you'll have complex languages you're designing where the context of grammar rules will make a difference on how things get interpreted and this type of interpreter just doesn't have the power to process it. For that, we need to output an AST and walk or visit the tree to process the language. So, in a later post, I'll talk about more sophisticated interpreters and how we can implement them in Java.