com.google.testing.compile.MoreTrees.java Source code

Java tutorial

Introduction

Here is the source code for com.google.testing.compile.MoreTrees.java

Source

/*
 * Copyright (C) 2014 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.
 */
package com.google.testing.compile;

import static com.sun.source.tree.Tree.Kind.ANNOTATION_TYPE;
import static com.sun.source.tree.Tree.Kind.BOOLEAN_LITERAL;
import static com.sun.source.tree.Tree.Kind.BREAK;
import static com.sun.source.tree.Tree.Kind.CHAR_LITERAL;
import static com.sun.source.tree.Tree.Kind.CLASS;
import static com.sun.source.tree.Tree.Kind.CONTINUE;
import static com.sun.source.tree.Tree.Kind.DOUBLE_LITERAL;
import static com.sun.source.tree.Tree.Kind.ENUM;
import static com.sun.source.tree.Tree.Kind.FLOAT_LITERAL;
import static com.sun.source.tree.Tree.Kind.IDENTIFIER;
import static com.sun.source.tree.Tree.Kind.INTERFACE;
import static com.sun.source.tree.Tree.Kind.INT_LITERAL;
import static com.sun.source.tree.Tree.Kind.LABELED_STATEMENT;
import static com.sun.source.tree.Tree.Kind.LONG_LITERAL;
import static com.sun.source.tree.Tree.Kind.MEMBER_SELECT;
import static com.sun.source.tree.Tree.Kind.METHOD;
import static com.sun.source.tree.Tree.Kind.NULL_LITERAL;
import static com.sun.source.tree.Tree.Kind.STRING_LITERAL;
import static com.sun.source.tree.Tree.Kind.TYPE_PARAMETER;
import static com.sun.source.tree.Tree.Kind.VARIABLE;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;

import com.sun.source.tree.BreakTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ContinueTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.LabeledStatementTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreePathScanner;

import java.util.Arrays;

import javax.annotation.Nullable;

/**
 * A class containing methods which are useful for gaining access to {@code Tree} instances from
 * within unit tests.
 */
@SuppressWarnings("restriction") // Sun APIs usage intended
final class MoreTrees {

    /** Parses the source given into a {@link CompilationUnitTree}. */
    static CompilationUnitTree parseLinesToTree(String... source) {
        return parseLinesToTree(Arrays.asList(source));
    }

    /** Parses the source given into a {@link CompilationUnitTree}. */
    static CompilationUnitTree parseLinesToTree(Iterable<String> source) {
        Iterable<? extends CompilationUnitTree> parseResults = Compilation
                .parse(ImmutableList.of(JavaFileObjects.forSourceLines("", source))).compilationUnits();
        return Iterables.getOnlyElement(parseResults);
    }

    /** Parses the source given and produces a {@link Compilation.ParseResult}. */
    static Compilation.ParseResult parseLines(String... source) {
        return parseLines(Arrays.asList(source));
    }

    /** Parses the source given and produces a {@link Compilation.ParseResult}. */
    static Compilation.ParseResult parseLines(Iterable<String> source) {
        return Compilation.parse(ImmutableList.of(JavaFileObjects.forSourceLines("", source)));
    }

    /**
     * Finds the first instance of the given {@link Tree.Kind} that is a subtree of the root provided.
     *
     * @throw IllegalArgumentException if no such subtree exists.
     */
    static Tree findSubtree(CompilationUnitTree root, Tree.Kind treeKind) {
        return findSubtree(root, treeKind, null);
    }

    /**
     * Finds the first instance of the given {@link Tree.Kind} that is a subtree of the root provided
     * and which matches identifier string.
     *
     * <p>See the doc on {@link #findSubtreePath} for details on the identifier param.
     *
     * @throw IllegalArgumentException if no such subtree exists.
     */
    static Tree findSubtree(CompilationUnitTree root, Tree.Kind treeKind, @Nullable String identifier) {
        return findSubtreePath(root, treeKind, identifier).getLeaf();
    }

    /**
     * Finds a path to the first instance of the given {@link Tree.Kind} that is a subtree of the root
     * provided.
     *
     * @throw IllegalArgumentException if no such subtree exists.
     */
    static TreePath findSubtreePath(CompilationUnitTree root, Tree.Kind treeKind) {
        return findSubtreePath(root, treeKind, null);
    }

    /**
     * Finds a TreePath terminating at the first instance of the given {@link Tree.Kind} that is a
     * subtree of the root provided and which matches the optional identifier string.
     *
     * <p>Identifier strings are only valid for some {@link Tree} and may take different meanings. The
     * following list provides a quick summary of the matching behavior:
     * <ul>
     * <li>{@link Tree}s with kind {@code BREAK}, {@code CONTINUE}, and {@code LABELED_STATEMENT}
     * match on their {@code getLabel()} methods.
     * <li>{@link Tree}s with kind {@code ANNOTATION_TYPE}, {@code CLASS}, {@code ENUM},
     * and {@code INTERFACE} match on their {@code getSimpleName()} method.
     * <li>{@link Tree}s with kind {@code *_LITERAL} match on their {@code getValue()} method.
     * <li>{@link Tree}s with kind {@code IDENTIFIER}, {@code METHOD}, and {@code TYPE_PARAMETER}
     * match on their {@code getName()} method.
     * <li>{@link Tree}s with kind {@code MEMBER_SELECT} matches on their {@code getIdentifier()}
     * method.
     *
     * @throws IllegalArgumentException if no such subtree exists or if an identifier-based match
     * is requested for any type but one of the following:
     */
    static TreePath findSubtreePath(CompilationUnitTree root, Tree.Kind treeKind, @Nullable String identifier) {
        SearchScanner subtreeFinder = new SearchScanner(treeKind,
                (identifier == null) ? Optional.<String>absent() : Optional.of(identifier));
        Optional<TreePath> res = subtreeFinder.scan(root, null);
        Preconditions.checkArgument(res.isPresent(),
                "Couldn't find any subtree matching the given " + "criteria. Root: %s, Class: %s, Identifier: %s",
                root, treeKind, identifier);
        return res.get();
    }

    /**
     * A {@link TreePathScanner} to power the subtree searches in this class
     */
    static final class SearchScanner extends TreePathScanner<Optional<TreePath>, Void> {
        private final Optional<String> identifier;
        private final Tree.Kind kindSought;

        public SearchScanner(Tree.Kind kindSought, Optional<String> identifier) {
            this.kindSought = kindSought;
            this.identifier = identifier;
        }

        /**
         * Returns {@code true} iff the node and corresponding id value provided match the identifier
         * and kind sought.
         */
        private boolean isMatch(Tree node, Optional<Object> idValue) {
            boolean idsMatch;
            if (!identifier.isPresent()) {
                idsMatch = true;
            } else if (!idValue.isPresent()) {
                idsMatch = false;
            } else {
                idsMatch = (idValue.get() == null && identifier.get() == null)
                        || identifier.get().equals(idValue.get().toString());
            }
            return kindSought.equals(node.getKind()) && idsMatch;
        }

        /**
         * Returns {@code true} iff the node and corresponding id value provided match the identifier
         * and kind sought.
         */
        private boolean isMatch(Tree node, Object idValue) {
            return isMatch(node, Optional.fromNullable(idValue));
        }

        /** Returns a TreePath that includes the current path plus the node provided */
        private Optional<TreePath> currentPathPlus(Tree node) {
            return Optional.of(new TreePath(getCurrentPath(), node));
        }

        /**
         * Returns the {@code Optional} value given, or {@code Optional.absent()} if the value given
         * was {@code null}.
         */
        private Optional<TreePath> absentIfNull(Optional<TreePath> ret) {
            return (ret != null) ? ret : Optional.<TreePath>absent();
        }

        @Override
        public Optional<TreePath> scan(Tree node, Void v) {
            if (node == null) {
                return Optional.<TreePath>absent();
            }

            return isMatch(node, Optional.absent()) ? currentPathPlus(node) : absentIfNull(super.scan(node, v));
        }

        @Override
        public Optional<TreePath> scan(Iterable<? extends Tree> nodes, Void v) {
            Optional<TreePath> ret = super.scan(nodes, v);
            return (ret != null) ? ret : Optional.<TreePath>absent();
        }

        /** Returns the first present value. If both values are absent, then returns absent .*/
        @Override
        public Optional<TreePath> reduce(Optional<TreePath> t1, Optional<TreePath> t2) {
            return (t1.isPresent()) ? t1 : t2;
        }

        @Override
        public Optional<TreePath> visitBreak(@Nullable BreakTree node, Void v) {
            if (node == null) {
                return Optional.absent();
            }

            return isMatch(node, node.getLabel()) ? currentPathPlus(node) : Optional.<TreePath>absent();
        }

        @Override
        public Optional<TreePath> visitClass(@Nullable ClassTree node, Void v) {
            if (node == null) {
                return Optional.absent();
            } else if (isMatch(node, node.getSimpleName())) {
                return currentPathPlus(node);
            }

            return super.visitClass(node, v);
        }

        @Override
        public Optional<TreePath> visitContinue(@Nullable ContinueTree node, Void v) {
            if (node == null) {
                return Optional.absent();
            } else if (isMatch(node, node.getLabel())) {
                return currentPathPlus(node);
            }

            return super.visitContinue(node, v);
        }

        @Override
        public Optional<TreePath> visitIdentifier(@Nullable IdentifierTree node, Void v) {
            if (node == null) {
                return Optional.absent();
            } else if (isMatch(node, node.getName())) {
                return currentPathPlus(node);
            }

            return super.visitIdentifier(node, v);
        }

        @Override
        public Optional<TreePath> visitLabeledStatement(@Nullable LabeledStatementTree node, Void v) {
            if (node == null) {
                return Optional.absent();
            } else if (isMatch(node, node.getLabel())) {
                return currentPathPlus(node);
            }

            return super.visitLabeledStatement(node, v);
        }

        @Override
        public Optional<TreePath> visitLiteral(@Nullable LiteralTree node, Void v) {
            if (node == null) {
                return Optional.absent();
            } else if (isMatch(node, node.getValue())) {
                return currentPathPlus(node);
            }

            return super.visitLiteral(node, v);
        }

        @Override
        public Optional<TreePath> visitMethod(@Nullable MethodTree node, Void v) {
            if (node == null) {
                return Optional.absent();
            } else if (isMatch(node, node.getName())) {
                return currentPathPlus(node);
            }

            return super.visitMethod(node, v);
        }

        @Override
        public Optional<TreePath> visitMemberSelect(@Nullable MemberSelectTree node, Void v) {
            if (node == null) {
                return Optional.absent();
            } else if (isMatch(node, node.getIdentifier())) {
                return currentPathPlus(node);
            }

            return super.visitMemberSelect(node, v);
        }

        @Override
        public Optional<TreePath> visitTypeParameter(@Nullable TypeParameterTree node, Void v) {
            if (node == null) {
                return Optional.absent();
            } else if (isMatch(node, node.getName())) {
                return currentPathPlus(node);
            }

            return super.visitTypeParameter(node, v);
        }

        @Override
        public Optional<TreePath> visitVariable(@Nullable VariableTree node, Void v) {
            if (node == null) {
                return Optional.absent();
            } else if (isMatch(node, node.getName())) {
                return currentPathPlus(node);
            }

            return super.visitVariable(node, v);
        }
    }
}