DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Composing Custom Annotations in Spring
  • Mastering Spring: Synchronizing @Transactional and @Async Annotations With Various Propagation Strategies
  • Implementing Persistence With Clean Architecture
  • Unraveling Lombok's Code Design Pitfalls: Exploring Encapsulation Issues

Trending

  • *You* Can Shape Trend Reports: Join DZone's Software Supply Chain Security Research
  • GDPR Compliance With .NET: Securing Data the Right Way
  • How to Build Scalable Mobile Apps With React Native: A Step-by-Step Guide
  • Accelerating AI Inference With TensorRT
  1. DZone
  2. Coding
  3. Java
  4. Filtering Java Collections via Annotation-Driven Introspection

Filtering Java Collections via Annotation-Driven Introspection

Learn a versatile collection filtering strategy based on Java annotations and reflection and how it can simplify the code of some predicates.

By 
Thiago Nascimento user avatar
Thiago Nascimento
DZone Core CORE ·
Oct. 28, 24 · Tutorial
Likes (6)
Comment
Save
Tweet
Share
5.9K Views

Join the DZone community and get the full member experience.

Join For Free

Since Java 8, the programming workload of iterating over collections and selecting a subcollection (filtering) based on defined constraints (predicates) has been considerably simplified using the new Stream API. Despite this new feature, some scenarios can still overwhelm developers by requiring them to implement a significant amount of very specific code to meet the filtering conditions. A common example in enterprise systems is the need to filter:

". . . a collection where each object is an element of a large graph and the attributes to be considered in the filters belong to distinct objects."

To illustrate the scenario, let's consider the small class diagram below. It is required to filter a collection of Post objects taking into account different attributes of the object graph. At this point, disregard the @Filterable annotation on some attributes; this will be covered later.

Class diagram

To present a traditional Java code for filtering Post collections, let's make some assumptions:

  • A List<Post> has been instantiated and loaded.
  • A primitive type textFilter variable has been set as the value to be searched.
  • The Apache Commons Lang StringUtils class is used to remove diacritics from strings.

A way to filter a postsCollection by the text attribute of the Publication class is:

Java
 
postsCollection
	.stream()
	.filter(p -> Objects.isNull(p.getText()) || StringUtils.stripAccents(p.getText().trim().toLowerCase())
            .contains(textFilter.trim().toLowerCase()))
	.toList();


If the need is to filter the collection by the  review attribute of the Comment class, the code could be like this:

Java
 
postsCollection
	.stream()
	.filter(p -> Objects.isNull(p.getComments()) || p.getComments().isEmpty() ||
            p.getComments().stream().anyMatch(c -> StringUtils.stripAccents(c.getReview().trim().toLowerCase())
                                              .contains(textFilter.trim().toLowerCase())))
	.toList();


Note that for each change in filter requirements, the code must be adjusted accordingly. And what's worse: the need to combine multiple attributes from different graph classes can lead to code hard to maintain.

Addressing such scenarios, this article presents a generic approach to filtering collections by combining annotations and the Java Reflection API.

Introspector Filter Algorithm

The proposed approach is called Introspector Filter and its main algorithm relies on three fundaments:

  • Traverse all relationships of each object using a Breadth-First Search strategy.
  • Traverse the hierarchy of each object. 
  • Check if any attribute annotated with @Filterable of each object contains the given pattern.

It's worth explaining that all traversing operations are supported by the Reflection API. Another relevant point is that the attributes to be considered in the filtering operation must be annotated with @Filterable. 

The code below presents the implementation of the Introspector Filter algorithm. Its full source code is available in the GitHub repository.

Java
 
public IntrospectorFilter() {
  
    public Boolean filter(Object value, Object filter) {
		
        if (Objects.isNull(filter)) {
            return true;
        }
		
        String textFilter = StringUtils.stripAccents(filter.toString().trim().toLowerCase());
        var nodesList = new ArrayList<Node>();

        nodesList.add(new Node(0, 0, value));

        while (!nodesList.isEmpty()) { // BFS for relationships

            var node = nodesList.removeFirst();
			
            if (node.height() > this.heightBound || node.breadth() > this.breadthBound) {
                continue;
            }
			
            var fieldValue = node.value();
            var fieldValueClass = fieldValue.getClass();

            int heightHop = node.height();
            do { // Hierarchical traversing
				
                if (Objects.nonNull(
                    this.searchInRelationships(node, fieldValueClass, heightHop, textFilter, nodesList))) {
                        return true;
                }
				
                fieldValueClass = fieldValueClass.getSuperclass(); 
                heightHop++;
            } while (isValidParentClass(fieldValueClass) && heightHop <= this.heightBound);

            if (isStringOrWrapper(fieldValue) && containsTextFilter(fieldValue.toString(), textFilter)) {
                return true;
            }
        }
		
        return false;
    }
}


Three methods have been abstracted from this code to keep the focus on the main algorithm:

  • isValidParentClass: Verify if the parent class is valid, considering any additional annotations that have been provided to identify the class. If no additional annotation have been provided, all parent classes are considered valid.
  • isStringOrWrapper: Check if the value of the attribute annotated with @Filterable is a String or a primitive type wrapper. When this is true, such a relationship traversing is interrupted, as there is no further way forward.
  • containsTextFilter: Check if the attribute annotated with @Filterable contains the supplied pattern.

Let's go back to the previous small class diagram. The two codes presented for filtering a postsCollection may be replaced with the following code using the Introspector Filter Algorithm.

Java
 
postsCollection.stream().filter(p -> filter.filter(p, textFilter)).toList();


Introspector Filter Project

The implementation of the Introspector Filter Algorithm has been encapsulated in a library (jar). It can be incorporated into a Maven project by simply adding the following dependency to the pom.xml file. This implementation requires at least Java version 21 to work. But there is a Java 8 compatible version — change the version dependency to 0.1.0.

XML
 
<dependency>
    <groupId>io.github.tnas</groupId>
    <artifactId>introspectorfilter</artifactId>
    <version>1.0.0</version>
</dependency>


The project also has an example module detailing how to use the Introspector Filter as a global filter in JSF datatable components.

Conclusion

Selecting a subset of objects from a Java collection is a common task in software development. Having a flexible strategy (Introspector Filter Algorithm) or tool (Introspector Filter Library) for creating filters to be used in these selections can help developers write more concise and maintainable code. This is the proposal of Instrospector Filter Project, which is fully available in a GitHub repository.

Annotation Class diagram Attribute (computing) Filter (software) Java (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • Composing Custom Annotations in Spring
  • Mastering Spring: Synchronizing @Transactional and @Async Annotations With Various Propagation Strategies
  • Implementing Persistence With Clean Architecture
  • Unraveling Lombok's Code Design Pitfalls: Exploring Encapsulation Issues

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!