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

com.helger.scope.singleton.AbstractSingleton Maven / Gradle / Ivy

/*
 * Copyright (C) 2014-2024 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.helger.scope.singleton;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.util.BitSet;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.OverrideOnDemand;
import com.helger.commons.annotation.ReturnsMutableCopy;
import com.helger.commons.annotation.UsedViaReflection;
import com.helger.commons.collection.impl.CommonsArrayList;
import com.helger.commons.collection.impl.ICommonsList;
import com.helger.commons.concurrent.SimpleReadWriteLock;
import com.helger.commons.debug.GlobalDebug;
import com.helger.commons.lang.ClassHelper;
import com.helger.commons.statistics.IMutableStatisticsHandlerKeyedCounter;
import com.helger.commons.statistics.StatisticsManager;
import com.helger.commons.string.ToStringGenerator;
import com.helger.scope.IScope;
import com.helger.scope.IScopeDestructionAware;

/**
 * Base class for all singletons.
 *
 * @author Philip Helger
 */
@ThreadSafe
public abstract class AbstractSingleton implements IScopeDestructionAware
{
  private static final int STATUS_IN_INSTANTIATION = 0;
  private static final int STATUS_INSTANTIATED = 1;
  private static final int STATUS_IN_PRE_DESTRUCTION = 2;
  private static final int STATUS_IN_DESTRUCTION = 3;
  private static final int STATUS_DESTROYED = 4;

  private static final int DEFAULT_KEY_LENGTH = 255;
  private static final Logger LOGGER = LoggerFactory.getLogger (AbstractSingleton.class);
  private static final IMutableStatisticsHandlerKeyedCounter STATS_INSTANCE_COUNTER = StatisticsManager.getKeyedCounterHandler (AbstractSingleton.class);
  private static final SimpleReadWriteLock RW_LOCK = new SimpleReadWriteLock ();

  protected final SimpleReadWriteLock m_aRWLock = new SimpleReadWriteLock ();
  @GuardedBy ("m_aRWLock")
  private BitSet m_aStatus = new BitSet (16);

  /**
   * Write the internal status variables to the passed
   * {@link ObjectOutputStream}. This can be used to make singletons
   * serializable.
   *
   * @param aOOS
   *        The output stream to write to. May not be null.
   * @throws IOException
   *         In case writing failed
   */
  protected final void writeAbstractSingletonFields (@Nonnull final ObjectOutputStream aOOS) throws IOException
  {
    aOOS.writeObject (m_aStatus);
  }

  /**
   * Set all internal status variables to the values read from the specified
   * {@link ObjectInputStream}. This can be used to make singletons
   * serializable.
   *
   * @param aOIS
   *        The input stream to read from. May not be null.
   * @throws IOException
   *         In case reading failed
   * @throws ClassNotFoundException
   *         In case reading failed
   */
  protected final void readAbstractSingletonFields (@Nonnull final ObjectInputStream aOIS) throws IOException,
                                                                                           ClassNotFoundException
  {
    m_aStatus = (BitSet) aOIS.readObject ();
  }

  /**
   * Ctor.
   */
  @UsedViaReflection
  protected AbstractSingleton ()
  {
    // Check the call stack to avoid manual instantiation
    // Only required while developing
    if (GlobalDebug.isDebugMode ())
    {
      boolean bFound = false;
      final String sRequiredMethodName = "getSingleton";

      // check if this method is called indirectly via the correct method
      for (final StackTraceElement aStackTraceElement : Thread.currentThread ().getStackTrace ())
      {
        final String sMethodName = aStackTraceElement.getMethodName ();
        if (sMethodName.equals (sRequiredMethodName))
        {
          bFound = true;
          break;
        }
        // Special handling when deserializing from a stream
        if (aStackTraceElement.getClassName ().equals (ObjectInputStream.class.getName ()) &&
            sMethodName.equals ("readOrdinaryObject"))
        {
          bFound = true;
          break;
        }
      }
      if (!bFound)
      {
        // Required method name was not found - error
        throw new IllegalStateException ("You cannot instantiate the class " +
                                         getClass ().getName () +
                                         " manually! Use the method " +
                                         sRequiredMethodName +
                                         " instead!");
      }
    }
  }

  /**
   * Called after the singleton was instantiated. The constructor has finished,
   * and calling getInstance will work! This method is present to init the
   * object with a virtual table present.
   *
   * @param aScope
   *        The scope in which the object was instantiated. Never
   *        null.
   */
  @OverrideOnDemand
  protected void onAfterInstantiation (@Nonnull final IScope aScope)
  {}

  protected final void setInInstantiation (final boolean bInInstantiation)
  {
    m_aRWLock.writeLocked ( () -> m_aStatus.set (STATUS_IN_INSTANTIATION, bInInstantiation));
  }

  /**
   * @return true if this singleton is currently in the phase of
   *         instantiation, false if it is instantiated or already
   *         destroyed.
   */
  public final boolean isInInstantiation ()
  {
    return m_aRWLock.readLockedBoolean ( () -> m_aStatus.get (STATUS_IN_INSTANTIATION));
  }

  protected final void setInstantiated (final boolean bInstantiated)
  {
    m_aRWLock.writeLocked ( () -> m_aStatus.set (STATUS_INSTANTIATED, bInstantiated));
  }

  /**
   * @return true if this singleton was already instantiated,
   *         false if it is active.
   */
  public final boolean isInstantiated ()
  {
    return m_aRWLock.readLockedBoolean ( () -> m_aStatus.get (STATUS_INSTANTIATED));
  }

  protected final void setInPreDestruction (final boolean bInPreDestruction)
  {
    m_aRWLock.writeLocked ( () -> m_aStatus.set (STATUS_IN_PRE_DESTRUCTION, bInPreDestruction));
  }

  /**
   * @return true if this singleton is currently in the phase of
   *         pre destruction, false if it is active or already
   *         destroyed.
   */
  public final boolean isInPreDestruction ()
  {
    return m_aRWLock.readLockedBoolean ( () -> m_aStatus.get (STATUS_IN_PRE_DESTRUCTION));
  }

  protected final void setInDestruction (final boolean bInDestruction)
  {
    m_aRWLock.writeLocked ( () -> m_aStatus.set (STATUS_IN_DESTRUCTION, bInDestruction));
  }

  /**
   * @return true if this singleton is currently in the phase of
   *         destruction, false if it is active or already
   *         destroyed.
   */
  public final boolean isInDestruction ()
  {
    return m_aRWLock.readLockedBoolean ( () -> m_aStatus.get (STATUS_IN_DESTRUCTION));
  }

  protected final void setDestroyed (final boolean bDestroyed)
  {
    m_aRWLock.writeLocked ( () -> m_aStatus.set (STATUS_DESTROYED, bDestroyed));
  }

  /**
   * @return true if this singleton was already destroyed,
   *         false if it is active.
   */
  public final boolean isDestroyed ()
  {
    return m_aRWLock.readLockedBoolean ( () -> m_aStatus.get (STATUS_DESTROYED));
  }

  /**
   * Called before this singleton is destroyed. This method is called when
   * "inPreDestruction" is true, "inDestruction" is still
   * false and "isDestroyed" is false.
   *
   * @param aScopeToBeDestroyed
   *        The scope that will be destroyed. Never null.
   * @throws Exception
   *         If something goes wrong
   */
  @OverrideOnDemand
  protected void onBeforeDestroy (@Nonnull final IScope aScopeToBeDestroyed) throws Exception
  {}

  /*
   * Implementation of {@link IScopeDestructionAware}. Calls the protected
   * {@link #onBeforeDestroy()} method.
   */
  @Override
  public final void onBeforeScopeDestruction (@Nonnull final IScope aScopeToBeDestroyed) throws Exception
  {
    if (LOGGER.isDebugEnabled ())
      LOGGER.debug ("onBeforeScopeDestruction for '" + toString () + "' in scope " + aScopeToBeDestroyed.toString ());

    // Check init state
    if (isInInstantiation ())
      LOGGER.warn ("Object currently in instantiation is destroyed soon: " + toString ());
    else
      if (!isInstantiated ())
        LOGGER.warn ("Object not instantiated is destroyed soon: " + toString ());

    // Check destruction state
    if (isInPreDestruction ())
      LOGGER.error ("Object already in pre destruction is destroyed soon again: " + toString ());
    else
      if (isInDestruction ())
        LOGGER.error ("Object already in destruction is destroyed soon again: " + toString ());
      else
        if (isDestroyed ())
          LOGGER.error ("Object already destroyed is destroyed soon again: " + toString ());

    setInPreDestruction (true);

    onBeforeDestroy (aScopeToBeDestroyed);

    // do not reset PreDestruction - happens in onScopeDestruction
  }

  /**
   * Called when this singleton is destroyed. Perform all cleanup in this
   * method. This method is called when "inPreDestruction" is false
   * , "inDestruction" is true and "isDestroyed" is
   * false.
   *
   * @param aScopeInDestruction
   *        The scope in destruction. Never null.
   * @throws Exception
   *         If something goes wrong
   */
  @OverrideOnDemand
  protected void onDestroy (@Nonnull final IScope aScopeInDestruction) throws Exception
  {}

  /*
   * Implementation of {@link IScopeDestructionAware}. Calls the protected
   * {@link #onDestroy()} method.
   */
  @Override
  public final void onScopeDestruction (@Nonnull final IScope aScopeInDestruction) throws Exception
  {
    if (LOGGER.isDebugEnabled ())
      LOGGER.debug ("onScopeDestruction for '" + toString () + "' in scope " + aScopeInDestruction.toString ());

    // Check init state
    if (isInInstantiation ())
      LOGGER.warn ("Object currently in instantiation is now destroyed: " + toString ());
    else
      if (!isInstantiated ())
        LOGGER.warn ("Object not instantiated is now destroyed: " + toString ());

    // Check destruction state
    if (!isInPreDestruction ())
      LOGGER.error ("Object should be in pre destruction phase but is not: " + toString ());

    if (isInDestruction ())
      LOGGER.error ("Object already in destruction is now destroyed again: " + toString ());
    else
      if (isDestroyed ())
        LOGGER.error ("Object already destroyed is now destroyed again: " + toString ());

    setInDestruction (true);
    // Set after destruction is set to true
    setInPreDestruction (false);
    try
    {
      onDestroy (aScopeInDestruction);
    }
    finally
    {
      // Ensure scope is marked as "destroyed"
      setDestroyed (true);

      // Ensure field is reset even in case of an exception
      setInDestruction (false);
    }
  }

  /**
   * @return true if the object is instantiated and neither in
   *         destruction nor destroyed.
   */
  public final boolean isUsableObject ()
  {
    return isInstantiated () && !isInDestruction () && !isDestroyed ();
  }

  /**
   * Create the key which is used to reference the object within the scope.
   *
   * @param aClass
   *        The class for which the key is to be created. May not be
   *        null.
   * @return The non-null key.
   */
  @Nonnull
  public static final String getSingletonScopeKey (@Nonnull final Class  aClass)
  {
    ValueEnforcer.notNull (aClass, "Class");

    // Preallocate some bytes
    return new StringBuilder (DEFAULT_KEY_LENGTH).append ("singleton.").append (aClass.getName ()).toString ();
  }

  /**
   * Get the singleton object if it is already instantiated inside a scope or
   * null if it is not instantiated.
   *
   * @param 
   *        The type to be returned
   * @param aScope
   *        The scope to check. May be null to avoid constructing a
   *        scope.
   * @param aClass
   *        The class to be checked. May not be null.
   * @return The singleton for the specified class is already instantiated,
   *         null otherwise.
   */
  @Nullable
  public static final  T getSingletonIfInstantiated (@Nullable final IScope aScope,
                                                                                  @Nonnull final Class  aClass)
  {
    ValueEnforcer.notNull (aClass, "Class");
    if (aScope != null)
    {
      final String sSingletonScopeKey = getSingletonScopeKey (aClass);
      final Object aObject = RW_LOCK.readLockedGet ( () -> aScope.attrs ().get (sSingletonScopeKey));
      if (aObject != null)
      {
        // Object is in the scope
        final T aCastedObject = aClass.cast (aObject);
        if (aCastedObject.isUsableObject ())
        {
          // Object has finished initialization
          return aCastedObject;
        }
      }
    }
    return null;
  }

  /**
   * Check if a singleton is already instantiated inside a scope
   *
   * @param aScope
   *        The scope to check. May be null to avoid constructing a
   *        scope.
   * @param aClass
   *        The class to be checked. May not be null.
   * @return true if the singleton for the specified class is
   *         already instantiated, false otherwise.
   */
  public static final boolean isSingletonInstantiated (@Nullable final IScope aScope,
                                                       @Nonnull final Class  aClass)
  {
    return getSingletonIfInstantiated (aScope, aClass) != null;
  }

  /**
   * Instantiate singleton using reflection. The passed class must be a public
   * class. First a public constructor with a single argument of type
   * {@link IScope} is searched. If not found the public no-argument constructor
   * is searched.
   *
   * @param aClass
   *        The class to instantiate. May not be null.
   * @param aScope
   *        The scope to be passed to the instantiated class. Never
   *        null.,
   * @return Never null.
   * @throws IllegalStateException
   *         If instantiation failed
   */
  @Nonnull
  private static  T _instantiateSingleton (@Nonnull final Class  aClass,
                                                                        @Nonnull final IScope aScope)
  {
    // create new object in passed scope
    try
    {
      if (LOGGER.isDebugEnabled ())
        LOGGER.debug ("Created singleton for '" + aClass.toString () + "' in scope " + aScope.toString ());

      // Check if class is public, non-abstract etc.
      if (!ClassHelper.isPublicClass (aClass))
        throw new IllegalStateException ("Class " + aClass.toString () + " is not instancable!");

      // First check, if constructor is present, that takes an IScope argument
      try
      {
        final Constructor  aCtor = aClass.getDeclaredConstructor (IScope.class);

        // Invoke ctor with scope
        return aCtor.newInstance (aScope);
      }
      catch (final NoSuchMethodException ex)
      {
        // Fall through to default ctor
      }
      // Alternatively find the no-argument constructor
      final Constructor  aCtor = aClass.getDeclaredConstructor ((Class  []) null);

      // Don't call "aCtor.setAccessible ()" because on
      // Ubuntu: java.security.AccessControlException: access denied
      // (java.lang.reflect.ReflectPermission suppressAccessChecks)

      // Invoke default ctor
      return aCtor.newInstance ((Object []) null);
    }
    catch (final RuntimeException ex)
    {
      throw ex;
    }
    catch (final Exception ex)
    {
      throw new IllegalStateException ("Error instantiating singleton of class " +
                                       aClass.getName () +
                                       " in scope " +
                                       aScope.toString (),
                                       ex);
    }
  }

  /**
   * Get the singleton object in the passed scope, using the passed class. If
   * the singleton is not yet instantiated, a new instance is created.
   *
   * @param 
   *        The singleton type
   * @param aScope
   *        The scope to be used. May not be null.
   * @param aClass
   *        The class to be used. May not be null. The class must
   *        be public as needs to have a public no-argument constructor.
   * @return The singleton object and never null.
   */
  @Nonnull
  public static final  T getSingleton (@Nonnull final IScope aScope,
                                                                    @Nonnull final Class  aClass)
  {
    ValueEnforcer.notNull (aScope, "aScope");
    ValueEnforcer.notNull (aClass, "Class");

    final String sSingletonScopeKey = getSingletonScopeKey (aClass);

    // check if already contained in passed scope
    T aInstance = RW_LOCK.readLockedGet ( () -> aScope.attrs ().getCastedValue (sSingletonScopeKey));
    if (aInstance == null || aInstance.isInInstantiation ())
    {
      // Not yet present or just in instantiation

      // Safe instantiation check in write lock
      RW_LOCK.writeLock ().lock ();
      try
      {
        // Check again in write lock
        aInstance = aScope.attrs ().getCastedValue (sSingletonScopeKey);
        if (aInstance == null)
        {
          // Main instantiation
          aInstance = _instantiateSingleton (aClass, aScope);

          // Set in scope so that recursive calls to the same singleton are
          // caught appropriately
          aScope.attrs ().putIn (sSingletonScopeKey, aInstance);

          // Start the initialization process
          // Do this after the instance was added to the scope
          aInstance.setInInstantiation (true);
          try
          {
            // Invoke callback method
            aInstance.onAfterInstantiation (aScope);

            // Set "instantiated" only if no exception was thrown
            aInstance.setInstantiated (true);
          }
          finally
          {
            // Ensure field is reset even in case of an exception
            aInstance.setInInstantiation (false);
          }
          // And some statistics
          STATS_INSTANCE_COUNTER.increment (sSingletonScopeKey);
        }
        else
        {
          // May not be instantiated if this method is called from the same
          // thread as the original instantiation
        }
        // We have the instance - maybe from re-querying the scope, maybe from
        // instantiation
      }
      finally
      {
        RW_LOCK.writeLock ().unlock ();
      }
    }
    // This happens too often in practice, therefore this is disabled
    if (SingletonHelper.isDebugConsistency ())
    {
      // Just a small note in case we're returning an unusable object
      if (!aInstance.isUsableObject ())
        LOGGER.warn ("Singleton '" +
                     aClass.getName () +
                     "' is not usable - please check your calling order: " +
                     aInstance.toString (),
                     SingletonHelper.getDebugStackTrace ());
    }
    return aInstance;
  }

  /**
   * Get all singleton objects registered in the respective sub-class of this
   * class.
   *
   * @param 
   *        The singleton type to be retrieved
   * @param aScope
   *        The scope to use. May be null to avoid creating a new
   *        scope.
   * @param aDesiredClass
   *        The desired sub-class of this class. May not be null.
   * @return A non-null list with all instances of the passed class
   *         in the passed scope.
   */
  @Nonnull
  @ReturnsMutableCopy
  public static final  ICommonsList  getAllSingletons (@Nullable final IScope aScope,
                                                                                       @Nonnull final Class  aDesiredClass)
  {
    ValueEnforcer.notNull (aDesiredClass, "DesiredClass");

    final ICommonsList  ret = new CommonsArrayList <> ();
    if (aScope != null)
      for (final Object aScopeValue : aScope.attrs ().values ())
        if (aScopeValue != null && aDesiredClass.isAssignableFrom (aScopeValue.getClass ()))
          ret.add (aDesiredClass.cast (aScopeValue));
    return ret;
  }

  @Override
  @Nonnull
  public String toString ()
  {
    return new ToStringGenerator (this).append ("Status", m_aStatus).getToString ();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy