Skip to content

Commit

Permalink
Merge pull request #1043 from jqno/rethink-forpackage-api
Browse files Browse the repository at this point in the history
Rethink forpackage api
  • Loading branch information
jqno authored Feb 6, 2025
2 parents ebe39cc + dc2d3da commit bf9da20
Show file tree
Hide file tree
Showing 14 changed files with 443 additions and 95 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- `forPackage(String packageName, ScanOption... option)` overload. This provides a more consistent way to fine-tune the scanning of packages. `ScanOption` provides several features, which can be mixed and matched:
- `ScanOption.recursive()` to search recursively. This replaces `forPackage(String packageName, boolean scanRecursively)`.
- `ScanOption.mustExtend(Class<?> type)` to find only classes that extend or implement the given type. This replaces `forPackage(String packageName, Class<?> mustExtend)`. Note that this overload used to search recursively too; this is no longer the case. If you want a recursive search that also only matches subtypes, you have to combine `ScanOption.recursive()` and `ScanOption.mustExtend(Class<?> type)`.
- `ScanOption.except(Class<?>... types)` to find all classes except the given ones. This replaces `forPackage(...).except(Class<?>... types)`.
- `ScanOption.except(Predicate<Class<?>> exclusionPredicate)` to exclude all classes that match the given predicate. This replaces `forPackage(...).except(Predicate<Class<?>> exclusionPredicate)`.
- `ScanOption.ignoreExternalJars()` to not throw an exception when attempting to scan a package from a third-party jar file. This can be useful if you have a split package between a dependency and your own codebase. This is a new option. ([Issue 1040](https://github.com/jqno/equalsverifier/issues/1040))

### Deprecated

- `forPackage(String packageName, boolean scanRecursively)`: replaced by `ScanOption.recursive()`.
- `forPackage(String packageName, Class<?> mustExtend)`: replaced by `ScanOption.mustExtend(Class<?> type)` combined with `ScanOption.recursive()`.
- `forPackage(...).except(Class<?>... types)`: replaced by `ScanOption.except(Class<?>... type)`.
- `forPackage(...).except(Predicate<Class<?>>... exclusionPredicate)`: replaced by `ScanOption.except(Predicate<Class<?>> exclusionPredicate)`.

## [3.18.2] - 2025-01-30

### Fixed
Expand Down
7 changes: 5 additions & 2 deletions docs/_manual/08-several-classes-at-once.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ This will test each class within the `com.example.app.domain` package.
If there's a class within that package that you don't want to test with EqualsVerifier, for instance a helper class, you can exclude it as follows:

{% highlight java %}
EqualsVerifier.forPackage("com.example.app.domain")
.except(Helper.class)
EqualsVerifier.forPackage(
"com.example.app.domain",
ScanOption.except(Helper.class))
.verify();
{% endhighlight %}

Note that the `ScanOption` class contains several more tools to fine-tune your search. `ScanOption.recursive()` will let EqualsVerifier look in all the sub-packages of `com.example.app.domain` and `ScanOption.mustExtend(Foo.class)` only finds classes that extend or implement `Foo.class`.

You can achieve even more granularity:

{% highlight java %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi;
import nl.jqno.equalsverifier.internal.instantiation.vintage.FactoryCache;
import nl.jqno.equalsverifier.internal.instantiation.vintage.PrefabValuesApi;
import nl.jqno.equalsverifier.internal.reflection.PackageScanOptions;
import nl.jqno.equalsverifier.internal.reflection.PackageScanner;
import nl.jqno.equalsverifier.internal.util.ListBuilders;
import nl.jqno.equalsverifier.internal.util.Validations;
Expand Down Expand Up @@ -164,11 +165,15 @@ public MultipleTypeEqualsVerifierApi forClasses(Class<?> first, Class<?> second,
* instead.
*
* @param packageName A package for which each class's {@code equals} should be tested.
* @param options Modifications to the standard package scanning behaviour.
* @return A fluent API for EqualsVerifier.
*/
@CheckReturnValue
public MultipleTypeEqualsVerifierApi forPackage(String packageName) {
return forPackage(packageName, false);
public MultipleTypeEqualsVerifierApi forPackage(String packageName, ScanOption... options) {
PackageScanOptions opts = ScanOptions.process(options);
List<Class<?>> classes = PackageScanner.getClassesIn(packageName, opts);
Validations.validatePackageContainsClasses(packageName, classes);
return new MultipleTypeEqualsVerifierApi(classes, this);
}

/**
Expand All @@ -181,12 +186,12 @@ public MultipleTypeEqualsVerifierApi forPackage(String packageName) {
* @param packageName A package for which each class's {@code equals} should be tested.
* @param scanRecursively true to scan all sub-packages
* @return A fluent API for EqualsVerifier.
* @deprecated Use {@link #forPackage(String, ScanOption...)} instead.
*/
@CheckReturnValue
@Deprecated
public MultipleTypeEqualsVerifierApi forPackage(String packageName, boolean scanRecursively) {
List<Class<?>> classes = PackageScanner.getClassesIn(packageName, null, scanRecursively);
Validations.validatePackageContainsClasses(packageName, classes);
return new MultipleTypeEqualsVerifierApi(classes, this);
return scanRecursively ? forPackage(packageName, ScanOption.recursive()) : forPackage(packageName);
}

/**
Expand All @@ -202,10 +207,14 @@ public MultipleTypeEqualsVerifierApi forPackage(String packageName, boolean scan
* @param packageName A package for which each class's {@code equals} should be tested.
* @param mustExtend if not null, returns only classes that extend or implement this class.
* @return A fluent API for EqualsVerifier.
* @deprecated Use {@link #forPackage(String, ScanOption...)} with {@link ScanOption#mustExtend(Class)}, and
* possibly {@link ScanOption#recursive()}, instead.
*/
@CheckReturnValue
@Deprecated
public MultipleTypeEqualsVerifierApi forPackage(String packageName, Class<?> mustExtend) {
List<Class<?>> classes = PackageScanner.getClassesIn(packageName, mustExtend, true);
PackageScanOptions opts = ScanOptions.process(ScanOption.recursive(), ScanOption.mustExtend(mustExtend));
List<Class<?>> classes = PackageScanner.getClassesIn(packageName, opts);
Validations.validatePackageContainsClasses(packageName, classes);
return new MultipleTypeEqualsVerifierApi(classes, this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import nl.jqno.equalsverifier.api.MultipleTypeEqualsVerifierApi;
import nl.jqno.equalsverifier.api.RelaxedEqualsVerifierApi;
import nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi;
import nl.jqno.equalsverifier.internal.reflection.PackageScanOptions;
import nl.jqno.equalsverifier.internal.reflection.PackageScanner;
import nl.jqno.equalsverifier.internal.util.ListBuilders;
import nl.jqno.equalsverifier.internal.util.Validations;
Expand Down Expand Up @@ -111,11 +112,15 @@ public static MultipleTypeEqualsVerifierApi forClasses(Class<?> first, Class<?>
* instead.
*
* @param packageName A package for which each class's {@code equals} should be tested.
* @param options Modifications to the standard package scanning behaviour.
* @return A fluent API for EqualsVerifier.
*/
@CheckReturnValue
public static MultipleTypeEqualsVerifierApi forPackage(String packageName) {
return forPackage(packageName, false);
public static MultipleTypeEqualsVerifierApi forPackage(String packageName, ScanOption... options) {
PackageScanOptions opts = ScanOptions.process(options);
List<Class<?>> classes = PackageScanner.getClassesIn(packageName, opts);
Validations.validatePackageContainsClasses(packageName, classes);
return new MultipleTypeEqualsVerifierApi(classes, new ConfiguredEqualsVerifier());
}

/**
Expand All @@ -128,12 +133,12 @@ public static MultipleTypeEqualsVerifierApi forPackage(String packageName) {
* @param packageName A package for which each class's {@code equals} should be tested.
* @param scanRecursively true to scan all sub-packages
* @return A fluent API for EqualsVerifier.
* @deprecated Use {@link #forPackage(String, ScanOption...)} with {@link ScanOption#recursive()} instead.
*/
@CheckReturnValue
@Deprecated
public static MultipleTypeEqualsVerifierApi forPackage(String packageName, boolean scanRecursively) {
List<Class<?>> classes = PackageScanner.getClassesIn(packageName, null, scanRecursively);
Validations.validatePackageContainsClasses(packageName, classes);
return new MultipleTypeEqualsVerifierApi(classes, new ConfiguredEqualsVerifier());
return scanRecursively ? forPackage(packageName, ScanOption.recursive()) : forPackage(packageName);
}

/**
Expand All @@ -149,10 +154,14 @@ public static MultipleTypeEqualsVerifierApi forPackage(String packageName, boole
* @param packageName A package for which each class's {@code equals} should be tested.
* @param mustExtend if not null, returns only classes that extend or implement this class.
* @return A fluent API for EqualsVerifier.
* @deprecated Use {@link #forPackage(String, ScanOption...)} with {@link ScanOption#mustExtend(Class)}, and
* possibly {@link ScanOption#recursive()}, instead.
*/
@CheckReturnValue
@Deprecated
public static MultipleTypeEqualsVerifierApi forPackage(String packageName, Class<?> mustExtend) {
List<Class<?>> classes = PackageScanner.getClassesIn(packageName, mustExtend, true);
PackageScanOptions opts = ScanOptions.process(ScanOption.recursive(), ScanOption.mustExtend(mustExtend));
List<Class<?>> classes = PackageScanner.getClassesIn(packageName, opts);
Validations.validatePackageContainsClasses(packageName, classes);
return new MultipleTypeEqualsVerifierApi(classes, new ConfiguredEqualsVerifier());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package nl.jqno.equalsverifier;

import java.util.function.Predicate;

/**
* Contains a number of options that can be set in {@link EqualsVerifier#forPackage(String, ScanOption...)}. These
* options affect the way in which EqualsVerifier scans the given package.
*/
public interface ScanOption {

/**
* Signals that not just the given package should be scanned, but also all of its sub-packages.
*
* @return The 'recursive' flag.
*/
public static ScanOption recursive() {
return ScanOptions.O.RECURSIVE;
}

/**
* Signals that packages from external jars, which can't be scanned, will be ignored rather than throw an exception.
*
* @return The 'ignore external jars' flag.
*/
public static ScanOption ignoreExternalJars() {
return ScanOptions.O.IGNORE_EXTERNAL_JARS;
}

/**
* Finds only classes that extend or implement the given type.
*
* @param type The type that all classes must extend or implement.
* @return The 'mustExtend' flag with the associated type.
*/
public static ScanOption mustExtend(Class<?> type) {
return new ScanOptions.MustExtend(type);
}

/**
* Removes the given type or types from the list of types to verify.
*
* @param type A type to remove from the list of types to verify.
* @param more More types to remove from the list of types to verify.
* @return The 'except' flag with the associated types.
*/
public static ScanOption except(Class<?> type, Class<?>... more) {
return new ScanOptions.ExceptClasses(type, more);
}

/**
* Removes all types matching the given Predicate.
*
* @param exclusionPredicate A Predicate matching classes to remove from the list of types to verify.
* @return The 'except' flag with the associated Predicate.
*/
public static ScanOption except(Predicate<Class<?>> exclusionPredicate) {
return new ScanOptions.ExclusionPredicate(exclusionPredicate);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package nl.jqno.equalsverifier;

import java.util.*;
import java.util.function.Predicate;

import nl.jqno.equalsverifier.internal.reflection.PackageScanOptions;

final class ScanOptions {
private ScanOptions() {}

enum O implements ScanOption {
RECURSIVE, IGNORE_EXTERNAL_JARS;
}

static class MustExtend implements ScanOption {
final Class<?> type;

MustExtend(Class<?> type) {
Objects.requireNonNull(type);
this.type = type;
}
}

static class ExceptClasses implements ScanOption {
final Set<Class<?>> types;

ExceptClasses(Class<?> type, Class<?>... more) {
this.types = new HashSet<>();
this.types.add(type);
this.types.addAll(Arrays.asList(more));
}
}

static class ExclusionPredicate implements ScanOption {
final Predicate<Class<?>> exclusionPredicate;

ExclusionPredicate(Predicate<Class<?>> exclusionPredicate) {
this.exclusionPredicate = exclusionPredicate;
}
}

public static PackageScanOptions process(ScanOption... options) {
PackageScanOptions result = new PackageScanOptions();
for (ScanOption option : options) {
if (option.equals(O.RECURSIVE)) {
result.scanRecursively = true;
}
if (option.equals(O.IGNORE_EXTERNAL_JARS)) {
result.ignoreExternalJars = true;
}
if (option instanceof MustExtend) {
MustExtend me = (MustExtend) option;
result.mustExtend = me.type;
}
if (option instanceof ExceptClasses) {
ExceptClasses ec = (ExceptClasses) option;
result.exceptClasses.addAll(ec.types);
}
if (option instanceof ExclusionPredicate) {
ExclusionPredicate ep = (ExclusionPredicate) option;
result.exclusionPredicate = ep.exclusionPredicate;
}
}
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,11 @@ public MultipleTypeEqualsVerifierApi withResetCaches() {
* @param type A type to remove from the list of types to verify.
* @param more More types to remove from the list of types to verify.
* @return {@code this}, for easy method chaining.
* @deprecated Use {@link EqualsVerifier#forPackage(String, ScanOption...)} with
* {@link ScanOption#except(Class, Class...)} instead.
*/
@CheckReturnValue
@Deprecated
public MultipleTypeEqualsVerifierApi except(Class<?> type, Class<?>... more) {
List<Class<?>> typesToRemove = ListBuilders.buildListOfAtLeastOne(type, more);
removeTypes(typesToRemove);
Expand All @@ -114,8 +117,11 @@ public MultipleTypeEqualsVerifierApi except(Class<?> type, Class<?>... more) {
*
* @param exclusionPredicate A Predicate matching classes to remove from the list of types to verify.
* @return {@code this}, for easy method chaining.
* @deprecated Use {@link EqualsVerifier#forPackage(String, ScanOption...)} with
* {@link ScanOption#except(Predicate)} instead.
*/
@CheckReturnValue
@Deprecated
public MultipleTypeEqualsVerifierApi except(Predicate<Class<?>> exclusionPredicate) {
List<Class<?>> typesToRemove = types.stream().filter(exclusionPredicate).collect(Collectors.toList());
removeTypes(typesToRemove);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package nl.jqno.equalsverifier.internal.reflection;

import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;

public class PackageScanOptions {

public boolean scanRecursively = false;
public boolean ignoreExternalJars = false;
public Class<?> mustExtend = null;
public Set<Class<?>> exceptClasses = new HashSet<>();
public Predicate<Class<?>> exclusionPredicate = c -> false;
}
Loading

0 comments on commit bf9da20

Please sign in to comment.