December 21, 2014
Hot Topics:

Working With Design Patterns: Interpreter

  • May 22, 2008
  • 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



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

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

Sitemap | Contact Us

Rocket Fuel