public Object visit(ReturnStatement node) {
            // Skip traversing this node altogether.
            mReturnAdded = true;
            return node;
        }
    }

    /**
     * Wraps all statements that can throw an exception with an
     * ExceptionGuardStatement.
     */
    private static class ExceptionGuardian extends TreeMutator {
        public Object visit(AssignmentStatement node) {
            if (!node.getRValue().isExceptionPossible()) {
                return node;
            }

            SourceInfo info = node.getSourceInfo();
            VariableRef lvalue = node.getLValue();

            // Replacement assigns null to lvalue.
            Statement replacement =
                new AssignmentStatement(info, lvalue, new NullLiteral(info));

            return new ExceptionGuardStatement(node, replacement);
        }

        public Object visit(ForeachStatement node) {
            Block body = node.getBody();
            if (body != null) {
                node.setBody(visitBlock(body));
            }

            boolean guard = false;
            test: {
                Expression range = node.getRange();
                if (range != null) {
                    if (range.isExceptionPossible()) {
                        guard = true;
                        break test;
                    }
                    Type type = range.getType();
                    if (type != null && type.isNullable()) {
                        guard = true;
                        break test;
                    }
                }

                range = node.getEndRange();
                if (range != null) {
                    if (range.isExceptionPossible()) {
                        guard = true;
                        break test;
                    }
                    Type type = range.getType();
                    if (type != null && type.isNullable()) {
                        guard = true;
                        break test;
                    }
                }
            }

            if (!guard) {
                return node;
            }

            return new ExceptionGuardStatement(node, node.getInitializer());
        }

        public Object visit(IfStatement node) {
            Block block = node.getThenPart();
            if (block != null) {
                node.setThenPart(visitBlock(block));
            }

            block = node.getElsePart();
            if (block != null) {
                node.setElsePart(visitBlock(block));
            }

            if (!node.getCondition().isExceptionPossible()) {
                return node;
            }

            Variable[] vars = node.getMergedVariables();
            int length;

            if (vars == null || (length = vars.length) == 0) {
                return new ExceptionGuardStatement(node, null);
            }

            // Create replacement assignments to ensure merged variables get
            // assigned null.
            Statement[] replacements = new Statement[length];
            SourceInfo info = node.getSourceInfo();

            for (int i=0; i<length; i++) {
                Variable v = vars[i];
                VariableRef lvalue = new VariableRef(v.getSourceInfo(),
                                                     v.getName());
                lvalue.setVariable(v);

                replacements[i] = new AssignmentStatement
                    (info, lvalue, new NullLiteral(info));
            }

            Statement replacement = new StatementList(info, replacements);
            return new ExceptionGuardStatement(node, replacement);
        }

        public Object visit(SubstitutionStatement node) {
            return new ExceptionGuardStatement(node, null);
        }

        public Object visit(ExpressionStatement node) {
            Expression expr = node.getExpression();
            if (expr != null) {
                if (expr instanceof CallExpression) {
                    expr = (CallExpression)expr.accept(this);
                    node.setExpression(expr);
                }
                if (expr.isExceptionPossible()) {
                    return new ExceptionGuardStatement(node, null);
                }
            }
            return node;
        }

        public Object visit(ReturnStatement node) {
            Expression expr = node.getExpression();
            if (expr instanceof CallExpression) {
                node.setExpression((CallExpression)expr.accept(this));
            }
            return node;
        }

        public Object visit(FunctionCallExpression node) {
            return visit((CallExpression)node);
        }

        public Object visit(TemplateCallExpression node) {
            return visit((CallExpression)node);
        }

        private Object visit(CallExpression node) {
            Block subParam = node.getSubstitutionParam();
            if (subParam != null) {
                node.setSubstitutionParam(visitBlock(subParam));
            }
            return node;
        }
    }

    /**
     * Reduces the amount of string concatenation operations performed by
     * breaking up ExpressionStatements that encapsulate ConcatenateExpressions
     * into several individual ExpressionStatements. Concatenations that are
     * enclosed in parenthesis are not broken up.
     */
    private static class ConcatenationReducer extends TreeMutator {
        public Object visit(ExpressionStatement node) {
            // Recurse into node.
            super.visit(node);

            Expression expr = node.getExpression();
            if (!(expr instanceof ConcatenateExpression)) {
                return node;
            }

            Collection<Statement> statements = new ArrayList<Statement>();
            breakup(statements, (ConcatenateExpression)expr);

            Statement[] stmts = new Statement[statements.size()];
            stmts = statements.toArray(stmts);

            return new StatementList(node.getSourceInfo(), stmts);
        }

        private void breakup(Collection<Statement> statements,
                             ConcatenateExpression concat) {

            Expression left = concat.getLeftExpression();
            if (left instanceof ConcatenateExpression) {
                breakup(statements, ((ConcatenateExpression)left));
            }
            else {
                statements.add(new ExpressionStatement(left));
            }

            Expression right = concat.getRightExpression();
            if (right instanceof ConcatenateExpression) {
                breakup(statements, ((ConcatenateExpression)right));
            }
            else {
                statements.add(new ExpressionStatement(right));
            }
        }
    }

    /**
     * Increases the amount of string concatenation operations by finding
     * consecutive ExpressionStatements and merging them together into a
     * single ExpressionStatement that operates on the concatenated results.
     */
    @SuppressWarnings("unused")
    private static class ConcatenationIncreaser extends TreeMutator {
        public Object visit(StatementList node) {
            // Recurse into node.
            super.visit(node);

            return new StatementList(node.getSourceInfo(),
                                     visit(node.getStatements()));
        }

        public Object visit(Block node) {
            // Recurse into node.
            super.visit(node);

            return new Block(node.getSourceInfo(),
                             visit(node.getStatements()));
        }

        private Statement[] visit(Statement[] stmts) {
            int length = stmts.length;

            Collection<Statement> statements = new ArrayList<Statement>();
            List<ExpressionStatement> expressionStatements =
                new ArrayList<ExpressionStatement>();

            for (int i=0; i<length; i++) {
                Statement stmt = stmts[i];
                if (stmt instanceof ExpressionStatement) {
                    Expression expr =
                        ((ExpressionStatement)stmt).getExpression();

                    if (!(expr instanceof CallExpression)) {
                        expressionStatements.add((ExpressionStatement) stmt);
                        continue;
                    }
                }

                merge(statements, expressionStatements);
                expressionStatements.clear();
                statements.add(stmt);
            }

            merge(statements, expressionStatements);

            stmts = new Statement[statements.size()];
            return statements.toArray(stmts);
        }

        private void merge(Collection<Statement> statements,
                           List<ExpressionStatement> expressionStatements) {
            int size = expressionStatements.size();

            if (size == 0) {
            }
            else if (size == 1) {
                statements.add(expressionStatements.get(0));
            }
            else {
                List<Expression> expressions = new ArrayList<Expression>();

                for (int i=0; i<size; i++) {
                    ExpressionStatement stmt = expressionStatements.get(i);
                    gatherExpressions(expressions, stmt.getExpression());
                }

                size = expressions.size();
                Expression concat = expressions.get(0);

                for (int i=1; i<size; i++) {
                    Expression right = expressions.get(i);
                    SourceInfo rightInfo = right.getSourceInfo();

                    SourceInfo info =
                        concat.getSourceInfo().setEndPosition(rightInfo);

                    Token token = new Token(rightInfo, Token.CONCAT);

                    concat = new ConcatenateExpression
                        (info, token, concat, right);
                }

                statements.add(new ExpressionStatement(concat));
            }
        }

        private void gatherExpressions(Collection<Expression> expressions,
                                       Expression expr) {
            if (expr instanceof ConcatenateExpression) {
                ConcatenateExpression concat = (ConcatenateExpression)expr;
                gatherExpressions(expressions, concat.getLeftExpression());
                gatherExpressions(expressions, concat.getRightExpression());
            }
            else {
                expressions.add(expr);
            }
        }
    }

    // TODO: move this class to Trove maybe?
    public static class GenericArrayTypeImpl
        implements java.lang.reflect.GenericArrayType {

        private java.lang.reflect.Type componentType;

        public GenericArrayTypeImpl(java.lang.reflect.Type componentType) {
            this.componentType = componentType;
        }

        @Override
        public java.lang.reflect.Type getGenericComponentType() {
            return componentType;
        }

        @Override
        public int hashCode() {
            int hash = (17 * this.componentType.hashCode());
            return hash;
        }

        @Override
        public boolean equals(Object object) {
            if (object == this) { return true; }
            else if (!(object instanceof java.lang.reflect.GenericArrayType)) {
                return false;
            }

            java.lang.reflect.GenericArrayType other =
                (java.lang.reflect.GenericArrayType) object;
            if (!this.getGenericComponentType()
                .equals(other.getGenericComponentType())) { return false; }

            return true;
        }

        @Override
        public String toString() {
            StringBuilder buffer = new StringBuilder(128);
            buffer.append(this.getGenericComponentType()).append("[]");
            return buffer.toString();
        }
    }

    public static class ParameterizedTypeImpl
        implements java.lang.reflect.ParameterizedType {

        private java.lang.reflect.Type rawType;
        private java.lang.reflect.Type ownerType;
        private java.lang.reflect.Type[] typeArguments;

        public ParameterizedTypeImpl(java.lang.reflect.Type rawType,
                                     java.lang.reflect.Type[] typeArguments) {
            this.rawType = rawType;
            this.typeArguments = typeArguments;
        }

        public ParameterizedTypeImpl(java.lang.reflect.Type ownerType,
                                     java.lang.reflect.Type rawType,
                                     java.lang.reflect.Type[] typeArguments) {
            this.rawType = rawType;
            this.ownerType = ownerType;
            this.typeArguments = typeArguments;
        }

        @Override
        public java.lang.reflect.Type getOwnerType() {
            return this.ownerType;
        }

        @Override
        public java.lang.reflect.Type getRawType() {
            return this.rawType;
        }

        @Override
        public java.lang.reflect.Type[] getActualTypeArguments() {
            return this.typeArguments;
        }

        @Override
        public int hashCode() {
            int hash = 17;
            hash += (19 * this.rawType.hashCode());
            for (java.lang.reflect.Type argument : this.typeArguments) {
                hash += (23 * argument.hashCode());
            }

            return hash;
        }

        @Override
        public boolean equals(Object object) {
            if (object == this) { return true; }
            else if (!(object instanceof java.lang.reflect.ParameterizedType)) {
                return false;
            }

            java.lang.reflect.ParameterizedType other =
                (java.lang.reflect.ParameterizedType) object;
            if (!this.getRawType().equals(other.getRawType())) { return false; }

            java.lang.reflect.Type[] thisArguments =
                this.getActualTypeArguments();
            java.lang.reflect.Type[] otherArguments =
                other.getActualTypeArguments();

            if (thisArguments.length != otherArguments.length) { return false; }

            for (int i = 0; i < thisArguments.length; i++) {
                if (!thisArguments[i].equals(otherArguments[i])) { return false; }
            }

            return true;
        }

        @Override
        public String toString() {
            StringBuilder buffer = new StringBuilder(128);
            buffer.append(this.getRawType()).append('<');

            boolean first = true;
            for (java.lang.reflect.Type argument : this.getActualTypeArguments()) {
                if (!first) { buffer.append(','); }

                first = false;
                buffer.append(argument);
            }

            buffer.append('>');
            return buffer.toString();
        }