Skip to content

Support 4-arg Whitebox.setInternalState(target, field, value, Class)#1023

Merged
timtebeek merged 1 commit into
mainfrom
MBoegers/setinternalstate-4arg-class
Jun 16, 2026
Merged

Support 4-arg Whitebox.setInternalState(target, field, value, Class)#1023
timtebeek merged 1 commit into
mainfrom
MBoegers/setinternalstate-4arg-class

Conversation

@MBoegers

Copy link
Copy Markdown
Collaborator

What

Extends PowerMockWhiteboxToJavaReflection to also handle the 4-arg overload
Whitebox.setInternalState(target, "field", value, Class). The 3-arg form was already
migrated; this overload was left untouched.

Why

The 4th argument is the declaring class to look the field up on, and it matters because
getDeclaredField does not walk the class hierarchy. For an inherited field,
child.getClass().getDeclaredField("name") throws NoSuchFieldException — only
Parent.class.getDeclaredField("name") resolves it. This overload is exactly what test code
uses to reach a field declared on a superclass.

The only semantic difference from the 3-arg path is the receiver of getDeclaredField:

Overload Generated lookup receiver
3-arg setInternalState(child, "name", v) child.getClass() (runtime class only)
4-arg setInternalState(child, "name", v, Parent.class) Parent.class (declaring superclass)

Everything else — setAccessible(true), field.set(target, value), the throws Exception,
the java.lang.reflect.Field import — is identical to the shipped 3-arg path. No cast, no boxing.

Before → After

// before
Whitebox.setInternalState(child, "name", "newValue", Parent.class);

// after
Field nameField = Parent.class.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(child, "newValue");

Notes

  • New MethodMatcher for setInternalState(Object, String, Object, Class); the String 2nd
    param disambiguates it from the setInternalState(Object, Class, Object, Class) sibling overload.
  • The 4th Class argument flows straight into a #{any(java.lang.Class)} placeholder, so a
    non-literal Class<?> variable as the 4th arg works too (generates where.getDeclaredField(...)).
  • No catalog/YAML change — the recipe is already wired in and displayName/description are unchanged.

Tests

Two new tests in PowerMockWhiteboxToJavaReflectionTest:

  • setInternalStateWithWhereClass — inherited field via a literal Parent.class.
  • setInternalStateWithWhereClassVariable — a Class<?> variable as the 4th arg.

./gradlew check passes (license, recipe CSV completeness/content validation, full test suite).

The where-overload supplies the declaring class to look the field up on.
getDeclaredField does not walk the class hierarchy, so for an inherited
field child.getClass().getDeclaredField("name") throws NoSuchFieldException;
only Parent.class.getDeclaredField("name") resolves it.

Generate the field lookup with the 4th Class argument as the
getDeclaredField receiver instead of target.getClass(). Everything else
(setAccessible, field.set, throws Exception, Field import) is identical to
the existing 3-arg path. The Class-typed placeholder also accepts a
non-literal Class<?> variable as the 4th arg.
@MBoegers

MBoegers commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator Author

#1021 is an ongoing refactoring with @steve-aom-elliott to break the recipe for expansion. This is the most pressing missing API.

@github-project-automation github-project-automation Bot moved this from In Progress to Ready to Review in OpenRewrite Jun 16, 2026
@timtebeek timtebeek merged commit 30c392d into main Jun 16, 2026
1 check passed
@timtebeek timtebeek deleted the MBoegers/setinternalstate-4arg-class branch June 16, 2026 16:47
@github-project-automation github-project-automation Bot moved this from Ready to Review to Done in OpenRewrite Jun 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants