org.perfectable.introspection.FunctionalReference Maven / Gradle / Ivy
package org.perfectable.introspection;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Set;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import kotlin.annotations.jvm.ReadOnly;
/**
* Interface that allows passing compile-time safe information around.
*
* This interface, when extended by functional interface, can be used for passing references to methods or
* constructors, or parameters of those between parts of program, with compile-time conformance checking enabled.
*
*
Sometimes, when one part of the program needs to identify some construct of the program that is not known to it
* at time of its creation, we often use complete or partial reflection information to identify these constructs.
* For example:
*
* - Class literal, method name, and method parameter erasures literals to identify method
* - Class literal and constructor parameter erasure literals to identify constructor
* - Any of above and parameter index to identify type with additional annotations
*
* In any of above case, some of the elements can be omitted if they are 'assumed' from context.
* More concrete examples:
*
* - Serialization framework needs a method of creating object of class C, if not by no-argument constructor,
* a static method name as string in the C class can be passed.
* - Dynamic form library wants to set properties entered by user on bound object, so property name as string is
* passed.
* - Dependency Injection framework needs a way to pass dependencies to newly created object, so it requires
* annotation type to be included when searching for qualifiers on each dependency.
*
*
* Using this interface, above scenarios can be solved in clean, compile-time checked and refactor-safe ways.
*
*
To use this interface, declare a functional interface that has method with signature that user needs to provide.
* This new interface needs to extend this one. Then, expect specified interface as a parameter for some method in place
* where reflection information would be passed. Expect client of this method to pass a non-class based implementation
* of this functional reference, preferably method/constructor reference, but lambda will also work if method name is
* not required and only actual parameter types and annotations are required. Document that requirement. After that
* on the passed interface implementation, execute {@link #introspect}, preferably once, and access expected information
* from it.
*
*
Example:
*
* interface Getter<S, V> extends FunctionalReference {
* V get(S source);
* }
*
* class DocumentCreator {
* public <S> useForSummaryExtraction(Getter<S, String> summaryExtractor) {
* FunctionalReference.Introspection introspection = summaryExtractor.introspect()
* String methodName = introspection.referencedMethodName();
* this.extractors.put(OutlinePart.SUMMARY, methodName);
* }
* }
*
* class Person {
* String getSelfDescription() { ... }
* }
*
* class PersonResume {
* String generate(Person person) {
* DocumentCreator creator = ...
* creator.useForSummaryExtraction(Person::getSelfDescription);
* }
* }
*
*
* In a pinch, this method can be also used to extract refactor-safe constants. Unfortunately this is not very clean
* way of representing it.
*
* private static final String SUMMARY_GETTER_NAME =
* ((Getter<Person,String>) Person::getSelfDescription).introspect().getReferencedMethodName();
*
*
* Introspected reference can be on of either of five 'modes'. Three of those are method references,
* one constructor reference and lambda. These modes are described in JSL section 15.13 and 15.27.
* This class named the modes for convenience:
*
* - Reference to static method (named "static"). This mode is expression in form
* {@literal ReferenceType::Identifier} and matches static method in {@literal ReferenceType}
* (ex. {@code System::identityHashCode})
* - Reference to instance method without primary identifier (named "instance"). This mode is expression
* in form {@literal ReferenceType::Identifier} and matches instance method in {@literal ReferenceType}
* (ex {@code Object::hashCode})
* - Reference to instance method with primary identifier (named "bound", as the instance will be bound to the
* reference). This mode is expression in form {@literal Primary::Identifier} and matches instance method in
* {@literal ReferenceType} (ex. {@code specificPerson::getSelfDescription})
* - Reference to constructor (named "constructor"). This mode is expression in form
* {@literal ReferenceType::new} and matches constructor in {@literal Reference type} (ex. Person::new)
* - Lambda expression (named "lambda").
*
*
* Although this interface can be used to extract information while application is in running cycle, it is strongly
* discouraged, because there is a performance hit any time the {@link #introspect} method is called. This should be
* only used when starting application - to configure objects.
*
*
Using this method has some performance penalty, but it should be somewhat in range of using reflections in above
* scenarios. When method/constructor references are used to pass information around, no additional program constructs
* should be generated by the compiler. Lambdas will still produce additional synthetic method(s) in declaring class.
*/
public interface FunctionalReference extends Serializable {
/**
* Analyzes reference and produces information access object.
*
*
This method has some performance penalty and should be called once on any given reference. Produced
* introspection is almost immutable, and for the same instance, this method will produce the same result. Almost
* immutable, because it references all of the captures in passed lambda and bound instance of bound methods,
* so if those are not immutable, the introspection also isn't.
*
*
This method will throw exception when the instance is not implemented by method/constructor reference
* or lambda (ex. it's implemented by class).
*
*
Do not override this method, as this is missing the whole point of this mechanism.
*
* @implSpec Introspection is done by using SerializedLambda trick: if method/constructor reference or lambda
* implements {@link Serializable}, beside its invocation function, it also declares {@code writeReplace} method
* (described in Serializable documentation), which returns {@link java.lang.invoke.SerializedLambda}.
* This object can be interrogated to extract:
*
* - What kind of implementation is provided
* - Where was it declared
* - Whats the signature of the target method
* - If its a bound method, what is the referenced instance
* - If its a lambda, what are the captured objects
*
* Using this information, and little reflection, all of the needed data can be extracted.
*
* This behavior is documented in {@link java.lang.invoke.LambdaMetafactory#altMetafactory} and should be
* consistent between implementations.
*
* @return introspection of passed reference
*/
default Introspection introspect() {
return FunctionalReferenceIntrospection.of(this);
}
/**
* Introspection of functional reference.
*
* WARNING: This interface is "consume only" from perspective of semantic versioning. Do not implement it,
* use its instances returned from {@link #introspect}. It might change in backward incompatible way for
* implementors (but not for clients) in minor version changes, for example by adding abstract method.
*/
interface Introspection {
/**
* Visitor Pattern acceptor method.
*
*
This method dispatches type of provided reference to visitor, calling single method for each
* implementation kind.
*
* @param visitor visitor to dispatch into
* @param type of visitor result
* @return whatever visitor call returned
*/
@CanIgnoreReturnValue
T visit(Visitor visitor);
/**
* Extracts class in which referenced method was declared.
*
* For lambdas, the method is a synthetic and declared in place where lambda was declared.
*
* @return declaration class of the method reference
*/
Class> capturingType();
/**
* Returns actual type returned by implementation method.
*
*
This might be different from function type return type which the reference implements, because
* implementation has a return-type-substitutable return type (JLS 8.4.5).
*
* @return actual return type for implemented method
*/
Type resultType();
/**
* Returns number of parameters that reference method has.
*
*
This method exists for convenience, as it is always equal to the number of formal parameters in functional
* interface declaration.
*
* @return parameter count of referenced method
*/
int parametersCount();
/**
* Returns actual type for parameter by its index.
*
*
This might be different from function type parameter because as it must have the same erasure, it can
* have different parameters, if its parameterized type, or it could be a type variable.
*
* @param index index of parameter to extract, counting from 0
* @return actual parameter type
*/
Type parameterType(int index);
/**
* Returns annotation that was placed on parameter with specified index in implementation method.
*
*
WARNING: due to bug in JDK, this method can return empty set for lambda methods rather than actual
* annotations.
*
*
This method extracts formal parameter annotations that are placed on parameter of implementation method
* with specified index.
*
*
This method behaves differently when this reference was in "bound" mode, as first of the formal parameters
* in functional interface will be permanently bound to the specified instance. In this case calling this method
* with {@literal 0} will always produce empty set, as annotations cannot be applied on receiver parameter.
*
* @param index index of parameter to extract, counting from 0
* @return annotations on parameter type
*/
@ReadOnly
Set parameterAnnotations(int index);
/**
* Extracts referenced method if the implementation was a method reference.
*
* Returns reflection of method of this functional reference. This works for modes "static", "instance" and
* "bound".
*
* @return method that is referenced
* @throws IllegalStateException if mode of this functional reference is not any of method ones
*/
Method referencedMethod() throws IllegalStateException;
/**
* Extracts referenced constructor if the implementation was a constructor reference.
*
*
Returns reflection of constructor of this functional reference. This works for modes "constructor" only.
*
* @return constructor that is referenced
* @throws IllegalStateException if this reference is not to a constructor
*/
Constructor> referencedConstructor() throws IllegalStateException;
}
/**
* Visitor Pattern interface for Functional reference.
*
* @param type of return value from visitor
*/
interface Visitor {
/**
* Executes when mode of this reference is "static", i.e. this is a reference to static method.
*
* @param method method to which this functional references refers
* @return value passed out of {@link FunctionalReference.Introspection#visit}
*/
T visitStatic(Method method);
/**
* Executes when mode of this reference is "instance", i.e. this is a reference to instance method without
* bound instance.
*
* @param method method to which this functional references refers
* @return value passed out of {@link FunctionalReference.Introspection#visit}
*/
T visitInstance(Method method);
/**
* Executes when mode of this reference is "bound", i.e. this is a reference to instance method with
* bound instance present.
*
* @param method method to which this functional references refers
* @param boundInstance instance that was used to create reference
* @return value passed out of {@link FunctionalReference.Introspection#visit}
*/
T visitBound(Method method, Object boundInstance);
/**
* Executes when mode of this reference is "constructor", i.e. this is a reference to constructor.
*
* @param constructor constructor to which this functional references refers
* @return value passed out of {@link FunctionalReference.Introspection#visit}
*/
T visitConstructor(Constructor> constructor);
/**
* Executes when mode of this reference is "lambda".
*
* @param method synthetic method that this lambda created
* @param captures captures caught by this lambda
* @return value passed out of {@link FunctionalReference.Introspection#visit}
*/
T visitLambda(Method method, List