/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 * http://www.dspace.org/license/
 */
package org.dspace.utils.servicemanager;

import java.lang.ref.WeakReference;


/**
 * A holder which is designed to make it easy to hold onto a reference
 * to a class which is outside of our ClassLoader and not cause it to
 * not be able to reload happily.  The reference to the
 * object that is held onto here needs to not be the only one in the
 * system or this object will be garbage collected before it probably
 * should be.  That is generally outside the purview of the service
 * manager system though.
 *
 * @author Aaron Zeckoski (azeckoski @ gmail.com)
 */
public final class ProviderHolder<T> {

    private WeakReference<T> providerReference = null;

    /**
     * Default constructor.
     */
    public ProviderHolder() {
    }

    /**
     * Create the holder with a provider already in it.
     *
     * @param provider the provider to place in the holder to start
     */
    public ProviderHolder(T provider) {
        setProvider(provider);
    }

    /**
     * Gets the provider out of this holder.
     * Use the {@link #getProviderOrFail()} method if you want a method
     * that never returns null.
     *
     * @return the provider if it is still available OR null if none is set or no longer available
     */
    public T getProvider() {
        T t = null;
        if (this.providerReference != null) {
            t = this.providerReference.get();
        }
        return t;
    }

    /**
     * Gets the provider out of this holder if it still exists.
     * Will not return null (unlike the {@link #getProvider()} method).
     *
     * @return the provider if it is available
     * @throws ProviderNotFoundException if there is none available
     */
    public T getProviderOrFail() {
        T t = getProvider();
        if (t == null) {
            throw new ProviderNotFoundException("Could not get provider from this holder, none available");
        }
        return t;
    }

    /**
     * Stores a provider in this holder in a ClassLoader safe way.
     *
     * @param provider the provider to store.  If this is null then the current provider is cleared.
     */
    public void setProvider(T provider) {
        if (provider == null) {
            if (this.providerReference != null) {
                this.providerReference.clear();
            }
            this.providerReference = null;
        } else {
            this.providerReference = new WeakReference<T>(provider);
        }
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        T provider = getProvider();
        result = prime * result + ((provider == null) ? 0 : provider.hashCode());
        return result;
    }

    @Override
    @SuppressWarnings("unchecked")
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        ProviderHolder<T> other = (ProviderHolder<T>) obj;
        T provider = getProvider();
        T otherProvider = other.getProvider();
        if (provider == null || otherProvider == null) {
            return false;
        } else if (provider.equals(otherProvider)) {
            return true;
        }
        return false;
    }

    @Override
    public String toString() {
        T provider = getProvider();
        return "ph:" + (provider == null ? null : provider.getClass() + ":" + provider) + ": " + super.toString();
    }

}
