/*******************************************************************************
* Copyright (c) 2007, 2014 compeople AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* compeople AG - initial API and implementation
*******************************************************************************/
package org.eclipse.riena.core.util;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import org.osgi.service.log.LogService;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.equinox.log.Logger;
import org.eclipse.riena.core.Log4r;
import org.eclipse.riena.internal.core.Activator;
/**
* A {@code WeakRef} tracks instances with a {@code WeakReference}. When the
* instance gets garbage collected it executes a {@code Runnable} for
* notification.<br>
* However, when the notification gets called, the {@code referent} has already
* been garbage collected and therefore {@code WeakRef.get()} will return
* {@code null}.
*/
public class WeakRef<T> {
private final Reference<T> ref;
private static final ReferenceQueue<? super Object> REF_QUEUE = new ReferenceQueue<Object>();
private static final Map<Reference<?>, Runnable> REMOVE_ACTIONS = new HashMap<Reference<?>, Runnable>();
static {
// Kick-off the remover
new Remover();
}
private static final Logger LOGGER = Log4r.getLogger(WeakRef.class);
/**
* Create a {@code WeakRef} for the given {@code referent} with the given
* {@code Runnable} for notification.
* <p>
* <b>Note:</b> Within the {@code Runnable} calling {@code WeakRef.get()}
* will always return {@code null}!
*
* @param referent
* the instance to track for garbage collection
* @param runnable
* the {@code Runnable} for notification
*/
public WeakRef(final T referent, final Runnable runnable) {
ref = new WeakReference<T>(referent, REF_QUEUE);
synchronized (REMOVE_ACTIONS) {
REMOVE_ACTIONS.put(ref, runnable);
}
}
/**
* Get the tracked instance.
*
* @return the tracked instance or {@code null} if the instance has been
* garbage collected
*/
public T get() {
return ref.get();
}
/**
* The {@code Remover} job waits for gc-ed instances and notifies the
* <i>owner</i> of the instance with its {@code Runnable}.
*/
private static class Remover extends Job {
private static final int REMOVE_TIMEOUT = 1;
private static final String WE_ARE_FAMILY = Activator.PLUGIN_ID;
public Remover() {
super("WeakRef remover"); //$NON-NLS-1$
setSystem(true);
schedule();
}
@Override
public boolean belongsTo(final Object family) {
return WE_ARE_FAMILY.equals(family);
}
@Override
protected IStatus run(final IProgressMonitor monitor) {
try {
while (!monitor.isCanceled()) {
final Reference<?> removed = REF_QUEUE.remove(REMOVE_TIMEOUT);
if (removed == null) {
continue;
}
Runnable runnable;
synchronized (REMOVE_ACTIONS) {
runnable = REMOVE_ACTIONS.remove(removed);
}
if (runnable != null) {
try {
runnable.run();
} catch (final Throwable t) {
LOGGER.log(LogService.LOG_ERROR, "Got exception executing remove notification.", t); //$NON-NLS-1$
}
}
}
} catch (final InterruptedException e) {
LOGGER.log(LogService.LOG_ERROR, "WeakRef remover has been interrupted.", e); //$NON-NLS-1$
Thread.currentThread().interrupt();
return Status.CANCEL_STATUS;
}
return Status.CANCEL_STATUS;
}
}
}