package org.mutabilitydetector.unittesting; /* * #%L * MutabilityDetector * %% * Copyright (C) 2008 - 2014 Graham Allan * %% * 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. * #L% */ import static com.google.common.collect.ImmutableSet.copyOf; import static com.google.common.collect.Iterables.transform; import static java.util.Arrays.asList; import static org.mutabilitydetector.locations.Dotted.dotted; import static org.mutabilitydetector.locations.Dotted.fromClass; import org.hamcrest.Matcher; import org.mutabilitydetector.MutableReasonDetail; import org.mutabilitydetector.locations.Dotted; import org.mutabilitydetector.unittesting.matchers.reasons.AllowingForSubclassing; import org.mutabilitydetector.unittesting.matchers.reasons.AllowingNonFinalFields; import org.mutabilitydetector.unittesting.matchers.reasons.FieldAssumptions; import org.mutabilitydetector.unittesting.matchers.reasons.NoReasonsAllowed; import org.mutabilitydetector.unittesting.matchers.reasons.ProvidedOtherClass; import com.google.common.collect.ImmutableSet; /** * Provides ways to suppress false positives generated by Mutability Detector. * <p> * Regretfully, Mutability Detector may produce false positives, which cause * your unit tests to fail, even though your class is immutable. In order to get * around this fault in Mutability Detector, you can provide an "allowed reason" * for mutability. This is preferable to deleting the test, or marking it as * ignored, as it allows checking for all other violations except those you have * explicitly sanctioned. * <p> * All types returned from the static methods on AllowedReason are * implementations of Hamcrest {@link Matcher}, generic on the type * {@link MutableReasonDetail}. * <p> * For more information on configuring a mutability assertion, see * {@link MutabilityAssert}. * * @see MutabilityAssert * @see MutableReasonDetail * * @see ProvidedOtherClass * @see AllowingForSubclassing * @see AllowingNonFinalFields * @see FieldAssumptions * @see NoReasonsAllowed * */ public final class AllowedReason { private AllowedReason() { } /** * Please see the JavaDoc listed with {@link MutabilityAssert} for an * introduction on using this method. * * @see MutabilityAssert */ public static ProvidedOtherClass provided(String dottedClassName) { return ProvidedOtherClass.provided(dotted(dottedClassName)); } /** * Please see the JavaDoc listed with {@link MutabilityAssert} for an * introduction on using this method. * * @see MutabilityAssert */ public static ProvidedOtherClass provided(Class<?> clazz) { return ProvidedOtherClass.provided(fromClass(clazz)); } /** * Please see the JavaDoc listed with {@link MutabilityAssert} for an * introduction on using this method. * * @see MutabilityAssert */ public static ProvidedOtherClass provided(Class<?>... classes) { return ProvidedOtherClass.provided(transform(asList(classes), Dotted::fromClass)); } /** * Please see the JavaDoc listed with {@link MutabilityAssert} for an * introduction on using this method. * * @see MutabilityAssert */ public static AllowingForSubclassing allowingForSubclassing() { return new AllowingForSubclassing(); } /** * Insists that non-final fields are acceptable. * <p> * Please see the JavaDoc listed with {@link MutabilityAssert} for an * introduction on using this method. * <p> * Example usage: * <pre><code> * @Immutable * public final class HasNonFinalField { * private int someValue; * * public HasNonFinalField(int value) { * this.someValue = value; * } * * // may have getter methods, but definitely no setter methods or reassignments. * } * * // a warning will be raised because field 'someValue' is not declared final * assertInstancesOf(HasNonFinalField.class, areImmutable()); * * // use AllowingNonFinalFields to permit this * // must be used if matching result with areEffectivelyImmutable() * assertInstancesOf(UsesInternalMapForCaching.class, * areImmutable(), * allowingNonFinalFields()); * </code></pre> * <p> * Must be used if matching with {@link MutabilityMatchers#areEffectivelyImmutable()}. * <p> * @see MutabilityAssert */ public static AllowingNonFinalFields allowingNonFinalFields() { return new AllowingNonFinalFields(); } /** * Allowed reasons for mutability warnings related to fields. * <p> * Please see the JavaDoc listed with {@link MutabilityAssert} for an * introduction on using this method. * <p> * Several warnings raised by Mutability Detector relate to the definition, or * the use, of a field in a class. For example: the definition of a field may * include declaring that the type of the field is mutable; or the use of a * field may include reassigning it outwith the constructor. * {@link FieldAssumptions} provides several methods which allow this category * of reasons. * <p> * Reasons are allowed by matching the field by it's name and then * matching the reasons that particular field causes mutability. * <p> * Example usage: * * <pre><code> * @Immutable * public final class UsesInternalMapForCaching { * private final Map<String, String> internalCache = new HashMap<String, String>(); * // ... constructor, and methods which mutate the map for caching * } * * // a warning will be raised because field 'internalCache' is a mutable type. * assertInstancesOf(UsesInternalMapForCaching.class, areImmutable()); * * // use FieldAssumptions to insist the usage is safe * assertInstancesOf(UsesInternalMapForCaching.class, * areImmutable(), * assumingFields("internalCache").areModifiedAsPartOfAnUnobservableCachingStrategy()); * </code></pre> * <p> * This method is also available in Iterable$lt;String> form {@link #assumingFields(Iterable))}. * * * @see MutabilityAssert */ public static FieldAssumptions assumingFields(String firstFieldName, String... otherFieldNames) { return FieldAssumptions.named(ImmutableSet.<String>builder().add(firstFieldName).add(otherFieldNames).build()); } /** * Allowed reasons for mutability warnings related to fields. * <p> * Please see the JavaDoc listed with {@link MutabilityAssert} for an * introduction on using this method. * <p> * Several warnings raised by Mutability Detector relate to the definition, or * the use, of a field in a class. For example: the definition of a field may * include declaring that the type of the field is mutable; or the use of a * field may include reassigning it outwith the constructor. * {@link FieldAssumptions} provides several methods which allow this category * of reasons. * <p> * Reasons are allowed by matching the field by it's name and then * matching the reasons that particular field causes mutability. * <p> * Example usage: * * <pre><code> * @Immutable * public final class UsesInternalMapForCaching { * private final Map<String, String> internalCache = new HashMap<String, String>(); * // ... constructor, and methods which mutate the map for caching * } * * // a warning will be raised because field 'internalCache' is a mutable type. * assertImmutable(UsesInternalMapForCaching.class, areImmutable()); * * // use FieldAssumptions to insist the usage is safe * assertImmutable(UsesInternalMapForCaching.class, * areImmutable(), * assumingFields(Arrays.asList("internalCache")).areModifiedAsPartOfAnUnobservableCachingStrategy()); * </code></pre> * <p> * This method is also available in varargs form {@link #assumingFields(String, String...)}. * * * @see MutabilityAssert */ public static FieldAssumptions assumingFields(Iterable<String> named) { return FieldAssumptions.named(copyOf(named)); } }