/* ****************************************************************************** * Copyright (C) 2005-2011, International Business Machines Corporation and * * others. All Rights Reserved. * ****************************************************************************** */ package com.ibm.icu.util; /** * Provides a flexible mechanism for controlling access, without requiring that * a class be immutable. Once frozen, an object can never be unfrozen, so it is * thread-safe from that point onward. Once the object has been frozen, * it must guarantee that no changes can be made to it. Any attempt to alter * it must raise an UnsupportedOperationException exception. This means that when * the object returns internal objects, or if anyone has references to those internal * objects, that those internal objects must either be immutable, or must also * raise exceptions if any attempt to modify them is made. Of course, the object * can return clones of internal objects, since those are safe. *
* There are often times when you need objects to be objects 'safe', so that * they can't be modified. Examples are when objects need to be thread-safe, or * in writing robust code, or in caches. If you are only creating your own * objects, you can guarantee this, of course -- but only if you don't make a * mistake. If you have objects handed into you, or are creating objects using * others handed into you, it is a different story. It all comes down to whether * you want to take the Blanche Dubois approach ("depend on the kindness of * strangers") or the Andy Grove approach ("Only the Paranoid * Survive"). *
** For example, suppose we have a simple class: *
* ** public class A { * protected Collection b; * * protected Collection c; * * public Collection get_b() { * return b; * } * * public Collection get_c() { * return c; * } * * public A(Collection new_b, Collection new_c) { * b = new_b; * c = new_c; * } * } ** *
* Since the class doesn't have any setters, someone might think that it is * immutable. You know where this is leading, of course; this class is unsafe in * a number of ways. The following illustrates that. *
* ** public test1(SupposedlyImmutableClass x, SafeStorage y) { * // unsafe getter * A a = x.getA(); * Collection col = a.get_b(); * col.add(something); // a has now been changed, and x too * * // unsafe constructor * a = new A(col, col); * y.store(a); * col.add(something); // a has now been changed, and y too * } ** *
* There are a few different techniques for having safe classes. *
** There are advantages and disadvantages of each of these. *
*
* The Freezable
model supplements these choices by giving you
* the ability to build up an object by calling various methods, then when it is
* in a final state, you can make it immutable. Once immutable, an
* object cannot ever be modified, and is completely thread-safe: that
* is, multiple threads can have references to it without any synchronization.
* If someone needs a mutable version of an object, they can use
* cloneAsThawed()
, and modify the copy. This provides a simple,
* effective mechanism for safe classes in circumstances where the alternatives
* are insufficient or clumsy. (If an object is shared before it is immutable,
* then it is the responsibility of each thread to mutex its usage (as with
* other objects).)
*
* Here is what needs to be done to implement this interface, depending on the * type of the object. *
** These are the easiest. You just use the interface to reflect that, by adding * the following: *
* ** public class A implements Freezable { * ... * public final boolean isFrozen() {return true;} * public final A freeze() {return this;} * public final A cloneAsThawed() { return this; } * } ** *
* These can be final methods because subclasses of immutable objects must
* themselves be immutable. (Note: freeze
is returning
* this
for chaining.)
*
* Add a protected 'flagging' field: *
* ** protected boolean immutable; ** *
* Add the following methods: *
* ** public final boolean isFrozen() { * return frozen; * }; * * public A freeze() { * frozen = true; * return this; * } ** *
* Add a cloneAsThawed()
method following the normal pattern for
* clone()
, except that frozen=false
in the new
* clone.
*
* Then take the setters (that is, any method that can change the internal state * of the object), and add the following as the first statement: *
* ** if (isFrozen()) { * throw new UnsupportedOperationException("Attempt to modify frozen object"); * } ** *
* Any subclass of a Freezable
will just use its superclass's
* flagging field. It must override freeze()
and
* cloneAsThawed()
to call the superclass, but normally does not
* override isFrozen()
. It must then just pay attention to its
* own getters, setters and fields.
*
* Internal caches are cases where the object is logically unmodified, but * internal state of the object changes. For example, there are const C++ * functions that cast away the const on the "this" pointer in order * to modify an object cache. These cases are handled by mutexing the internal * cache to ensure thread-safety. For example, suppose that UnicodeSet had an * internal marker to the last code point accessed. In this case, the field is * not externally visible, so the only thing you need to do is to synchronize * the field for thread safety. *
*
* Internal fields are called safe if they are either
* frozen
or immutable (such as String or primitives). If you've
* never allowed internal access to these, then you are all done. For example,
* converting UnicodeSet to be Freezable
is just accomplished
* with the above steps. But remember that you have allowed
* access to unsafe internals if you have any code like the following, in a
* getter, setter, or constructor:
*
* Collection getStuff() { * return stuff; * } // caller could keep reference & modify * * void setStuff(Collection x) { * stuff = x; * } // caller could keep reference & modify * * MyClass(Collection x) { * stuff = x; * } // caller could keep reference & modify ** *
* These also illustrated in the code sample in Background above. *
*
* To deal with unsafe internals, the simplest course of action is to do the
* work in the freeze()
function. Just make all of your internal
* fields frozen, and set the frozen flag. Any subsequent getter/setter will
* work properly. Here is an example:
*
* public A freeze() { * if (!frozen) { * foo.freeze(); * frozen = true; * } * return this; * } ** *
* If the field is a Collection
or Map
, then to
* make it frozen you have two choices. If you have never allowed access to the
* collection from outside your object, then just wrap it to prevent future
* modification.
*
* zone_to_country = Collections.unmodifiableMap(zone_to_country); ** *
* If you have ever allowed access, then do a clone()
* before wrapping it.
*
* zone_to_country = Collections.unmodifiableMap(zone_to_country.clone()); ** *
* If a collection (or any other container of objects) itself can * contain mutable objects, then for a safe clone you need to recurse through it * to make the entire collection immutable. The recursing code should pick the * most specific collection available, to avoid the necessity of later * downcasing. *
*** @stable ICU 3.8 */ public interface Freezable* Note: An annoying flaw in Java is that the generic collections, like *
* *Map
orSet
, don't have aclone()
* operation. When you don't know the type of the collection, the simplest * course is to just create a new collection: ** zone_to_country = Collections.unmodifiableMap(new HashMap(zone_to_country)); ** *