0

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();
    }

}
}
Fullstack Guy
  • 16,368
  • 3
  • 29
  • 44

1 Answers1

0

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;
}
Elka
  • 227
  • 1
  • 9
  • It works thanks! It is now throwing java.lang.UnsupportedOperationException, when I try to change/add a key after calling this method – Fullstack Guy Nov 19 '17 at 20:38
  • 1
    Is it possible to do this with a java.util.Date field? Also can we dynamically make a field final? – Fullstack Guy Nov 19 '17 at 20:42
  • Oh, unfortunately in Java there isn't a immutable type of java.util.Date as Collections.unmodifiableMap is for the java.util.Map class (as far as I know). I am not sure if the final modifier can help you in this case because `final` is a language feature for compile time, preventing reassignment of a variable in source code. At runtime, it does (almost) nothing. However, you have interesting problem :) I will make research and if I find something I will share with you. – Elka Nov 20 '17 at 08:03
  • 2
    Use `Map.class.isAssignableFrom(field.getType())` instead of `field.getType().equals(Map.class)` – Dean Xu Nov 20 '17 at 09:17
  • Yes, @DeanXu is right. The `Map.class.isAssignableFrom(field.getType())` works for any types of Map. Good point ;) – Elka Nov 20 '17 at 09:20
  • How will I make it work if the field is declared as a HashMap or any implementation of the Map interface? with this it is throwing a "java.lang.IllegalArgumentException: Can not set java.util.HashMap field" if I change the field type from Map to a HashMap. – Fullstack Guy Nov 24 '17 at 19:21