How to implement Interpreter pattern in Java?

Let’s say you are writing code for an application.

You have decided to use only plain JDBC to peform database operations. No ORM (Object Relational Mapping tools).

And so you write plain queries in your application code.

When you run your code , your application starts throwing Query Syntax errors.

You go back to the code and find you have minor errors in constructing the sql queries.

This happens again and again .

Fed up you decide to write a plugin which will check for queries in your application and highlight errors in their syntax as soon as you save the code.

You come to know that Interpreter Pattern helps exactly in doing this.

You decide to adopt it.

And now let’s jump into action together.

Interpreter patterns lets you construct a grammar and then verify expressions against that grammar.

For simplicity sake , let’s take one type of SQL queries , the DELETE query and create a program which checks for errors in the DELETE query syntax.

Our program checks if DELETE query follows the below syntax:

DELETE FROM table_name WHERE field_name = field_value

Let’s make one more assumption that there should be only one condition in the WHERE clause and it checks only for integer values.

So below are the rules any DELETE query should follow :

  • First keyword should be ‘DELETE’
  • Second keyword should be ‘FROM’
  • Third word should be table name which is any valid string. It should not contain special characters or numbers. It can contain underscore characters.
  • Fourth keyword should be ‘WHERE’
  • Fifth, sixth and seventh words together form a condition. They should obey the below rules:
    • Fifth keyword should be a field name which again like table name should be a valid string with the same restrictions.
    • Sixth word can be any of the comparison operators “<“, “>”, “=”,”<=”,”>=”,”!=”
    • Seventh word should be an integer value.

Every DELETE query should follow the above rules. That is its grammar.

In the application we are developing , we have a method which deletes all blog posts which have lower view counts. You like me after putting all the hardwork is disappointed that certain posts are not getting any views. And so ruthlessly you have decided to delete them all using a program.

Here is the client code:

package behavioural.interpreter.pattern;

public class Client {

	public static void main(String a[]) {

		Expression deleteKeyword = new KeywordExpression("DELETE");

		Expression fromKeyword = new KeywordExpression("FROM");

		Expression tableName = new VariableExpression();

		Expression whereKeyword = new KeywordExpression("WHERE");

		Expression fieldName = new VariableExpression();

		Expression compare = new OperatorExpression();

		Expression variable = new ValueExpression();

		Expression conditions = new ConditionsExpression(fieldName, compare, variable);

		Expression expression = new SentenceExpression(deleteKeyword, fromKeyword, tableName, whereKeyword, conditions);

		String sqlQuery = "DELETE FROM BLOG WHERE VIEW_COUNT = 2";

		boolean valid = expression.interpret(sqlQuery);

		if (valid) {
			System.out.println("Your Delete Query is Valid!");
		} else {

			System.out.println("Your Delete Query is Invalid");
		}
	}

}

A lot of expressions there!

Basically you are building an abstract syntax tree. The final expression ‘SentenceExpression’ has the entire grammar we built!.

We can now use this grammar to validate delete queries.

Every expression class implements the method “interpret” from its interface.

This method checks if the given word or set of words follow the defined rules.

For example,

KeywordExpression class checks if the passed keyword is a valid keyword. The valid keyword is the keyword passed in its constructor.

Eg) KeywordExpression(“DELETE”) checks if the keyword entered by user is DELETE . If yes it returns true.

We split the input query into words and pass the first word to the above expression class.

Similarly

KeywordExpression(“FROM”) checks if the second word is “FROM”

KeywordExpression(“WHERE”) checks if the fourth word is “WHERE”

VariableExpression checks for valid table name and field name

OperatorExpression checks for valid operator in the where clause ( view_count < 100 for example)

ValueExpression checks for valid value for the field value in the where clause

Here is the KeywordExpression class:

package behavioural.interpreter.pattern;

public class KeywordExpression implements Expression {

	private String name;

	public KeywordExpression(String name) {

		this.name = name;
	}

	@Override
	public boolean interpret(String word) {

		boolean valid = word.equalsIgnoreCase(this.name);

		if (!valid) {

			System.out.println("Keyword incorrect - Keyword expected is :" + this.name);
		}

		return valid;
	}

}

As you see , the expected keyword is passed as a parameter to its constructor.

And then the interpret() method compares this keyword with the word passed.

Here is the VariableExpression class:

package behavioural.interpreter.pattern;

import java.util.regex.Pattern;

public class VariableExpression implements Expression {

	@Override
	public boolean interpret(String word) {

		Pattern pattern = Pattern.compile("[a-zA-Z_]+");
		boolean valid = pattern.matcher(word).matches();

		if (!valid) {

			System.out.println("Table name or field name has restricted characters");
		}
		return valid;

	}

}

The interpret() method of this class just checks if the given word is a valid string and does not contain any special characters apart from underscore. This is used for validating table name and field name.

Here is the OperatorExpression class:

package behavioural.interpreter.pattern;

import java.util.ArrayList;
import java.util.List;

public class OperatorExpression implements Expression {

	@Override
	public boolean interpret(String word) {

		List<String> operators = new ArrayList<>();

		operators.add("=");
		operators.add("<");
		operators.add(">");
		operators.add("<=");
		operators.add(">=");
		operators.add("!=");

		boolean valid = operators.contains(word);

		if (!valid) {

			System.out.println("Not a valid comparison operator");
		}

		return valid;
	}

}

Here we check for valid comparison operator after the field name in the where clause.

Here is the ValueExpression class:

package behavioural.interpreter.pattern;

import java.util.regex.Pattern;

public class ValueExpression implements Expression {

	@Override
	public boolean interpret(String word) {

		Pattern pattern = Pattern.compile("[0-9]+");

		boolean isValidPattern = pattern.matcher(word).matches();

		if (!isValidPattern) {

			System.out.println("Incorrect value for condition value - An Integer value expected");
		}

		return isValidPattern;
	}

}

It checks if passed word is an integer. This is used to check the word which follows the comparison operator in the where clause.

All the expressions mentioned so far KeywordExpression , VariableExpression , OperatorExpression and ValueExpression are called Terminal Expressions. These don’t depend on other expression classes .

And then comes Non Terminal Expression classes.

These combine the output from Terminal Expression classes to interpret the given word(s).

For example,

ConditionsExpression is a non terminal expression.

It depends on VariableExpression , OperatorExpression and ValueExpression to define its grammar.

Its grammar is:

  • Field name should be a variable
  • Operator should be a valid comparison operator
  • Field value should be an integer.

All the three terminal expressions are passed as a parameter to its constructor . It uses the interpret() method of the terminal expressions to define its own interpret method.

Three words are used by this expression (the field name , comparison operator and field value)

Here is the class:

package behavioural.interpreter.pattern;

import java.util.StringTokenizer;

public class ConditionsExpression implements Expression {

	Expression fieldExpression;

	Expression comparisonExpression;

	Expression valueExpression;

	public ConditionsExpression(Expression fieldName, Expression comparison, Expression variable) {

		this.fieldExpression = fieldName;

		this.comparisonExpression = comparison;

		this.valueExpression = variable;
	}

	@Override
	public boolean interpret(String words) {

		try {

			StringTokenizer tokenizer = new StringTokenizer(words);

			if (tokenizer.countTokens() != 3) {

				System.out.println("Number of words in conditions is incorrect , should be 3");

				return false;
			}

			String fieldName = tokenizer.nextToken();

			String equalsSign = tokenizer.nextToken();

			String variable = tokenizer.nextToken();

			boolean valid = this.fieldExpression.interpret(fieldName) && this.comparisonExpression.interpret(equalsSign)
					&& this.valueExpression.interpret(variable);

			return valid;
		} catch (Exception e) {

			return false;
		}

	}

}

It splits the given string into three (we pass the entire condition clause to it) and checks if first word matches the rule set by VariableExpression , second one passses the rule set by OperatorExpression and the final one passes the rule set by ValueExpression.

If all passes then it returns true else false.

Finally we have the SentenceExpression which is the parent expression. It takes help of all the terminal and non terminal expressions to validate the given query:

package behavioural.interpreter.pattern;

import java.util.StringTokenizer;

public class SentenceExpression implements Expression {

	Expression deleteExp;

	Expression fromExp;

	Expression tableExp;

	Expression whereExp;

	Expression condtionsExp;

	public SentenceExpression(Expression deleteKeyword, Expression fromKeyword, Expression tableName,
			Expression whereKeyword, Expression conditions) {

		this.deleteExp = deleteKeyword;

		this.fromExp = fromKeyword;

		this.tableExp = tableName;

		this.whereExp = whereKeyword;

		this.condtionsExp = conditions;

	}

	@Override
	public boolean interpret(String sentence) {

		try {

			StringTokenizer tokenizer = new StringTokenizer(sentence);

			String delete = tokenizer.nextToken();

			String from = tokenizer.nextToken();

			String table = tokenizer.nextToken();

			String where = tokenizer.nextToken();

			String conditions = sentence.substring(sentence.indexOf(where) + where.length()).trim();

			boolean valid = this.deleteExp.interpret(delete) && this.fromExp.interpret(from)
					&& this.tableExp.interpret(table) && this.whereExp.interpret(where)
					&& this.condtionsExp.interpret(conditions);

			return valid;

		} catch (Exception e) {

			return false;
		}

	}

}

As you can see in the interpret method , it splits the given query sentence into words and passes them to all Terminal and Non Terminal expression classes. If all of them say the rules defined by them pass, the SentenceExpression says that the delete query is valid.

Here is the Expression interface which is adopted by all Expression classes:

package behavioural.interpreter.pattern;

public interface Expression {

	boolean interpret(String sqlQuery);

}

Here is the output of the client code:

Your Delete Query is Valid!

We passed the below query for validation :

String sqlQuery = "DELETE FROM BLOG WHERE VIEW_COUNT = 2";

Now let’s tweak the query and run the code :

Instead of “DELETE” keyword let’s use “SELECT” keyword:

String sqlQuery = "SELECT FROM BLOG WHERE VIEW_COUNT = 2";

Here is the ouput:

Keyword incorrect - Keyword expected is :DELETE
Your Delete Query is Invalid

Let’s tweak it again , lets use a special character in the table name:

String sqlQuery = "DELETE FROM BLOG# WHERE VIEW_COUNT = 2";

Here is the output :

Table name or field name has restricted characters
Your Delete Query is Invalid

Let’s do one more tweak. Let’s replace the comparison operator with a word:

String sqlQuery = "DELETE FROM BLOG WHERE VIEW_COUNT hello 2";

Here is the output:

Not a valid comparison operator
Your Delete Query is Invalid

That’s it!

Now you can code your application without worrying about getting the delete query syntax wrong.

Thanks to the Interpreter pattern!

Here is what the Gang of Four say :

Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language

Here is the code :

https://github.com/vijaysrj/designPatternsGoF/tree/master/src/behavioural/interpreter/pattern


Posted

in

,

by

Comments

Leave a Reply

Discover more from The Full Stack Developer

Subscribe now to keep reading and get access to the full archive.

Continue reading