
io.foldright.inspectablewrappers.Inspector Maven / Gradle / Ivy
Show all versions of inspectable-wrappers Show documentation
package io.foldright.inspectablewrappers;
import edu.umd.cs.findbugs.annotations.DefaultAnnotationForParameters;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import org.jetbrains.annotations.Contract;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import static java.util.Objects.requireNonNull;
/**
* This {@code Inspector} class is used to inspect the wrapper chain.
*
* Common simple usages
*
*
* - Reports whether any instance on the wrapper chain matches the given type
* by static method {@link #containsInstanceTypeOnWrapperChain(Object, Class)}
*
- Retrieves the attachment of instance on the wrapper chain
* by static method {@link #getAttachmentFromWrapperChain(Object, Object)}
*
- Gets the wrapper chain, aka. the list of all instances on the wrapper chain
* by static method {@link #getInstancesOfWrapperChain(Object)}
*
- Gets the base of the wrapper chain, aka. the last instance of the wrapper chain
* by static method {@link #getBaseOfWrapperChain(Object)}
*
- Verifies the compliance of wrapper chain with the specification contracts
* by static method {@link #verifyWrapperChainContracts(Object)}
* or {@link #verifyWrapperChainContracts(Object, Class)}
*
*
* Convenience methods for Wrapper
interface
*
*
* - Unwraps {@link Wrapper} to the underlying instance
* by static method {@link #unwrap(Object)}
*
- Checks the input object is an instance of {@link Wrapper} or not
* by static method {@link #isWrapper(Object)}
*
*
* Advanced usages
*
*
* - Reports whether any instance on the wrapper chain satisfy the given {@link Predicate}
* by static method {@link #testWrapperChain(Object, Predicate)}
*
- Performs the given {@code action} for each instance on the wrapper chain
* by static method {@link #forEachOnWrapperChain(Object, Consumer)}
*
- Traverses the wrapper chain and applies the given {@link Function} to each instance on the wrapper chain
* by static method {@link #travelWrapperChain(Object, Function)}
*
*
* You can implement your own inspection logic using above advanced methods.
*
*
Note about usage and methods naming
*
* All method names contain the word "wrapper chain",
* so the usage code is easily recognizable as related to {@code inspectable wrappers}.
*
* Because the method names are long and informative,
* it's recommended to {@code static import} these methods.
*
* @author Jerry Lee (oldratlee at gmail dot com)
* @author Zava Xu (zava dot kid at gmail dot com)
* @see Wrapper
* @see Attachable
* @see WrapperAdapter
*/
@DefaultAnnotationForParameters(NonNull.class)
public final class Inspector {
/**
* Reports whether any instance on the wrapper chain matches the given type.
*
* The wrapper chain consists of wrapper itself, followed by the wrappers
* obtained by repeatedly calling {@link Wrapper#unwrap_()}.
*
* @param wrapper wrapper instance/wrapper chain
* @param instanceType target type
* @param the type of instances that be wrapped
* @return return {@code false} if no wrapper on the wrapper chain matches the given type,
* otherwise return {@code true}
* @throws NullPointerException if any arguments is null,
* or any wrapper {@link Wrapper#unwrap_()} returns null,
* or the adaptee of {@link WrapperAdapter} is null
* @throws IllegalStateException if the adaptee of {@link WrapperAdapter} is an instance of {@link Wrapper}
* or CYCLIC wrapper chain
*/
@Contract(pure = true)
public static boolean containsInstanceTypeOnWrapperChain(final W wrapper, final Class> instanceType) {
requireNonNull(wrapper, "wrapper is null");
requireNonNull(instanceType, "instanceType is null");
return testWrapperChain(wrapper, w -> isInstanceOf(w, instanceType));
}
private static boolean isInstanceOf(final Object o, final Class> clazz) {
return clazz.isAssignableFrom(o.getClass());
}
/**
* Retrieves the attachment of instance on the wrapper chain for the given key
* by calling {@link Attachable#getAttachment_(Object)}.
*
* The wrapper chain consists of wrapper itself, followed by the wrappers
* obtained by repeatedly calling {@link Wrapper#unwrap_()}.
*
* If the same key exists in multiple wrappers, outer wrapper win.
*
* @param wrapper wrapper instance
* @param key the attachment key
* @param the type of instances that be wrapped
* @param the type of attachment key
* @param the type of attachment value
* @return the attachment value of wrapper for given key on the wrapper chain,
* or null if the attachment is absent
* @throws NullPointerException if any arguments is null,
* or any wrapper {@link Wrapper#unwrap_()} returns null,
* or the adaptee of {@link WrapperAdapter} is null
* @throws ClassCastException if the return value is not type {@code }
* @throws IllegalStateException if the adaptee of {@link WrapperAdapter} is an instance of {@link Wrapper}
* or CYCLIC wrapper chain
* @see Attachable#getAttachment_(Object)
*/
@Nullable
@Contract(pure = true)
@SuppressWarnings("unchecked")
public static V getAttachmentFromWrapperChain(final W wrapper, final K key) {
requireNonNull(wrapper, "wrapper is null");
requireNonNull(key, "key is null");
return travelWrapperChain(wrapper, w -> {
if (w instanceof Attachable) {
V value = ((Attachable) w).getAttachment_(key);
return Optional.ofNullable(value);
} else {
return Optional.empty();
}
}).orElse(null);
}
/**
* Gets the wrapper chain, aka. the list of all instances on the wrapper chain.
*
* The wrapper chain consists of wrapper itself, followed by the wrappers
* obtained by repeatedly calling {@link Wrapper#unwrap_()}.
*
* @param wrapper wrapper instance
* @param the type of instances that be wrapped
* @throws NullPointerException if wrapped argument is null,
* or any wrapper {@link Wrapper#unwrap_()} returns null,
* or the adaptee of {@link WrapperAdapter} is null
* @throws IllegalStateException if the adaptee of {@link WrapperAdapter} is an instance of {@link Wrapper}
* or CYCLIC wrapper chain
*/
@NonNull
@Contract(pure = true)
public static List getInstancesOfWrapperChain(final W wrapper) {
List ret = new ArrayList<>();
forEachOnWrapperChain(wrapper, ret::add);
return ret;
}
/**
* Gets the base of the wrapper chain, aka. the last instance of the wrapper chain.
*
* The wrapper chain consists of wrapper itself, followed by the wrappers
* obtained by repeatedly calling {@link Wrapper#unwrap_()}.
*
* @param wrapper wrapper instance
* @param the type of instances that be wrapped
* @throws NullPointerException if wrapped argument is null,
* or any wrapper {@link Wrapper#unwrap_()} returns null,
* or the adaptee of {@link WrapperAdapter} is null
* @throws IllegalStateException if the adaptee of {@link WrapperAdapter} is an instance of {@link Wrapper}
* or CYCLIC wrapper chain
*/
@NonNull
@Contract(pure = true)
@SuppressWarnings("unchecked")
public static W getBaseOfWrapperChain(final W wrapper) {
Object[] holder = new Object[1];
forEachOnWrapperChain(wrapper, w -> holder[0] = w);
return (W) holder[0];
}
/**
* Unwraps {@link Wrapper} to the underlying instance if input is a {@link Wrapper} instance.
*
* This method is {@code null}-safe, return {@code null} iff input parameter is {@code null};
* If input parameter is not a {@link Wrapper} just return input.
*
* A convenience method for {@link Wrapper#unwrap_()}
*
* @param obj wrapper instance
* @param the type of instances that be wrapped
* @throws NullPointerException if {@link Wrapper#unwrap_()} returns null
* @see Wrapper#unwrap_()
* @see #isWrapper(Object)
*/
@Nullable
@Contract(value = "null -> null; !null -> !null", pure = true)
@SuppressWarnings("unchecked")
public static W unwrap(@Nullable final W obj) {
if (!isWrapper(obj)) return obj;
return (W) unwrapNonNull(obj);
}
/**
* Checks the input object is an instance of {@link Wrapper} or not,
* return {@code false} if input {@code null}.
*
* A convenience method for {@link Wrapper} interface.
*
* @see #unwrap(Object)
*/
@Contract(value = "null -> false", pure = true)
public static boolean isWrapper(@Nullable Object obj) {
return obj instanceof Wrapper;
}
/**
* Verifies the compliance of wrapper chain with the specification contracts.
*
* The wrapper chain consists of wrapper itself, followed by the wrappers
* obtained by repeatedly calling {@link Wrapper#unwrap_()}.
*
* more about the specification contracts see the doc of below methods:
*
* - {@link Wrapper#unwrap_()}
*
- {@link WrapperAdapter#adaptee_()}
*
*
* @param wrapper wrapper instance
* @param the type of instances that be wrapped
* @throws NullPointerException if wrapped argument is null,
* or any wrapper {@link Wrapper#unwrap_()} returns null,
* or the adaptee of {@link WrapperAdapter} is null
* @throws IllegalStateException if the adaptee of {@link WrapperAdapter} is an instance of {@link Wrapper}
* or CYCLIC wrapper chain
*/
public static void verifyWrapperChainContracts(final W wrapper) {
travelWrapperChain(wrapper, w -> Optional.empty());
}
/**
* Verifies the compliance of wrapper chain with the specification contracts,
* and checks all instances on wrapper chain is an instance of the given {@code bizInterface}.
*
* The wrapper chain consists of wrapper itself, followed by the wrappers
* obtained by repeatedly calling {@link Wrapper#unwrap_()}.
*
* more about the specification contracts see the doc of below methods:
*
* - {@link Wrapper#unwrap_()}
*
- {@link WrapperAdapter#adaptee_()}
*
*
* @param wrapper wrapper instance
* @param the type of instances that be wrapped
* @throws NullPointerException if any arguments is null,
* or any wrapper {@link Wrapper#unwrap_()} returns null,
* or the adaptee of {@link WrapperAdapter} is null
* @throws IllegalStateException if any instance on the wrapper chain is not an instance of {@code bizInterface},
* or the adaptee of {@link WrapperAdapter} is an instance of {@link Wrapper}
* or CYCLIC wrapper chain
*/
public static void verifyWrapperChainContracts(final W wrapper, final Class bizInterface) {
requireNonNull(wrapper, "wrapper is null");
requireNonNull(bizInterface, "bizInterface is null");
forEachOnWrapperChain(wrapper, w -> {
if (!isInstanceOf(w, bizInterface)) {
throw new IllegalStateException("the instance(" + w.getClass().getName() +
") on wrapper chain is not an instance of " + bizInterface.getName());
}
});
}
/**
* Reports whether any instance on the wrapper chain satisfies the given {@code predicate}.
* Exceptions thrown by the {@code predicate} are relayed to the caller.
*
* The wrapper chain consists of wrapper itself, followed by the wrappers
* obtained by repeatedly calling {@link Wrapper#unwrap_()}.
*
* @param wrapper wrapper instance/wrapper chain
* @param predicate inspect logic
* @param the type of instances that be wrapped
* @return return {@code false} if no wrapper on the wrapper chain satisfy the given {@code predicate},
* otherwise return {@code true}
* @throws NullPointerException if any arguments is null,
* or any wrapper {@link Wrapper#unwrap_()} returns null,
* or the adaptee of {@link WrapperAdapter} is null
* @throws IllegalStateException if the adaptee of {@link WrapperAdapter} is an instance of {@link Wrapper}
* or CYCLIC wrapper chain
*/
public static boolean testWrapperChain(final W wrapper, final Predicate super W> predicate) {
requireNonNull(wrapper, "wrapper is null");
requireNonNull(predicate, "predicate is null");
return travelWrapperChain(wrapper, w -> {
if (predicate.test(w)) return Optional.of(true);
else return Optional.empty();
}).orElse(false);
}
/**
* Performs the given {@code action} for each instance on the wrapper chain
* until all elements have been processed or the action throws an exception.
* Exceptions thrown by the {@code action} are relayed to the caller.
*
* The wrapper chain consists of wrapper itself, followed by the wrappers
* obtained by repeatedly calling {@link Wrapper#unwrap_()}.
*
* @param wrapper wrapper instance/wrapper chain
* @param action The action to be performed for each instance
* @param the type of instances that be wrapped
* @throws NullPointerException if any arguments is null,
* or any wrapper {@link Wrapper#unwrap_()} returns null,
* or the adaptee of {@link WrapperAdapter} is null
* @throws IllegalStateException if the adaptee of {@link WrapperAdapter} is an instance of {@link Wrapper}
* or CYCLIC wrapper chain
* @see #travelWrapperChain(Object, Function)
*/
public static void forEachOnWrapperChain(final W wrapper, final Consumer super W> action) {
requireNonNull(wrapper, "wrapper is null");
requireNonNull(action, "action is null");
travelWrapperChain(wrapper, w -> {
action.accept(w);
return Optional.empty();
});
}
/**
* Traverses the wrapper chain and applies the given {@code process} function to
* each instance on the wrapper chain, returns the first non-empty({@link Optional#empty()}) result
* of the process function, otherwise returns {@link Optional#empty()}.
* Exceptions thrown by the process function are relayed to the caller.
*
* The wrapper chain consists of wrapper itself, followed by the wrappers
* obtained by repeatedly calling {@link Wrapper#unwrap_()}.
*
* @param wrapper wrapper instance
* @param process process function
* @param the type of instances that be wrapped
* @param the return data type of process function
* @return the first non-empty({@link Optional#empty()}) result of the process function,
* otherwise returns {@link Optional#empty()}
* @throws NullPointerException if any arguments is null,
* or any wrapper {@link Wrapper#unwrap_()} returns null,
* or the adaptee of {@link WrapperAdapter} is null
* @throws IllegalStateException if the adaptee of {@link WrapperAdapter} is an instance of {@link Wrapper}
* or CYCLIC wrapper chain
* @see #forEachOnWrapperChain(Object, Consumer)
*/
@NonNull
@SuppressWarnings("unchecked")
public static Optional travelWrapperChain(
final W wrapper, final Function super W, Optional> process) {
requireNonNull(wrapper, "wrapper is null");
requireNonNull(process, "process is null");
final IdentityHashMap