Skip to content

APT Visitor

Adrian edited this page Sep 13, 2021 · 1 revision

Visitor

The visitor pattern is a design pattern for software engineering. It was defined in 1994 in the book: "Design Patterns: Elements of Reusable Object-Oriented Software". The visitor pattern is designed to traverse recurring structures. In our case, it is used to traverse the APT. Our implementation traverses the APT similar to depth first search, with a priority on the on nodes that are executed before. A visitor can be implemented by creating a new class which implements the interface APTVisitor.

Implementation

The visitor pattern consists of 5 defining functions.

1. Accept

The accept function is defined in the data structure to be traversed. It defines the entry point for a visitor i.e. the starting node for the traversion. The accept function takes a visitor as an argument. The following example shows how the instantiation node of an APT node calls the accept method on the instantiation someVisitor of a visitor.

node.accept(someVisitor);

1. Accept

The accept function is defined in the data structure to be traversed. It defines the entry point for a visitor i.e. the starting node for the traversion. The accept function takes a visitor as an argument. The following example shows how the instantiation node of an APT node calls the accept method on the instantiation someVisitor of a visitor.

node.accept(someVisitor);

2. Handle

The handle function defines how the visitor operates and should not be changed.

3. Visit

The the visit function is always call when the visitor first encounters a node. To define the behaviour of a visitor, this function can be overwritten in a specific visitor implementation. This function does nothing, if not explicitly changed. The following example shows how the function can be overwritten. In this example the visit function prints the name of the functions that are called.

@Override
public void visit(CallNode node) {
  print(node.getFunctionIdentifier());
}

4. Traverse

The traverse function recursivly defines the traversal strategy. It is called in succession to the visit function. The traverse function can be overwritten for each node individually to redefine the traversal strategie. The example shows the predefined traversal strategie.

public void traverse(CallNode node){
  for (PatternNode child: node.getChildren()) {
    child.accept(getRealThis());
  }
}

5. endVisit

The endVisit function is similar to the visit function with the difference, that it is called after traversing the child nodes.

Example

The following example shows how we could print the nesting of function calls. Where for each call we open a section denoted by "[ ]" where all descendent calls are enclosed in these brackets. The real this parameter and functions are used clarify that always the same instantiation is used.

public class CallNesting implements APTVisitor{
  String nesting = "";

  @Override
  public void visit(Call node) {
    nesting = nesting + node.getgetFunctionIdentifier() + "\n[\n";
  }

  @Override
  public void endVisit(Call node) {
    nesting = nesting + "]\n";
  }

  /**
  * Visitor support functions.
  */
  private PatternDSLVisitor realThis = this;

  @Override
  public PatternDSLVisitor getRealThis() {
    return realThis;
  }

  @Override
  public void setRealThis(PatternDSLVisitor realThis) {
    this.realThis = realThis;
  }
}

Extended Visitor [TODO]