All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sprouts.impl.ChangeListenerCleaner Maven / Gradle / Ivy

package sprouts.impl;


import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;
import java.util.List;

/**
 *  This class stores actions which are being executed when an associated object is being garbage collected.
 *  This class is similar to the cleaner class introduced in JDK 11, however the minimal version compatibility target
 *  for Sprouts is Java 8, which means that this cleaner class introduced in Java 11 is not available here!
 *  That is why a custom cleaner implementation is needed.
*

* Warning: This is an internal class, meaning it should not be used * anywhere but within this library.
* This class or its public methods might change or get removed in future versions!
*/ final class ChangeListenerCleaner { private static final Logger log = LoggerFactory.getLogger(ChangeListenerCleaner.class); private static final ChangeListenerCleaner _INSTANCE = new ChangeListenerCleaner(); private static final long _QUEUE_TIMEOUT = 60 * 1000; public static ChangeListenerCleaner getInstance() { return _INSTANCE; } private final ReferenceQueue _referenceQueue = new ReferenceQueue<>(); private final List> _toBeCleaned = new ArrayList<>(); private final Thread _thread; private ChangeListenerCleaner() { _thread = new Thread(this::run, "Sprouts-Cleaner"); } static class ReferenceWithCleanup extends PhantomReference { private @Nullable Runnable _action; ReferenceWithCleanup( T o, Runnable action, ReferenceQueue queue ) { super( o, queue ); _action = action; } public void cleanup() { if ( _action != null ) { try { _action.run(); } catch (Exception e) { log.error("Failed to execute cleanup action '"+_action+"'.", e); } finally { _action = null; } } } } public void register( @Nullable Object o, Runnable action ) { if ( o == null ) { log.warn("Attempt to register a null object for cleanup. This is not allowed!"); try { action.run(); } catch (Exception e) { log.error("Failed to execute cleanup action '"+action+"'.", e); } return; } synchronized ( _referenceQueue ) { _toBeCleaned.add(new ReferenceWithCleanup<>(o, action, _referenceQueue)); if ( _toBeCleaned.size() == 1 ) { if ( !_thread.isAlive() ) { _thread.start(); } else { // We notify the cleaner thread that there are new items to be cleaned synchronized ( _thread ) { _thread.notify(); } } } } } private void run() { if ( !_thread.isAlive() ) { _thread.start(); } while ( _thread.isAlive() ) { while ( !_toBeCleaned.isEmpty() ) { checkCleanup(); } try { synchronized ( _thread ) { _thread.wait(); } } catch (Exception e) { log.error("Failed to make cleaner thread wait for cleaning notification!", e); } } } private void checkCleanup() { try { ReferenceWithCleanup ref = (ReferenceWithCleanup) _referenceQueue.remove(_QUEUE_TIMEOUT); if ( ref != null ) { try { ref.cleanup(); } catch ( Throwable e ) { log.error("Failed to perform cleanup!", e); } finally { _toBeCleaned.remove(ref); } } } catch ( Throwable e ) { log.error("Failed to call 'remove()' on cleaner internal queue.", e); } } @Override public String toString() { return this.getClass().getSimpleName()+"@"+Integer.toHexString(this.hashCode())+"[" + "registered=" + _toBeCleaned.size() + "]"; } }