I am getting some Java objects from a library which have mutable map objects as fields. I need a way to make those object fields read only at run time by wrapping the map objects in Collections.unmodifiableMap() method. I have tried the below approach using Java Reflection API, but I am stuck at getting the actual map instance from the field:
public static <T>T freeze(T obj){
if(obj!=null){
Field[] fields=obj.getClass().getDeclaredFields();
for(Field field:fields){
if(field.getType().equals(Map.class)){
//How to wrap a Map instance from the field in Collections.unmodifiableMap() object
}
}
}
return obj;
}
EDIT----------------------------------------------------------------------------------------------------------------------------- I have written a Immutable Date class which will wrap a java.util.Date object and will disable all the mutable operations. Using this wrapper I can get a functionality similar to Collections.unmodifiableCollection().
final class DateUtil{
private DateUtil(){}
/**
*
* @param date
* @return Date
*
* This method will return an unmodifiable Date object.
*
*/
public static Date unmodifiableDate(Date date){
if(date==null){
throw new IllegalArgumentException();
}
return new ImmutableDate(new Date(date.getTime()));
}
/**
* This Date sub-class will override all the mutable Date operations
* and throw UnsupportedOperationException.
*/
private final static class ImmutableDate extends Date{
private static final long serialVersionUID = -1869574656923004097L;
private final Date date;
ImmutableDate(Date date){
this.date=date;
}
@Override
public void setTime(long date) {
throw new UnsupportedOperationException();
}
@Override
public void setDate(int date) {
throw new UnsupportedOperationException();
}
@Override
public void setHours(int hours) {
throw new UnsupportedOperationException();
}
@Override
public void setMinutes(int minutes) {
throw new UnsupportedOperationException();
}
@Override
public void setSeconds(int seconds) {
throw new UnsupportedOperationException();
}
@Override
public void setYear(int year) {
throw new UnsupportedOperationException();
}
@Override
public void setMonth(int month) {
throw new UnsupportedOperationException();
}
@Override
public Object clone() {
throw new UnsupportedOperationException();
}
}
}
One possible solution is to get the map value from the object you want to freeze, convert it to Collections.unmodifiableMap() and after that set the value to the object. Before field manipulation you should set field.setAccessible(true)
.
public static <T>T freeze(T obj) throws IllegalAccessException {
if(obj!=null){
Field[] fields=obj.getClass().getDeclaredFields();
for(Field field:fields){
//update accessibility
field.setAccessible(true);
if(field.getType().equals(Map.class)){
//Convert the map field to Collections.unmodifiableMap() object and update the field
field.set(obj, Collections.unmodifiableMap((Map<?, ?>) field.get(obj)));
}
}
}
return obj;
}