Change field in Record - java

I study reflection and try to change field's value in Record.
public record Account(Integer id, String login, Boolean blocked) {}
public class Main {
public static void main(String[] args) {
Account account = new Account(null, null, null);
setFieldValue(account, "id", 1);
setFieldValue(account, "login", "admin");
setFieldValue(account, "blocked", false);
System.out.println(account);
}
public static void setFieldValue(Object instance,
String fieldName,
Object value) {
try {
Field field = instance.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(instance, value);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
If I convert Record to Class everything works, but with Record I get Exception
java.lang.IllegalAccessException: Can not set final java.lang.Integer field Account.id to java.lang.Integer
at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)
at java.base/jdk.internal.reflect.UnsafeQualifiedObjectFieldAccessorImpl.set(UnsafeQualifiedObjectFieldAccessorImpl.java:79)
at java.base/java.lang.reflect.Field.set(Field.java:799)
What do I have to do to make the code work with records?

In general the commenters saying that this "can't be done" or is "impossible" aren't wrong... unless you are willing to slightly bend the rules of the JVM :) For example, by using unsafe reflection to change the relevant value directly at the memory location in the record like this:
public static void setFieldValue(Object instance, String fieldName, Object value) {
try {
Field f = instance.getClass().getDeclaredField(fieldName);
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
Field theInternalUnsafeField = Unsafe.class.getDeclaredField("theInternalUnsafe");
theInternalUnsafeField.setAccessible(true);
Object theInternalUnsafe = theInternalUnsafeField.get(null);
Method offset = Class.forName("jdk.internal.misc.Unsafe").getMethod("objectFieldOffset", Field.class);
unsafe.putBoolean(offset, 12, true);
unsafe.putObject(instance, (long) offset.invoke(theInternalUnsafe, f), value);
} catch (IllegalAccessException | NoSuchFieldException | ClassNotFoundException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
}

Related

org.mockito.internal.util.reflection.FieldSetter; deprecated in mockito-core 4.3.1

In the below code FieldSetter.SetField was used for the test case but now as I have up upgraded to mockito-core 4.3.1. This no longer works. Can you please suggest to me what can I replace it with?
This is throwing an error as it is deprecated in mockito 4.3.1
import org.mockito.internal.util.reflection.FieldSetter;
#Rule
public AemContext context = new AemContext();
private FareRulesRequestProcessor fareRulesRequestProcessor = new FareRulesRequestProcessorImpl();
private FareRulesPathInfo pathInfo;
#Mock
private SlingHttpServletRequest mockRequest;
private FareRulesDataService mockFareRulesDataService;
#Before
public void before() throws Exception {
mockFareRulesDataService = new FareRulesDataServiceImpl();
mockFareRulesDataService = mock(FareRulesDataService.class);
PrivateAccessor.setField(fareRulesRequestProcessor, "fareRulesDataService", mockFareRulesDataService);
}
#Test
public void testFareRulesDataForRequest() throws NoSuchFieldException {
when(mockRequest.getPathInfo()).thenReturn(FARE_RULES_PAGE_URL);
FieldSetter.setField(fareRulesRequestProcessor, fareRulesRequestProcessor.getClass().getDeclaredField("validFareRulesDataMap"), getFareRulesDataMap());
FareRulesData fareRulesData = fareRulesRequestProcessor.getFareRulesData(mockRequest);
assertEquals(FROM, fareRulesData.getDestinationFrom());
assertEquals(TO, fareRulesData.getDestinationTo());
assertEquals(MARKET, fareRulesData.getMarket());
assertTrue(fareRulesData.isFareRulesByMarket());
}
This was an internal class of Mockito, you should not depend on it. I ended up using this simple util instead:
//import java.lang.reflect.Field;
public class ReflectUtils {
private ReflectUtils() {}
public static void setField(Object object, String fieldName, Object value) {
try {
var field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException("Failed to set " + fieldName + " of object", e);
}
}
public static void setField(Object object, Field fld, Object value) {
try {
fld.setAccessible(true);
fld.set(object, value);
} catch (IllegalAccessException e) {
String fieldName = null == fld ? "n/a" : fld.getName();
throw new RuntimeException("Failed to set " + fieldName + " of object", e);
}
}
}

How does Java handles object of generic class when deserializing?

Here is a very simple generic class:
import java.io.Serializable;
public class GenericDemoObject<T> implements Serializable {
private static final long serialVersionUID = 1L;
T date;
#Override
public String toString() {
return date.getClass().getName() + ':' + date.toString();
}
}
I then created an instance of GenericDemoObject with T typed to org.joda.time.LocalDate, and serialized it to disk. Then I tried to deserialize it with the following code:
private static Object deserialize(String fileName) throws IOException, ClassNotFoundException {
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(fileName))) {
return in.readObject();
}
}
// LocalDate is java.time.LocalDate
GenericDemoObject<LocalDate> obj = (GenericDemoObject<LocalDate>) deserialize("GenericDemoObject.ser");
I can't understand how the deserialization works. Why does it not throw a ClassCastException?
Edited:
More precisely, I mean obj is a reference of type GenericDemoObject, however the deserilized object's date field is of type org.joda.time.LocalDate. How can that assignment work?
After testing the case you posted in your question I confirm that you won't get an exception by merely casting a generic as you did, the real casting never happens until you access the field date, check the following code:
public class Main {
public static void main(String[] args) {
org.joda.time.LocalDate date = org.joda.time.LocalDate.now();
GenericDemoObject<org.joda.time.LocalDate> gdo = new GenericDemoObject<>();
gdo.date = date;
try {
serialize("GenericDemoObject.ser", gdo);
} catch (IOException | ClassNotFoundException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
try {
GenericDemoObject<java.time.LocalDate> obj1 = (GenericDemoObject<java.time.LocalDate>) deserialize("GenericDemoObject.ser");//no exception
GenericDemoObject<String> obj2 = (GenericDemoObject<String>) deserialize("GenericDemoObject.ser");//no exception
LocalDate date1 = obj1.date;//The exception is thrown here
String date2 = obj2.date;//The exception is thrown here
} catch (ClassNotFoundException | IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
private static Object deserialize(String fileName) throws IOException, ClassNotFoundException {
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(fileName))) {
return in.readObject();
}
}
private static void serialize(String fileName, Object object) throws IOException, ClassNotFoundException {
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(fileName))) {
out.writeObject(object);
}
}
}
class GenericDemoObject<T> implements Serializable {
private static final long serialVersionUID = 1L;
T date;
#Override
public String toString() {
return date.getClass().getName() + ':' + date.toString();
}
}
I hope this answers your question.
Note:
Reading through comments again I think Antoniossss really answered but didn't really post it as an answer.

How to change an ATM class during run time (Java Reflection)

There is an ATM GUI .class.
In the ATM class, if the user click swipe, it will use Java reflection to dynamically call my Card.class.
In the ATM class there is this variable:
int userBalanceField = 100;
I am trying to dynamically change it to Integer.MAX_VALUE.
Here is the swipe function in ATM:
Object object;
Class class_ = null;
ClassLoader classLoader;
this.printToScreen("Loading Card");
String string = "." + File.separator + "Card.class";
File file = new File(string);
ClassLoader classLoader2 = classLoader = ClassLoader.getSystemClassLoader();
try {
object = new URL("file:" + System.getProperty("user.dir") + File.separator + "Card.class");
class_ = Card.class;
}
catch (MalformedURLException var6_7) {
var6_7.printStackTrace();
}
object = "Card";
this.printToScreen("Reading card data and verifying ATM");
try {
Method method = class_.getMethod("swipe", ATM.class);
Data data = (Data)method.invoke(null, this);
if (data == null) {
this.printToScreen("Machine considered invalid");
} else {
this.user = data;
this.tempEntry = "";
this.screenState = 2;
this.updateScreen();
}
}
catch (SecurityException var8_11) {
this.printToScreen("Security Exception when swiping card.");
}
catch (NoSuchMethodException var8_12) {
this.printToScreen("No Such method exception when swiping card.");
}
catch (IllegalArgumentException var8_13) {
this.printToScreen("Illegal Argument exception when swiping card.");
}
catch (IllegalAccessException var8_14) {
this.printToScreen("Illegal Access exception when swiping card.");
}
catch (InvocationTargetException var8_15) {
this.printToScreen("Invocation Target exception when swiping card.");
}
Here is my attempt.
public static ATM.Data swipe(ATM anATM){
Class atmClass = anATM.getClass();
try {
Field moneyInMachineField = atmClass.getDeclaredField("moneyInMachine");
System.out.println("Loading field..." + moneyInMachineField.getName());
Field userBalanceField = atmClass.getDeclaredField("userBalance");
userBalanceField.setAccessible(true);
ATM.Data result = new ATM.Data(cardNumber, accountNumber, name, pinNumber);
userBalanceField.set(result, Integer.MAX_VALUE);
return result;
} catch (IllegalAccessException
| NoSuchFieldException
| SecurityException e) {
e.printStackTrace();
}
return null;
}
I keep getting "Invocation Target exception when swiping card."
The syntax that you are using is for the creation of new non-static inner classes. Data is a static inner class of ATM, so you want to do the following:
ATM.Data result = new ATM.Data(cardNumber, accountNumber, name, pinNumber);
According to the Java Docs
For example, to create an object for the static nested class, use this
syntax:
OuterClass.StaticNestedClass nestedObject =
new OuterClass.StaticNestedClass();
If Data was non-static, it would contain a specific reference to ATM (which would give it the ability to refenence AMT.this.userBalance), but it is only used as a POJO.
In addition to the above, you are setting the field incorrectly. When calling field.set() you need to provide it with the object which contains the field and the value you want to set it to.
In your case it would be:
userBalanceField.set(anATM, Integer.MAX_VALUE);
See Java doc
The code
Field userBalanceField = atmClass.getDeclaredField("userBalance");
suggests that userBalance is a field of ATM, but
ATM.Data result = new ATM.Data(cardNumber, accountNumber, name, pinNumber);
userBalanceField.set(result, Integer.MAX_VALUE);
tries to set the field userBalance of ATM.Data object.
If the field userBalance is in ATM.Data, try:
Field userBalanceField = ATM.Data.class.getDeclaredField("userBalance");
The InvocationTargetException wraps all exceptions thrown by reflected methods. This is because the Reflection API is oblivious to the types of exceptions that methods can throw.
Try catching:
InvocationTargetException
And then use:
getCause();
For example:
catch (InvocationTargetException e) {
System.out.println(e.getCause();
}
This will tell you what is actually going wrong. It will also help us to debug your code.

Returning Unknown Type Java

So I'm working with JSON in Java and JSON can have a base of either an Array or an Object. In my Config class, I take a class as an argument so I can create the file accordingly if it doesn't exist. I also store the class as a private field so I know in future.
However, when I get to reading the file, I'd prefer to have multiple return types though the same method name. If I return Object, I then have to cast the returned value which I want to avoid.
Current code:
public class Config {
private File dir = null;
private File file = null;
private Class clazz = null;
public Config(String program, String fileName, Class root) throws IOException {
this.dir = new File(System.getProperty("user.home") + File.separator + program);
if (!this.dir.exists()) {
this.dir.mkdir();
}
this.file = new File(this.dir + File.separator + fileName);
if (!this.file.exists()) {
this.file.createNewFile();
if (root.getName().equals(JSONArray.class.getName())) {
Files.write(this.file.toPath(), "[]".getBytes());
} else if (root.getName().equals(JSONObject.class.getName())) {
Files.write(this.file.toPath(), "{}".getBytes());
}
}
this.clazz = root;
}
public JSONArray readConfig() {
return null;
}
public JSONObject readConfig() {
return null;
}
}
Is there anyway I can do what I want without having to return Object?
multiple return types though the same method name
well, it is possible to use generic function to achieve that. For example,
public static void main(String[] args) {
try {
String t = getObject(String.class);
Integer d = getObject(Integer.class);
} catch (Exception e) {
e.printStackTrace();
}
}
public static <T> T getObject(Class<T> returnType) throws Exception {
if(returnType == String.class) {
return (T) "test";
} else if(returnType == Integer.class) {
return (T) new Integer(0);
} else {
return (T) returnType.newInstance();
}
}
Will the following code even compile?
I'm afraid no. There are few compilation errors such as
public Object readConfig() {
try {
// Assume jsonString exists
return (this.clazz.getDeclaredConstructor(String.class).newInstance(jsonString)); <--- clazz should be getClass()
} catch (InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
e.printStackTrace();
<---- missing return statement
}
}

Set Property on Generic Java Bean

Is this possible? I am creating a an object from a JSON string with this code:
String obj = new Gson().toJson(jsonArray.getJSONObject(i));
String className = getClassName(jsonArray.getJSONObject(i));
Class targetClass = null;
try {
targetClass = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//Create Object
Object data = new Gson().fromJson(obj, targetClass);
I then do some database stuff, get a return key value and I want to set that key on the bean object using its setId() setter, but I don't want to have to cast the specific type of object to the generic object because that would require my repeating the exact same code many times just to cast the object.
key = contactsListDAO.manageDataObj(data, sql, true);
((PhoneNumber) data).setId(key);
Can I use some sort of if statement to check if the object contains an id property and then set the id on the generic object without having to cast?
Here is my working code. For some reason I could never find the method using class.getMethod() so I had to loop through an array of methods and match the names to the setId method that I knew existed. From there using invoke was the key to setting the property correctly.
public void setIdOnObject(Object obj, int id, Class<?> targetClass) {
Method[] methods = targetClass.getMethods();
for(Method i : methods) {
if(i.getName().equals("setId")) {
try {
i.invoke(obj, id);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
reflection can do that but I think maybe here you should do something else.
Write a utility function
public static <T> T fromJson(String json, Class<T> clzz)
{
return (T) new Gson().fromJson(obj, targetClass);
}
and then you can call it like so
PhoneNumber data = fromJson(obj, PhoneNumber.class);
no more conversion.
EDIT : if using "Object" is a constraint you can use reflection
public void setIdOnObject(Object obj, Object id)
{
try{
Method m = obj.getClass().getMethod("setId",id.getClass());
m.invoke(obj, id );
}catch(NoSuchMethodException e){ return false; } catch (InvocationTargetException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
} catch (IllegalAccessException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
Here is a working example I have, just copy-paste-run.
import java.lang.reflect.InvocationTargetException;
public class Reflection
{
public static void main( String[] args )
{
MyParent p = new MyParent();
setParentKey( p, "parentKey" );
MyObj o = new MyObj();
setParentKey( o, "myParentKey" );
setMyKey( o, "myKey" );
System.out.println( "p = " + p );
System.out.println( "o = " + o );
}
public static void invokeMethod( Object p, Object k, String methodName )
{
try
{
p.getClass().getMethod( methodName, k.getClass() ).invoke( p, k );
}
catch ( NoSuchMethodException e )
{
e.printStackTrace();
}
catch ( InvocationTargetException e )
{
e.printStackTrace();
}
catch ( IllegalAccessException e )
{
e.printStackTrace();
}
}
public static void setParentKey( Object p, Object k )
{
invokeMethod( p,k,"setParentKey" );
}
public static void setMyKey( Object p, Object k )
{
invokeMethod( p,k,"setMyKey" );
}
public static class MyParent
{
private Object parentKey;
public void setParentKey( String k )
{
parentKey = k;
}
#Override
public String toString()
{
return "MyParent{" +
"parentKey=" + parentKey +
'}';
}
}
public static class MyObj extends MyParent
{
private Object myKey;
public void setMyKey( String k )
{
myKey = k;
}
#Override
public String toString()
{
return "MyObj{" +
"myKey=" + myKey +
"} " + super.toString();
}
}
}
And the expected output is :
p = MyParent{parentKey=parentKey}
o = MyObj{myKey=myKey} MyParent{parentKey=myParentKey}
If you have (as you mention) "multiple bean types" and "they all have an id property", why don't you define a common interface for you beans with a setId method?
You'll get your beans and just cast to the interface which will be a safe and object-oriented approach. Is it a viable solution for you?

Categories