SharedObject.java
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
*******************************************************************************
* Copyright (C) 2013-2014, International Business Machines
* Corporation and others. All Rights Reserved.
*******************************************************************************
* SharedObject.java, ported from sharedobject.h/.cpp
*
* C++ version created on: 2013dec19
* created by: Markus W. Scherer
*/
package com.ibm.icu.impl.coll;
import com.ibm.icu.util.ICUCloneNotSupportedException;
import java.lang.ref.Cleaner;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Base class for shared, reference-counted, auto-deleted objects. Java subclasses are mutable and
* must implement clone().
*
* <p>In C++, the SharedObject base class is used for both memory and ownership management. In Java,
* memory management (deletion after last reference is gone) is up to the garbage collector, but the
* reference counter is still used to see whether the referent is the sole owner.
*
* <p>Usage:
*
* <pre>
* class S extends SharedObject {
* public clone() { ... }
* }
*
* // Use the nest class Reference (which costs an extra allocation),
* class U {
* // For read-only access, use s.readOnly().
* // For writable access, use S ownedS = s.copyOnWrite();
* private SharedObject.Reference<S> s;
* public U(Reference<U> ref) {
* ...
* }
* // Returns a writable version of s.
* // If there is exactly one owner, then s itself is returned.
* // If there are multiple owners, then s is replaced with a clone,
* // and that is returned.
* private S getOwnedS() {
* return s.copyOnWrite();
* }
* }
* </pre>
*
* Either use only Java memory management, or use addRef()/removeRef(). Sharing requires
* reference-counting.
*
* <p>TODO: Consider making this more widely available inside ICU, or else adopting a different
* model.
*/
public class SharedObject implements Cloneable {
/**
* A class wrapping SharedObject. We need a final field in Reference class to access a
* SharedObject. This class implements Runnable interface which will be used for clean up a
* SharedObject with no references.
*/
private static final class SharedObjectHolder<T extends SharedObject> implements Runnable {
private T sharedObj;
SharedObjectHolder(T so) {
this.sharedObj = so;
}
void set(T otherSo) {
this.sharedObj = otherSo;
}
T get() {
return sharedObj;
}
/*
* This method is triggered by the Cleaner in Reference class below.
*/
@Override
public void run() {
if (sharedObj != null) {
sharedObj.removeRef();
sharedObj = null;
}
}
}
public static final class Reference<T extends SharedObject> implements AutoCloseable {
private static final Cleaner CLEANER = Cleaner.create();
// We need a final field to reach the SharedObject to be cleaned when
// it is no longer used.
private final SharedObjectHolder<T> sharedObjectHolder;
private final Cleaner.Cleanable cleanable;
public Reference(T so) {
this.sharedObjectHolder = new SharedObjectHolder<T>(so);
if (so != null) {
so.addRef();
}
// Registers this object to the CLEANER with a sharedObjectHolder implementing
// Runnable used for clean up action.
this.cleanable = CLEANER.register(this, sharedObjectHolder);
}
/**
* Copy constructor. Previously this class implemented Cloneable and had clone() method.
* Cloning an object with a final field which require modification is tricky (need to use
* reflection to force updating the field) and discouraged. The new implementation uses this
* copy constructor instead of clone().
*
* @param ref A Reference object pointing a SharedObject. This constructor automatically
* increment reference count of the SharedObject.
*/
public Reference(Reference<T> ref) {
this(ref.readOnly());
}
public T readOnly() {
return sharedObjectHolder.get();
}
public T copyOnWrite() {
T so = sharedObjectHolder.get();
if (so.getRefCount() <= 1) {
return so;
}
@SuppressWarnings("unchecked")
T cso = (T) so.clone();
so.removeRef();
cso.addRef();
sharedObjectHolder.set(cso);
return cso;
}
/**
* It's recommended to implement AutoCloseable in a class utilizing Java Cleaner. Although
* it's currently not used by ICU4J implementation, this design allow ICU4J developer to use
* try-with-resource statement to close the resource automatically.
*/
@Override
public void close() throws Exception {
cleanable.clean();
}
}
/** Initializes refCount to 0. */
public SharedObject() {}
/** Initializes refCount to 0. */
@Override
public SharedObject clone() {
SharedObject c;
try {
c = (SharedObject) super.clone();
} catch (CloneNotSupportedException e) {
// Should never happen.
throw new ICUCloneNotSupportedException(e);
}
c.refCount = new AtomicInteger();
return c;
}
/** Increments the number of references to this object. Thread-safe. */
public final void addRef() {
refCount.incrementAndGet();
}
/**
* Decrements the number of references to this object, and auto-deletes "this" if the number
* becomes 0. Thread-safe.
*/
public final void removeRef() {
// Deletion in Java is up to the garbage collector.
refCount.decrementAndGet();
}
/** Returns the reference counter. Uses a memory barrier. */
public final int getRefCount() {
return refCount.get();
}
public final void deleteIfZeroRefCount() {
// Deletion in Java is up to the garbage collector.
}
private AtomicInteger refCount = new AtomicInteger();
}