Skip to content

Instantly share code, notes, and snippets.

@cykl
Created May 16, 2022 08:34
Show Gist options
  • Save cykl/1939f8beea28c8116c2ab5931df9e148 to your computer and use it in GitHub Desktop.
Save cykl/1939f8beea28c8116c2ab5931df9e148 to your computer and use it in GitHub Desktop.
Reflection based record wither prototype
interface Wither<T extends Record> {
default <V> T with( SerializableSupplier<V> accessor, V newValue ) {
try {
var providedAccessor = accessor.method( );
var components = R.class.getRecordComponents( );
var accessors = Arrays.stream( components )
.map( RecordComponent::getAccessor )
.toArray( );
Object[ ] arguments = new Object[ components.length ];
for( int i = 0; i < accessors.length; i++ ) {
if( accessors[ i ].equals( providedAccessor ) ) {
arguments[ i ] = newValue;
} else {
arguments[ i ] = components[ i ].getAccessor( ).invoke( this );
}
}
var ctor = R.class.getDeclaredConstructors( )[ 0 ];
return ( T ) ctor.newInstance( arguments );
} catch( IllegalAccessException | InvocationTargetException | InstantiationException e ) {
throw new RuntimeException( e );
}
}
}
interface SerializableSupplier<T> extends Supplier<T>, Serializable, MethodReferenceReflection {
}
interface MethodReferenceReflection {
default SerializedLambda serialized( ) {
try {
Method replaceMethod = getClass( ).getDeclaredMethod( "writeReplace" );
replaceMethod.setAccessible( true );
return ( SerializedLambda ) replaceMethod.invoke( this );
} catch( Exception e ) {
throw new RuntimeException( e );
}
}
default Class<?> getContainingClass( ) {
try {
String className = serialized( ).getImplClass( ).replaceAll( "/", "." );
return Class.forName( className );
} catch( Exception e ) {
throw new RuntimeException( e );
}
}
default Method method( ) {
SerializedLambda lambda = serialized( );
Class<?> containingClass = getContainingClass( );
return Arrays.stream( containingClass.getDeclaredMethods( ) )
.filter( method -> Objects.equals( method.getName( ), lambda.getImplMethodName( ) ) )
.findFirst( )
.orElseThrow( UnableToGuessMethodException::new );
}
class UnableToGuessMethodException extends RuntimeException {
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment