dcsimg
July 5, 2020
Hot Topics:

Working With Design Patterns: Interpreter

  • By Jeff Langr
  • Send Email »
  • More Articles »

Well, that's nice, but it still leaves some redundancy that I must deal with. When adding a new Expression type, I have to add code to both createExpression and isKeyword. That's not only painful but increases risk. A bit of reflection closes off the code in both methods, in favor of a single map that must be initialized (see Listing 16). Adding new expression classes requires updating code only in this one place.

Listing 16: An appropriate use of reflection?

import java.util.*;

public class Parser {
   private List<String> arguments = new ArrayList<String>();
   private Map<String,Class<? extends Expression>>
      expressionTypes = new HashMap<String,Class<?
      extends Expression>>();
   {
      expressionTypes.put("contains", Contains.class);
      expressionTypes.put("olderThan", OlderThan.class);
   }

   public Expression parse(String expressionText) {
      String command = null;
      String[] tokens = expressionText.split(" ");
      for (String token: tokens)
         if (isKeyword(token))
            command = token;
         else
            arguments.add(token);

      Expression expression = createExpression(command);
      expression.setArgs(arguments);
      return expression;

   }

   private Expression createExpression(String command) {
      try {
         return expressionTypes.get(command).newInstance();
      } catch (Exception e) {
         throw new RuntimeException(e);
      }
   }

   private boolean isKeyword(String token) {
      return expressionTypes.containsKey(token);
   }
}

Not bad. I could make the case for splitting out the creation code into a separate factory class, but I'll do that on my own time.

A new test for "and" expressions (see Listing 17) fails.

Listing 17: Driving support for non-terminal expressions.

@Test
public void andExpression() {
   Expression expression =
      parser.parse("olderThan 03/31/2008 and contains java");
   And and = (And)expression;
   OlderThan olderThan = (OlderThan)and.getLeft();
   assertDate(2008, Calendar.MARCH, 31, olderThan.getDate());

   Contains contains = (Contains)and.getRight();
   assertKeywords((Contains)contains, "java");
}

The implementation requires a bit of rethinking the algorithm, but at least many of the required components are in place, so it doesn't take too long (see Listing 18).

Listing 18: Supporting non-terminal expressions.

import java.util.*;

public class Parser {
   private List<String> arguments = new ArrayList<String>();
   private Map<String, Class<? extends Expression>>
      expressionTypes =
      new HashMap<String, Class<? extends Expression>>();
   {
      expressionTypes.put("contains", Contains.class);
      expressionTypes.put("olderThan", OlderThan.class);
      expressionTypes.put("and", And.class);
   }

   private Expression current;
   private List<Expression> expressions =
      new ArrayList<Expression>();

   public Expression parse(String expressionText) {
      String[] tokens = expressionText.split(" ");
      for (String token: tokens)
         if (isKeyword(token)) {
            storeArguments();
            current = createExpression(token);
            if (isProcessingBinaryExpression()) {
               And and = (And)pop();
               Expression left = pop();
               and.set(left, current);
               push(and);
            } else
               push(current);
         } else
            arguments.add(token);

      storeArguments();
      return pop();
   }

   private boolean isProcessingBinaryExpression() {
      return expressions.size() == 2;
   }

   private void storeArguments() {
      if (current == null)
         return;
      current.setArgs(arguments);
      arguments = new ArrayList<String>();
   }

   private boolean push(Expression expression) {
      return expressions.add(expression);
   }

   private Expression pop() {
      return expressions.remove(expressions.size() - 1);
   }

   private Expression createExpression(String command) {
      try {
         return expressionTypes.get(command).newInstance();
      } catch (Exception e) {
         throw new RuntimeException(e);
      }
   }

   private boolean isKeyword(String token) {
      return expressionTypes.containsKey(token);
   }
}




Page 5 of 6



This article was originally published on May 22, 2008

Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.


Thanks for your registration, follow us on our social networks to keep up-to-date