Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

The Visitor Pattern and a Java Grep Utility


The Visitor Pattern and a Java Grep Utility

David is a developer for CrossLogic Corp. He can be contacted at [email protected].


Patterns are solutions to problems you consistently encounter during software development. All phases of software development have patterns -- version control, documentation, debugging, performance tuning, testing, and (of course) code production.

The Visitor pattern is a behavioral pattern that enables an enumeration of objects (visitors) to implement operations performed on a subject-object structure, without the subject-object structure having to retain knowledge of a visiting object (Figure 1). New operations can be implemented simply by allowing subjects to accept concrete visitor objects. Visitor behavior is exercised with a polymorphic message, passing the subject as an argument (an approach known as "double dispatching"). Subject-object structures are unaffected by adding new operations. However, changes to the subject-object structure do affect the visitor hierarchy.

How does the subject-object structure implement and invoke visitor objects? Visitor objects obtain a reference to the subject object they are "visiting," thus subject-object structure is made available. Visitor objects are free to implement designed operations using the subject-object structure.

When should the Visitor pattern be applied? This pattern is useful when an object structure is static and does not change, but an implementation requires new or different operations to be defined. Of course, there is a drawback. Changes to an object structure must be reflected in the visitor classes affected by the change. Thick visitor hierarchies could present a maintenance burden. Therefore, stable object structures are better suited to the pattern.

Java Grep

One application of the Visitor pattern involves the familiar grep utility, which searches for a string pattern in a file. Implementing the utility in Java is straightforward. You open a file, read each line, check for a pattern match, and output if a match occurs. At first glance, you might consider defining a single class that implements all this functionality. A more object-oriented design, however, promotes code reuse.

A Grep class (available electronically; see "Resource Center," page 3) is defined as the primary class that uses a file processor to read a file. It also uses a pattern-matching class, defined in a GrepVisitor hierarchy. Partitioning behavior in this way allows the file-processor class to be used by other classes. The significance of the visitor class abstraction allows different pattern-matching operations to be defined independent of the grep and file-processing classes; see Figure 2.

The Grep Class

The Grep class defines the behavior necessary to obtain a file or directory and a pattern to match. It then scans a file for lines matching the pattern.

Reading a file line by line is performed by the FileProcessor class. An instance of this object is assigned to the fileProcessor variable. The ability to grep one or many files at once results in an instance variable used to store paths typed as a Vector. Since pattern-matching behavior is partitioned to another class, the pattern needs to be available during the lifetime of a Grep instance. Therefore, it will be assigned to an instance variable named pattern. Likewise, each line read by the file-processor object needs to be available for pattern matching and, thus, is stored in an instance variable named buffer. For convenience, a constructor is implemented that takes a file or directory path and a pattern string to match.

Pattern matching occurs when a process() message is sent to the Grep instance. The process() method iterates over defined file paths, requesting one line at a time from a file. Each line is temporarily stored in the buffer instance variable. Once a line is obtained, the current pattern-matching visitor object is sent the visit() method with the current Grep object as an argument.

Lines matching a pattern are displayed (with line numbers) to the console. A method is defined that outputs the current line number and buffer to the console.

FileProcessor objects are assigned in the class definition template. Visitor objects are assigned to the Grep objects by defining an acceptVisitor method that takes an argument of type GrepVisitor; see Listing One

File Processor

The behavior required to read a file line by line is defined in the FileProcessor class. Partitioning this behavior in a separate class enables reuse. (For that matter, a URL processor could be introduced with minimal impact on the Grep class.)

Processing a file is accomplished using the ReadInputStream class defined in the Java.io package. An instance variable is defined for a file name, which is then passed in as an argument of the FileProcessor() constructor.

FileProcessor objects define the method read(), which reads a line of a file returned as a String. Returning a Null indicates End of File. This allows the Grep object to iterate over a file.

A Pattern-Matching Visitor

Pattern-matching operations are defined and enumerated in a GrepVistor hierarchy. An abstract class GrepVisitor is defined that implements a subject instance variable along with abstract methods match() and report(). An inherited method visit() exercises these methods, which are implemented by concrete classes. The visit() method takes a Subject object that will be visited as an argument.

Class GrepSimplePatternVisitor (see Listing Two) extends this class and implements concrete match() and report() methods. The visitor object checks to see if a pattern is found in the current line of the text file being processed; if so, it is reported.

Why is this significant? Why not just define this behavior as a method in the grep utility? Suppose the Grep class is defined with a method that performs visitor-object pattern matching. You are then given another requirement -- to traverse and grep URLs. Well, the current grep utility is outfitted to iterate over file paths using a file-processor object. URLs eventually result in text-based Internet standard documents (HTML), so pattern matching can be reused by creating a URL grep and accepting a visitor object. The alternative is to cut and paste.

The Visitor pattern can be further illustrated by creating concrete visitor classes that pattern match a line of text for Java message signatures being sent or defined. Different pattern-matching visitor objects can be created and accepted by the utility by defining an instance variable that indicates the type of visitor to create.

Of course, another behavioral design pattern, the State pattern, could be used if the number of visitors required an ugly case statement. However, that's beyond the scope of this article.

The Grep class defines a static main method allowing the utility to be used from a command line. A Grep object is created using command-line arguments and a GrepSimplePatternVisitor object is created and accepted by the Grep object. Then the Grep object is sent the process() method. Results are output to the console; see Listing Three.

DDJ

Listing One

public void acceptVisitor( GrepVisitor aVisitor)
        Visitor = aVisitor;

Back to Article

Listing Two

/** Return true if current line contains pattern *  @return boolean
 */
public boolean match() {
        String aLine = subject.processor.currentLine;
         if (aLine.toUpperCase().indexOf( subject.pattern.toUpperCase() ) 
                                                        > 0){ return true; }
     return false;
}
/** Output file, linenumber and line to console. */
public void report() {
     String printString;
     printString = subject.currentFileName+"("+subject.lineNumber+") - 
                                         "+subject.processor.currentLine;
     System.out.println(printString);
     return;

Back to Article

Listing Three

/** Invoke Utility from command line *  @param args java.lang.String[]
 */
public static void main(String args[]) {
    if (args.length < 2) 
        { System.out.println("Usage: java Grep <file path>, <pattern>");
            return ;}   
    Grep aGrep = new Grep();
    aGrep.filePath = args[0];
    aGrep.pattern = args[1];
    // Create and accept a Visitor
    aGrep.acceptVisitor( new GrepSimplePattern() );
    aGrep.process();
    return;
}


</p>

Back to Article


Copyright © 1998, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.