com.lonepulse.zombielink.util.Fields Maven / Gradle / Ivy
package com.lonepulse.zombielink.util;
/*
* #%L
* ZombieLink
* %%
* Copyright (C) 2013 - 2014 Lonepulse
* %%
* 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.lonepulse.zombielink.util.Assert.assertNotEmpty;
import static com.lonepulse.zombielink.util.Assert.assertNotNull;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* Fluent filtering for a collection of {@link Field}s.
*
* Note that two {@link Field}s are determined to be equal of and only if they were declared
* in the same {@link Class}, using the same name and type.
*
* @version 1.1.0
*
* @since 1.3.0
*
* @author Lahiru Sahan Jayasinghe
*/
public final class Fields implements Iterable {
/**
* This contract defines a strategy for filtering the {@link Field}s within an instance
* of {@link Fields} by evaluating each {@link Field} to determine if it should be included
* in the filtered result.
*
* Instances of {@link Criterion} can be used via {@link Fields#filter(Criterion)}.
*
* @version 1.1.0
*
* @since 1.3.0
*
* @author Lahiru Sahan Jayasinghe
*/
public static interface Criterion {
boolean evaluate(Field field);
}
private List filter(Criterion criterion) {
List filteredFields = new ArrayList();
for (Field field : fields) {
if(criterion.evaluate(field)) {
filteredFields.add(field);
}
}
return filteredFields;
}
private Collection fields = null;
/**
* Creates a new instance of {@link Fields} from the given object by extracting all its
* member {@link Field}s.
*
* See {@link Object#getClass()} and {@link Class#getDeclaredFields()}.
*
* @param target
* the object whose fields are to extracted
*
* @return a new instance of {@link Fields} for the {@link Field}s on the given object
*
* @since 1.3.0
*/
public static Fields in(Object target) {
return new Fields(target.getClass().getDeclaredFields());
}
/**
* Creates a new instance of {@link Fields} from the given {@link Class} by extracting
* all its member {@link Field}s.
*
* See {@link Class#getDeclaredFields()}.
*
* @param target
* the {@link Class} whose fields are to extracted
*
* @return a new instance of {@link Fields} for the {@link Field}s on the given {@link Class}
*
* @since 1.3.0
*/
public static Fields in(Class extends Object> target) {
return new Fields(target.getDeclaredFields());
}
private Fields(Field[] fields) {
assertNotNull(fields, Field[].class);
this.fields = Collections.unmodifiableList(Arrays.asList(fields));
}
private Fields(Collection fields) {
assertNotNull(fields, Collection.class);
this.fields = Collections.unmodifiableCollection(fields);
}
/**
* Filters the {@link Field}s which are annotated with the given annotation and returns a new
* instance of {@link Fields} that wrap the filtered collection.
*
* @param annotation
* the {@link Field}s annotated with this type will be filtered
*
* @return a new instance of {@link Fields} which wraps the filtered {@link Field}s
*
* @since 1.3.0
*/
public Fields annotatedWith(final Class extends Annotation> annotation) {
assertNotNull(annotation, Class.class);
return new Fields(filter(new Criterion() {
@Override
public boolean evaluate(Field field) {
return field.isAnnotationPresent(annotation);
}
}));
}
/**
* Filters the {@link Field}s which are annotated with all the given annotations and returns
* a new instance of {@link Fields} that wrap the filtered collection.
*
* @param annotation
* the {@link Field}s annotated with all these types will be filtered
*
* @return a new instance of {@link Fields} which wraps the filtered {@link Field}s
*
* @since 1.3.0
*/
public Fields annotatedWithAll(final Class extends Annotation>... annotations) {
assertNotNull(annotations, Class[].class);
return new Fields(filter(new Criterion() {
@Override
public boolean evaluate(Field field) {
boolean hasAllAnnotations = true;
for (Class extends Annotation> annotation : annotations) {
if(!field.isAnnotationPresent(annotation)) {
hasAllAnnotations = false;
break;
}
}
return hasAllAnnotations;
}
}));
}
/**
* Filters the {@link Field}s which are annotated with any of the given annotations and
* returns a new instance of {@link Fields} that wrap the filtered collection.
*
* @param annotation
* the {@link Field}s annotated with any of these types will be filtered
*
* @return a new instance of {@link Fields} which wraps the filtered {@link Field}s
*
* @since 1.3.0
*/
public Fields annotatedWithAny(final Class extends Annotation>... annotations) {
assertNotNull(annotations, Class[].class);
return new Fields(filter(new Criterion() {
@Override
public boolean evaluate(Field field) {
for (Class extends Annotation> annotation : annotations) {
if(field.isAnnotationPresent(annotation)) {
return true;
}
}
return false;
}
}));
}
/**
* Filters the {@link Field}s whose name equals (case-sensitive) the given name and returns a new
* instance of {@link Fields} that wrap the filtered collection.
*
* @param fieldName
* the {@link Field}s having this name will be filtered
*
* @return a new instance of {@link Fields} which wraps the filtered {@link Field}s
*
* @since 1.3.0
*/
public Fields named(final String fieldName) {
assertNotEmpty(fieldName);
return new Fields(filter(new Criterion() {
@Override
public boolean evaluate(Field field) {
return field.getName().equals(fieldName);
}
}));
}
/**
* Filters the {@link Field}s whose case insensitive name equals the given name and returns
* a new instance of {@link Fields} that wrap the filtered collection.
*
* @param fieldName
* the {@link Field}s having this case insensitive name will be filtered
*
* @return a new instance of {@link Fields} which wraps the filtered {@link Field}s
*
* @since 1.3.0
*/
public Fields strictlyNamed(final String fieldName) {
assertNotEmpty(fieldName);
return new Fields(filter(new Criterion() {
@Override
public boolean evaluate(Field field) {
return field.getName().equalsIgnoreCase(fieldName);
}
}));
}
/**
*
Filters the {@link Field}s whose type can be assigned to the given type and returns
* a new instance of {@link Fields} that wrap the filtered collection.
*
* @param type
* the {@link Class} type of the {@link Field}s to be filtered
*
* @return a new instance of {@link Fields} which wraps the filtered {@link Field}s
*
* @since 1.3.0
*/
public Fields ofType(final Class> type) {
assertNotEmpty(type);
return new Fields(filter(new Criterion() {
@Override
public boolean evaluate(Field field) {
return type.isAssignableFrom(field.getType());
}
}));
}
/**
* Filters the {@link Field}s whose type equals the given type and returns a new instance
* of {@link Fields} that wrap the filtered collection.
*
* @param type
* the {@link Class} type of the {@link Field}s to be filtered
*
* @return a new instance of {@link Fields} which wraps the filtered {@link Field}s
*
* @since 1.3.0
*/
public Fields strictlyOfType(final Class> type) {
assertNotEmpty(type);
return new Fields(filter(new Criterion() {
@Override
public boolean evaluate(Field field) {
return field.getType().equals(type);
}
}));
}
/**
* Finds the difference between this instance and a supplied instance. Returns a new
* instance of {@link Fields} with only those {@link Field}s which unique to this instance.
*
*
* Difference can be expressed as,
*
* A - B = { x ∈ A and x ∉ B }
*
* where,
*
* A = this Fields instance
* B = supplied Fields instance
* x = any field from the resulting Fields instance
*
*
* @param fields
* the instance of {@link Fields} whose common items are subtracted from this instance
*
* @return a new instance of {@link Fields} which wraps only the only those {@link Field}s which
* are unique to this instance
*
* @since 1.3.0
*/
public Fields difference(Fields fields) {
assertNotNull(fields);
Set view = new HashSet(this.fields);
view.removeAll(new HashSet(fields.fields));
return new Fields(view);
}
/**
* Finds the union between this instance and a supplied instance. Returns a new instance
* of {@link Fields} which contains a set of all the {@link Field}s which in both this instance
* and the supplied instance.
*
*
* Union can be expressed as,
*
* A ∪ B = { x : x ∈ A or x ∈ B }
*
* where,
*
* A = this Fields instance
* B = supplied Fields instance
* x = any field from the resulting Fields instance
*
*
* @param fields
* the instance of {@link Fields} whose {@link Field}s are added to this instance
*
* @return a new instance of {@link Fields} which wraps all the unique {@link Field}s in this
* instance and the given instance
*
* @since 1.3.0
*/
public Fields union(Fields fields) {
assertNotNull(fields);
Set view = new HashSet(this.fields);
view.addAll(new HashSet(fields.fields));
return new Fields(view);
}
/**
* Finds the intersection between this instance and a supplied instance. Returns a new
* instance of {@link Fields} which contains all the {@link Field}s common to both instances.
*
*
* Intersection can be expressed as,
*
* A ∩ B = { x : x ∈ A and x ∈ B }
*
* where,
*
* A = this Fields instance
* B = supplied Fields instance
* x = any field from the resulting Fields instance
*
*
* @param fields
* the instance of {@link Fields} whose common items are discovered
*
* @return a new instance of {@link Fields} which wraps only the only those {@link Field}s which
* are common between this instance and the passed instance
*
* @since 1.3.0
*/
public Fields intersection(Fields fields) {
assertNotNull(fields);
Set view = new HashSet(this.fields);
view.retainAll(new HashSet(fields.fields));
return new Fields(view);
}
/**
* Filters the {@link Field}s which match the given {@link Criterion} and returns a new instance
* of {@link Fields} that wrap the filtered collection.
*
* @param criterion
* the {@link Criterion} whose evaluation determines the filtered field
*
* @return a new instance of {@link Fields} which wraps the filtered {@link Field}s
*
* @since 1.3.0
*/
public Fields matching(Criterion criterion) {
return new Fields(filter(criterion));
}
/**
* Allows the {@link Field}s envelopped by this instance of {@link Fields} to be traversed
* sequentially using the returned {@link Iterator}.
*
* Note this {@link Iterator} does not allow the underlying {@link Field}s to be modified,
* for example using {@link Iterator#remove()}. Doing so will result in an
* {@link UnsupportedOperationException}.
*
* @return the iterator which allows the enclosed {@link Field}s to traversed sequentially
*
* @since 1.3.0
*/
@Override
public Iterator iterator() {
return this.fields.iterator();
}
}