@Filter¶
Applies a client-side predicate to filter listing results.
Declaration¶
@Repeatable(Filters.class)
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Filter {
Class<? extends Predicate<S3File>> value();
}
Attributes¶
| Attribute | Type | Description |
|---|---|---|
value |
Class<? extends Predicate<S3File>> |
The predicate class to use as a filter. Must have a no-arg constructor. |
Description¶
Applies an arbitrary client-side predicate to filter listing results. The
predicate class is instantiated via its no-arg constructor and applied to
each S3File in the listing.
@Filter is the most powerful filtering annotation, but also the last
to run. When a method or type carries several filter annotations, they
are evaluated in a fixed order — simplest and cheapest first:
- @Prefix — server-side, in the
ListObjectsrequest - @Suffix includes — client-side,
String.endsWith() - @Suffix excludes
- @Match includes — client-side, compiled regex
- @Match excludes
- @Filter — client-side, arbitrary
Predicate<S3File>
Because @Filter runs last, the predicate will only see entries that have
already passed @Prefix, @Suffix, and @Match. This is intentional —
a predicate can safely assume, for example, that the file name ends with
.jar if @Suffix(".jar") is also present on the same method or type.
@Filter is repeatable — multiple filters on the same method are combined
with AND logic. It can be placed on methods or on interface types.
Interface-level filters run before method-level filters.
Tip
When possible, use a simpler annotation. @Prefix filters server-side,
reducing the HTTP payload from AWS. @Suffix handles extension checks
and @Match handles name patterns. Reserve @Filter for criteria
that need access to the full S3File — size, metadata, path components,
or other attributes beyond the name.
Examples¶
Method-level filter¶
public class IsJar implements Predicate<S3File> {
@Override
public boolean test(S3File file) {
return file.getName().endsWith(".jar");
}
}
public class IsSnapshot implements Predicate<S3File> {
@Override
public boolean test(S3File file) {
return file.getName().contains("SNAPSHOT");
}
}
public interface VersionDir extends S3.Dir {
@Filter(IsJar.class)
Stream<S3File> jars();
@Filter(IsSnapshot.class)
Stream<S3File> snapshots();
// Multiple filters — both must pass (AND logic)
@Filter(IsJar.class)
@Filter(IsSnapshot.class)
Stream<S3File> snapshotJars();
}
Type-level filter¶
When @Filter is placed on an interface, it applies automatically to
every listing method that returns that type:
@Filter(IsJar.class)
public interface JarFile extends S3.File {
// Any Stream<JarFile> listing will only include .jar files
}
public interface VersionDir extends S3.Dir {
Stream<JarFile> artifacts(); // automatically filtered to .jar files
Stream<S3File> everything(); // no filter applied
}
What your predicate sees¶
Because @Prefix, @Suffix, and @Match all run before @Filter,
you can write predicates that depend on those earlier filters having
already narrowed the results:
// This predicate can safely assume every file ends with ".jar"
// because @Suffix(".jar") already ran. It strips the suffix
// and inspects what remains.
public class IsSnapshot implements Predicate<S3File> {
@Override
public boolean test(S3File file) {
String name = file.getName(); // e.g. "commons-lang-3.14-SNAPSHOT.jar"
String base = name.substring(0, name.length() - 4); // strip ".jar"
return base.endsWith("-SNAPSHOT");
}
}
@Suffix(".jar")
public interface JarFile extends S3.File {}
public interface VersionDir extends S3.Dir {
// @Suffix(".jar") on JarFile runs first
// @Filter(IsSnapshot.class) sees only .jar files
@Filter(IsSnapshot.class)
Stream<JarFile> snapshotJars();
}
Input validation on single-arg methods¶
When @Filter is placed on the return type of a single-arg proxy method,
it validates the input name. If the S3File doesn't pass the predicate,
an IllegalArgumentException is thrown:
@Filter(IsJar.class)
public interface JarFile extends S3.File {}
public interface VersionDir extends S3.Dir {
Stream<JarFile> artifacts(); // lists only .jar files
JarFile artifact(String name); // validates name passes IsJar
}
versionDir.artifact("commons.jar"); // OK
versionDir.artifact("readme.txt"); // throws IllegalArgumentException
This ensures that names passed to single-arg methods are consistent with the filtering applied to listing methods that return the same type.
Combining with other annotations¶
For maximum efficiency, use @Prefix to reduce the result set server-side,
then layer on client-side filters as needed:
public interface Repository extends S3.Dir {
@Prefix("org/apache")
@Suffix(".jar")
Stream<S3File> apacheJars();
@Prefix("org/apache")
@Suffix(".jar")
@Filter(IsSnapshot.class)
Stream<S3File> apacheSnapshotJars();
}