package com.yoursway.utils.disposable;
import static com.google.common.collect.Lists.newArrayList;
import static java.util.Collections.synchronizedCollection;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import com.google.common.collect.Lists;
import com.yoursway.utils.bugs.Bugs;
public abstract class DisposableImpl implements Disposable, Disposer,
UndoableDisposer {
private volatile boolean disposed = false;
private final Collection<UndoableDisposer> parents;
private final Collection<Disposable> disposables = synchronizedCollection(Lists
.<Disposable> newArrayList());
public DisposableImpl(Disposer parent) {
this(Collections.singleton(parent));
}
public DisposableImpl(Disposer... parents) {
this(Arrays.asList(parents));
}
public DisposableImpl(Collection<? extends Disposer> parents) {
this.parents = new ArrayList<UndoableDisposer>(parents.size());
for (Disposer parent : parents)
this.parents.add(parent.alsoDispose(this));
}
public final void dispose() {
synchronized (this) {
if (disposed)
return;
disposed = true;
}
disposeAdditionalResources();
disposeAnnotatedFieldChildren();
disposeRegisteredDisposables();
deliverDisposedNotification();
for (UndoableDisposer parent : parents)
parent.abstainFromDisposing(this);
}
private final void disposeAnnotatedFieldChildren() {
for (Class<?> klass = getClass(); klass != null; klass = klass
.getSuperclass()) {
for (Field field : klass.getDeclaredFields()) {
if (!Modifier.isStatic(field.getModifiers())
&& field.getAnnotation(DisposableChild.class) != null) {
field.setAccessible(true);
try {
Object disposable = field.get(this);
if (disposable instanceof Disposable)
disposeChild((Disposable) disposable);
else if (disposable != null) {
Method method = disposable.getClass().getMethod(
"dispose");
try {
method.invoke(disposable);
} catch (Throwable e) {
Bugs.listenerFailed(e, disposable, "dispose");
}
}
} catch (ClassCastException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
String fieldName = getClass().getName() + "."
+ field.getName();
throw new AssertionError(
"Field marked with @DisposableChild does not have a public void dispose() method: "
+ fieldName);
}
}
}
}
}
public final boolean isDisposed() {
return disposed;
}
protected void disposeAdditionalResources() {
}
protected void deliverDisposedNotification() {
}
final void disposeChild(Disposable disposable) {
try {
disposable.dispose();
} catch (Throwable e) {
Bugs.listenerFailed(e, disposable, "dispose");
}
}
protected void checkAccess() {
if (disposed)
throw new DisposedException();
}
private final void disposeRegisteredDisposables() {
List<Disposable> list = newArrayList(disposables);
for (Disposable disposable : list)
disposeChild(disposable);
disposables.clear();
}
public final UndoableDisposer alsoDispose(Disposable disposable) {
if (disposable == null)
throw new NullPointerException("disposable is null");
synchronized (this) {
if (!isDisposed()) {
disposables.add(disposable);
return this;
}
}
disposeChild(disposable);
return this;
}
public final void abstainFromDisposing(Disposable disposable) {
if (disposable == null)
throw new NullPointerException("disposable is null");
synchronized (this) {
if (isDisposed())
return;
disposables.remove(disposable);
}
}
}