/* * Copyright 2016 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.bugpatterns; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.errorprone.BugPattern.Category.GUAVA; import static com.google.errorprone.BugPattern.SeverityLevel.ERROR; import static com.google.errorprone.matchers.Description.NO_MATCH; import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableMultimap; import com.google.errorprone.BugPattern; import com.google.errorprone.VisitorState; import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher; import com.google.errorprone.matchers.Description; import com.google.errorprone.matchers.Matcher; import com.google.errorprone.matchers.Matchers; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.MethodInvocationTree; import java.util.regex.Pattern; /** @author cushon@google.com (Liam Miller-Cushon) */ @BugPattern( name = "ImmutableModification", category = GUAVA, summary = "Modifying an immutable collection is guaranteed to throw an exception and leave the" + " collection unmodified", explanation = "Calling a method that modifies a collection on an immutable implementation (e.g." + " `ImmutableList.add`) is guaranteed to always throw an `UnsupportedOperationException`" + " and leave the collection unmodified.", severity = ERROR ) public class ImmutableModification extends BugChecker implements MethodInvocationTreeMatcher { public static final ImmutableMultimap<String, String> ILLEGAL = ImmutableMultimap.<String, String>builder() .putAll("com.google.common.collect.ImmutableBiMap", "forcePut") .putAll("com.google.common.collect.ImmutableClassToInstanceMap", "putInstance") .putAll( "com.google.common.collect.ImmutableCollection", "add", "remove", "addAll", "removeAll", "removeIf", "retainAll", "clear") .putAll( "com.google.common.collect.ImmutableList", "addAll", "set", "add", "remove", "replaceAll", "sort") .putAll("com.google.common.collect.ImmutableListMultimap", "removeAll", "replaceValues") .putAll( "com.google.common.collect.ImmutableMap", "put", "putIfAbsent", "replace", "computeIfAbsent", "computeIfPresent", "compute", "merge", "putAll", "replaceAll", "remove", "clear") .putAll( "com.google.common.collect.ImmutableMultimap", "removeAll", "replaceValues", "clear", "put", "putAll", "remove") .putAll("com.google.common.collect.ImmutableMultiset", "add", "remove", "setCount") .putAll("com.google.common.collect.ImmutableRangeMap", "put", "putAll", "clear", "remove") .putAll( "com.google.common.collect.ImmutableRangeSet", "add", "addAll", "remove", "removeAll") .putAll("com.google.common.collect.ImmutableSetMultimap", "removeAll", "replaceValues") .putAll("com.google.common.collect.ImmutableSortedMap", "pollFirstEntry", "pollLastEntry") .putAll("com.google.common.collect.ImmutableSortedSet", "pollFirst", "pollLast") .putAll("com.google.common.collect.ImmutableTable", "clear", "put", "putAll", "remove") .putAll("com.google.common.collect.UnmodifiableIterator", "remove") .putAll("com.google.common.collect.UnmodifiableListIterator", "add", "set") .putAll( "com.google.common.collect.Sets.SetView", "add", "remove", "addAll", "removeAll", "removeIf", "retainAll", "clear") .build(); static final Matcher<ExpressionTree> MATCHER = Matchers.anyOf( ILLEGAL .asMap() .entrySet() .stream() .map( e -> instanceMethod() .onDescendantOf(e.getKey()) .withNameMatching(Pattern.compile(Joiner.on('|').join(e.getValue())))) .collect(toImmutableList())); @Override public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { return MATCHER.matches(tree, state) ? describeMatch(tree) : NO_MATCH; } }