/* * Copyright 2013 Google Inc. All Rights Reserved. * * 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.errorprone.matchers; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.errorprone.VisitorState; import com.google.errorprone.annotations.ForOverride; import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; import java.util.List; /** * A {@link MultiMatcher} that applies a matcher across multiple children of a single ancestor * node. Configurable to return true if any of, all of, or the last node matches. In the any or * last of cases, provides access to the node that matched. * * @author eaftan@google.com (Eddie Aftandilian) * @param <T> the type of the node to match on * @param <N> the type of the subnode that the given matcher should match */ public abstract class ChildMultiMatcher<T extends Tree, N extends Tree> implements MultiMatcher<T, N> { public enum MatchType { /** * Matches if all of the child elements match the matcher. If the parent element has no child * elements, this matcher returns true. */ ALL, /** * Matches if at least one of the child elements match the matcher. If the parent element has no * child elements, this matcher returns false. */ AT_LEAST_ONE, /** * Matches if the last child element matches the matcher, regardless of whether or not any of * the other child elements would match the matcher. If the parent element has no child * elements, this matcher returns false. */ LAST } @AutoValue abstract static class Matchable<T extends Tree> { public abstract T tree(); public abstract VisitorState state(); public static <T extends Tree> Matchable<T> create(T tree, VisitorState state) { return new AutoValue_ChildMultiMatcher_Matchable<>(tree, state); } } @AutoValue abstract static class MatchResult<T extends Tree> { public abstract List<T> matchingNodes(); public abstract boolean matches(); public static <T extends Tree> MatchResult<T> none() { return create(ImmutableList.<T>of(), false); } public static <T extends Tree> MatchResult<T> match(T matchingNode) { return create(ImmutableList.of(matchingNode), true); } public static <T extends Tree> MatchResult<T> match(ImmutableList<T> matchingNodes) { return create(matchingNodes, true); } private static <T extends Tree> MatchResult<T> create(List<T> matchingNode, boolean matches) { return new AutoValue_ChildMultiMatcher_MatchResult<>( ImmutableList.copyOf(matchingNode), matches); } } /** * A matcher that operates over a list of nodes, each of which includes an AST node and a * VisitorState with a TreePath for the given node. */ private abstract static class ListMatcher<N extends Tree> { abstract MatchResult<N> matches(List<Matchable<N>> matchables, Matcher<N> nodeMatcher); public static <N extends Tree> ListMatcher<N> create(MatchType matchType) { switch (matchType) { case ALL: return new AllMatcher<>(); case AT_LEAST_ONE: return new AtLeastOneMatcher<>(); case LAST: return new LastMatcher<>(); } throw new AssertionError("Unexpected match type: " + matchType); } } /** * A matcher that returns true if all nodes in the list match. */ private static class AllMatcher<N extends Tree> extends ListMatcher<N> { @Override public MatchResult<N> matches(List<Matchable<N>> matchables, Matcher<N> nodeMatcher) { ImmutableList.Builder<N> matchingTrees = ImmutableList.builder(); for (Matchable<N> matchable : matchables) { if (!nodeMatcher.matches(matchable.tree(), matchable.state())) { return MatchResult.none(); } matchingTrees.add(matchable.tree()); } return MatchResult.match(matchingTrees.build()); } } /** * A matcher that returns true if at least one node in the list matches. */ private static class AtLeastOneMatcher<N extends Tree> extends ListMatcher<N> { @Override public MatchResult<N> matches(List<Matchable<N>> matchables, Matcher<N> nodeMatcher) { ImmutableList.Builder<N> matchingTrees = ImmutableList.builder(); for (Matchable<N> matchable : matchables) { if (nodeMatcher.matches(matchable.tree(), matchable.state())) { matchingTrees.add(matchable.tree()); } } ImmutableList<N> allTheTrees = matchingTrees.build(); return allTheTrees.isEmpty() ? MatchResult.<N>none() : MatchResult.match(allTheTrees); } } /** * A matcher that returns true if the last node in the list matches. */ private static class LastMatcher<N extends Tree> extends ListMatcher<N> { @Override public MatchResult<N> matches(List<Matchable<N>> matchables, Matcher<N> nodeMatcher) { if (matchables.isEmpty()) { return MatchResult.none(); } Matchable<N> last = Iterables.getLast(matchables); return nodeMatcher.matches(last.tree(), last.state()) ? MatchResult.match(last.tree()) : MatchResult.<N>none(); } } /** * The matcher to apply to the subnodes in question. */ protected final Matcher<N> nodeMatcher; private final ListMatcher<N> listMatcher; public ChildMultiMatcher(MatchType matchType, Matcher<N> nodeMatcher) { this.nodeMatcher = nodeMatcher; this.listMatcher = ListMatcher.create(matchType); } @Override public boolean matches(T tree, VisitorState state) { return multiMatchResult(tree, state).matches(); } @Override public MultiMatchResult<N> multiMatchResult(T tree, VisitorState state) { ImmutableList.Builder<Matchable<N>> result = ImmutableList.builder(); for (N subnode : getChildNodes(tree, state)) { TreePath newPath = new TreePath(state.getPath(), subnode); result.add(Matchable.create(subnode, state.withPath(newPath))); } MatchResult<N> matchResult = listMatcher.matches(result.build(), nodeMatcher); return MultiMatchResult.create(matchResult.matches(), matchResult.matchingNodes()); } /** * Returns the set of child nodes to match. The nodes must be immediate children of the current * node to ensure the TreePath calculation is correct. MultiMatchers with other requirements * should not subclass ChildMultiMatcher. */ @ForOverride protected abstract Iterable<? extends N> getChildNodes(T tree, VisitorState state); }