com.google.api.tools.framework.util.GenericVisitor Maven / Gradle / Ivy
/*
* Copyright (C) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2012 Google Inc. All Rights Reserved.
package com.google.api.tools.framework.util;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import java.lang.reflect.InvocationTargetException;
import net.sf.cglib.reflect.FastMethod;
/**
* A generic visitor class. Implements the visitor style pattern for depth-first traversal
* of object graphs using dynamic dispatching based on method annotations.
*
* An implementation will typically derive from this class an abstract class for a given object
* model which defines how to visit children by providing methods annotated with
* {@link Accepts}. A concrete visitor is then defined by deriving another class which defines
* visitors annotated with {@link Visits} and/or {@link VisitsBefore} and {@link VisitsAfter}.
*
*
The generic {@link #visit} method first calls @VisitsBefore if present, then
* calls @Visit if present, and finally @VisitsAfter if present. If no method annotated
* with @Visits is defined, {@link #defaultVisit} will be called instead. This method in turn
* calls {@link #accept} to traverse down the tree.
*
*
As a methodological guidance, you should use @VisitsBefore if you do a proper pre-order
* traversal, @VisitsAfter if you do a proper post-order traversal, and @Visits only if
* you do a mix (i.e. need to do some mutual dependent computation before and after
* descending).
*
*
The method annotated with @VisitsBefore can return a boolean value, which if false
* will prevent calls to any @Visits and @VisitsAfter methods for the current element
* (which will prevent the automatic traversal of child elements).
*
*
More formally, the abstract pseudo code for the {@link #visit} method is:
*
* void visit(x) {
* if (hasVisitsBefore(x) && !visitsBefore(x)) return;
* if (hasVisits(x))
* visits(x);
* else
* defaultVisit(x);
* if (hasVisitsAfter(x)) visitsAfter(x);
* }
* void defaultVisit(x) {
* accept(x);
* }
*
*
* Example:
*
* Suppose a simple object model for expressions:
*
* abstract Expr { ... }
* class Add extends Expr {
* Expr left;
* Expr right;
* }
* class Value extends Expr {
* int value;
* }
*
* We can now define a general expression visitor as follows (this base class can be used
* for various concrete visitors on expressions):
*
* abstract class ExprVisitor extends GenericVisitor<Expr> {
* ExprVisitor() { super(Expr.class) }
* {@literal @}Accepts void accept(Add add) {
* visit(add.left); visit(add.right);
* }
* }
*
* Next we define a concrete visitor as below:
*
* class LeafCounter extends ExprVisitor {
* int counter = 0;
* {@literal @}VisitsAfter void count(Value val) {
* counter++;
* }
* }
*
* As another example, consider an evaluator:
*
* class Evaluator extends ExprVisitor {
* Stack<Integer> stack;
* {@literal @}VisitsAfter void eval(Add add) {
* stack.push(stack.pop() + stack.pop());
* }
* {@literal @}VisitsAfter void eval(Value val) {
* stack.push(val.value));
* }
* }
*
* The following funny evaluator uses the @Visits annotation instead of @VisitsAfter as it does
* some computation before descending:
*
*
* class FunnyEvaluator extends ExprVisitor {
* Stack<Integer> stack;
* {@literal @}Visits void eval(Add add) {
* int x = someFunction(); // compute some stuff
* accept(add);
* stack.push(x + stack.pop() + stack.pop());
* }
* {@literal @}VisitsAfter void eval(Value val) {
* stack.push(val.value));
* }
* }
*
* Here is a visitor which uses @VisitsBefore to replace the current visitor with another
* visitor if some condition is met:
*
*
* class LeafCounter extends ExprVisitor {
* int counter = 0;
* {@literal @}VisitsBefore boolean count(Expr expr) {
* if (someCondition(expr)) {
* // use a different visitor
* new DecreasingLeafCounter().visit(expr);
* return false; // stop with this visitor
* }
* return true; // continue with this visitor
* }
* {@literal @}VisitsAfter void count(Value val) {
* counter++;
* }
* class DecreasingLeafCounter extends ExprVisitor {
* {@literal @}VisitsAfter void count(Value val) {
* counter--;
* }
* }
* }
*
* A few additional remarks:
*
* - Methods are dispatched based on the type of their argument. If a precise match
* is not found, the superclass is tried, and so on, until the base type of the visitor
* is reached. This matches the standard behavior for method dispatching in Java.
*
* - Methods from base types are considered for resolution.
*
* - Methods must not be private or protected and not static to be able to be called via cglib's
* fast reflection support.
*
* - Use {@code Object} as your base type if the object model doesn't have a more
* specific root type.
*
* - You can customize default visitors for elements and children by overriding the
* methods {@link #defaultVisit(Object)} and {@link #defaultAccept(Object)}. See
* the javadoc of the available methods for more details.
*
*
*
*/
public abstract class GenericVisitor {
private final Class baseType;
private final Dispatcher visits;
private final Dispatcher accepts;
private final Dispatcher before;
private final Dispatcher after;
/**
* Constructs a generic visitor with {@code baseType} being the root type of the
* object model. Pass {@code Object.class} if the object model doesn't has such a type.
*/
protected GenericVisitor(Class baseType) {
this.baseType = Preconditions.checkNotNull(baseType);
this.visits = Dispatcher.getDispatcher(baseType, Visits.class, this.getClass());
this.accepts = Dispatcher.getDispatcher(baseType, Accepts.class, this.getClass());
this.before = Dispatcher.getDispatcher(baseType, VisitsBefore.class, this.getClass());
this.after = Dispatcher.getDispatcher(baseType, VisitsAfter.class, this.getClass());
}
/**
* The default method which is invoked if no explicit @Visits method is found for
* a type. Forwards to {@link #accept(Object)}. Override this to change this
* behavior.
*/
protected void defaultVisit(BaseType instance) {
accept(instance);
}
/**
* The default method which is invoked if no explicit @Accepts method is found for
* a type. Does nothing. Override this to change this behavior.
*/
protected void defaultAccept(BaseType instance) {
}
/**
* Visits a given instance. Dispatches to a @Visits method if one
* is found, otherwise calls {@link #defaultVisit(Object)}.
*
* If a @BeforeVisits method with matching type is defined, it
* will be executed before @Visits. Such a method can return a boolean which,
* if false, lets visitation stop at this point.
*
*
If a @AfterVisits method with matching type is defined, it
* will be executed after @Visits.
*
*/
public final void visit(BaseType instance) {
if (!dispatchBefore(before, instance)) {
// stop visitation at this point
return;
}
if (!dispatch(visits, instance)) {
defaultVisit(instance);
}
dispatch(after, instance);
}
/**
* Visits the children of a given instance. Dispatches to a @Accepts method if one
* is found, otherwise calls {@link #defaultAccept(Object)}. Call this in your @Visits
* method to traverse children. Depending on where the call is placed, pre-order or
* post-order traversal can be realized.
*
*
@Accepts methods are usually provided in a base class visitor for the particular
* data structure to be visited, from which a concrete visitor derives.
*/
protected final void accept(BaseType instance) {
if (!dispatch(accepts, instance)) {
defaultAccept(instance);
}
}
private boolean dispatch(Dispatcher dispatcher, BaseType instance) {
FastMethod method = getMethod(dispatcher, instance);
if (method == null) {
return false;
}
try {
method.invoke(this, new Object[]{ instance });
return true;
} catch (InvocationTargetException e) {
throw Throwables.propagate(e.getCause());
}
}
// Dispatching the @VisitsBefore is slightly different then the reset,
// as we need to determine whether to continue
private boolean dispatchBefore(Dispatcher dispatcher, BaseType instance) {
FastMethod method = getMethod(dispatcher, instance);
if (method == null) {
return true; // non-presence of before method means continue execution
}
try {
Object result = method.invoke(this, new Object[]{ instance });
if (method.getReturnType().equals(Boolean.TYPE)) {
return Boolean.class.cast(result); // method determines whether to continue
}
return true;
} catch (InvocationTargetException e) {
throw Throwables.propagate(e.getCause());
}
}
@SuppressWarnings("unchecked") // ensure by construction
private FastMethod getMethod(Dispatcher dispatcher, BaseType instance) {
Preconditions.checkNotNull(instance, "instance");
return dispatcher.getMethod((Class extends BaseType>) instance.getClass());
}
}