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

com.phloc.scopes.AbstractSingleton Maven / Gradle / Ivy

There is a newer version: 6.4.4
Show newest version
/**
 * Copyright (C) 2006-2015 phloc systems
 * http://www.phloc.com
 * office[at]phloc[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.phloc.scopes;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.security.AccessController;
import java.util.ArrayList;
import java.util.List;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

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

import com.phloc.commons.GlobalDebug;
import com.phloc.commons.ValueEnforcer;
import com.phloc.commons.annotations.OverrideOnDemand;
import com.phloc.commons.annotations.ReturnsMutableCopy;
import com.phloc.commons.annotations.UsedViaReflection;
import com.phloc.commons.callback.INonThrowingCallableWithParameter;
import com.phloc.commons.exceptions.LoggedRuntimeException;
import com.phloc.commons.lang.ClassHelper;
import com.phloc.commons.mutable.MutableBoolean;
import com.phloc.commons.priviledged.PrivilegedActionAccessibleObjectSetAccessible;
import com.phloc.commons.stats.IStatisticsHandlerKeyedCounter;
import com.phloc.commons.stats.StatisticsManager;
import com.phloc.commons.string.ToStringGenerator;

/**
 * Base class for all singletons.
 * 
 * @author Philip Helger
 */
public abstract class AbstractSingleton implements IScopeDestructionAware
{
  private static final int DEFAULT_KEY_LENGTH = 255;
  private static final Logger s_aLogger = LoggerFactory.getLogger (AbstractSingleton.class);
  private static final IStatisticsHandlerKeyedCounter s_aStatsCounterInstantiate = StatisticsManager.getKeyedCounterHandler (AbstractSingleton.class);

  private boolean m_bInInstantiation = false;
  private boolean m_bInstantiated = false;
  private boolean m_bInDestruction = false;
  private boolean m_bDestroyed = false;

  @Deprecated
  @UsedViaReflection ("For Serializable interface implementation in derived classes!")
  protected AbstractSingleton ()
  {}

  /**
   * 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.writeBoolean (m_bInInstantiation);
    aOOS.writeBoolean (m_bInstantiated);
    aOOS.writeBoolean (m_bInDestruction);
    aOOS.writeBoolean (m_bDestroyed);
  }

  /**
   * 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
   */
  protected final void readAbstractSingletonFields (@Nonnull final ObjectInputStream aOIS) throws IOException
  {
    m_bInInstantiation = aOIS.readBoolean ();
    m_bInstantiated = aOIS.readBoolean ();
    m_bInDestruction = aOIS.readBoolean ();
    m_bDestroyed = aOIS.readBoolean ();
  }

  /**
   * Ctor.
   * 
   * @param sRequiredMethodName
   *        The required method name to check for the correct invocation of the
   *        singleton. Only checked in debugMode.
   */
  @UsedViaReflection
  protected AbstractSingleton (@Nonnull final String sRequiredMethodName)
  {
    ValueEnforcer.notEmpty (sRequiredMethodName, "RequiredMethodName");

    // Check the call stack to avoid manual instantiation
    // Only required while developing
    if (GlobalDebug.isDebugMode ())
    {
      boolean bFound = false;

      // 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)
        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!
   */
  @OverrideOnDemand
  protected void onAfterInstantiation ()
  {}

  protected final void setInInstantiation (final boolean bInInstantiation)
  {
    m_bInInstantiation = 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_bInInstantiation;
  }

  protected final void setInstantiated (final boolean bInstantiated)
  {
    m_bInstantiated = bInstantiated;
  }

  /**
   * @return true if this singleton was already instantiated,
   *         false if it is active.
   */
  public final boolean isInstantiated ()
  {
    return m_bInstantiated;
  }

  protected final void setInDestruction (final boolean bInDestruction)
  {
    m_bInDestruction = 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_bInDestruction;
  }

  protected final void setDestroyed (final boolean bDestroyed)
  {
    m_bDestroyed = bDestroyed;
  }

  /**
   * @return true if this singleton was already destroyed,
   *         false if it is active.
   */
  public final boolean isDestroyed ()
  {
    return m_bDestroyed;
  }

  /**
   * Called when the singleton is destroyed. Perform all cleanup in this method.
   * 
   * @throws Exception
   *         If something goes wrong
   */
  @OverrideOnDemand
  protected void onDestroy () throws Exception
  {}

  /**
   * Implementation of {@link IScopeDestructionAware}. Calls the protected
   * {@link #onDestroy()} method.
   */
  public final void onScopeDestruction () throws Exception
  {
    // Check init state
    if (isInInstantiation ())
      s_aLogger.warn ("Object currently in instantiation is now destroyed: " + toString ());
    else
      if (!isInstantiated ())
        s_aLogger.warn ("Object not instantiated is now destroyed: " + toString ());

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

    setInDestruction (true);
    try
    {
      onDestroy ();
      setDestroyed (true);
    }
    finally
    {
      // 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 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 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
  protected 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 = aScope.getAttributeObject (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.
   */
  protected static final boolean isSingletonInstantiated (@Nullable final IScope aScope,
                                                          @Nonnull final Class  aClass)
  {
    return getSingletonIfInstantiated (aScope, aClass) != null;
  }

  @Nonnull
  private static  T _instantiateSingleton (@Nonnull final Class  aClass,
                                                                        @Nonnull final IScope aScope)
  {
    // create new object in passed scope
    try
    {
      if (s_aLogger.isDebugEnabled ())
        s_aLogger.debug ("Created singleton for '" + aClass + "' in scope " + aScope.toString ());

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

      // Find the now-argument constructor
      final Constructor  aCtor = aClass.getDeclaredConstructor ((Class  []) null);

      // Ubuntu: java.security.AccessControlException: access denied
      // (java.lang.reflect.ReflectPermission suppressAccessChecks)
      if (false)
        AccessController.doPrivileged (new PrivilegedActionAccessibleObjectSetAccessible (aCtor));

      // Invoke default ctor
      final T ret = aCtor.newInstance ((Object []) null);
      return ret;
    }
    catch (final Throwable t)
    {
      throw LoggedRuntimeException.newException (t);
    }
  }

  /**
   * 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 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
  protected 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 = aClass.cast (aScope.getAttributeObject (sSingletonScopeKey));
    if (aInstance == null)
    {
      // Some final objects to access them from the nested inner class
      final MutableBoolean aFinalWasInstantiated = new MutableBoolean (false);

      // Safe instantiation:
      aInstance = aScope.runAtomic (new INonThrowingCallableWithParameter  ()
      {
        public T call (@Nullable final IScope aInnerScope)
        {
          // try to resolve again in case it was set in the meantime
          T aInnerInstance = aClass.cast (aScope.getAttributeObject (sSingletonScopeKey));
          if (aInnerInstance == null)
          {
            // Main instantiation
            aInnerInstance = _instantiateSingleton (aClass, aScope);

            // Set in scope
            aScope.setAttribute (sSingletonScopeKey, aInnerInstance);

            // Remember that we instantiated the object
            aFinalWasInstantiated.set (true);

            // And some statistics
            s_aStatsCounterInstantiate.increment (sSingletonScopeKey);
          }

          // We have the instance - maybe from re-querying the scope, maybe from
          // instantiation
          return aInnerInstance;
        }
      });

      // Call outside the scope sync block, and after the instance was
      // registered in the scope
      if (aFinalWasInstantiated.booleanValue ())
      {
        aInstance.setInInstantiation (true);
        try
        {
          // Invoke virtual method
          aInstance.onAfterInstantiation ();
          aInstance.setInstantiated (true);
        }
        finally
        {
          // Ensure field is reset even in case of an exception
          aInstance.setInInstantiation (false);
        }
      }
    }

    if (false)
    {
      // Just a small note in case we're returning an incomplete object
      if (aInstance.isInInstantiation ())
        s_aLogger.warn ("Singleton is not yet ready - still in instantiation: " + aInstance.toString ());
    }

    return aInstance;
  }

  /**
   * Get all singleton objects registered in the respective sub-class of this
   * class.
   * 
   * @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
  protected static final  List  getAllSingletons (@Nullable final IScope aScope,
                                                                                  @Nonnull final Class  aDesiredClass)
  {
    ValueEnforcer.notNull (aDesiredClass, "DesiredClass");

    final List  ret = new ArrayList  ();
    if (aScope != null)
      for (final Object aScopeValue : aScope.getAllAttributeValues ())
        if (aScopeValue != null && aDesiredClass.isAssignableFrom (aScopeValue.getClass ()))
          ret.add (aDesiredClass.cast (aScopeValue));
    return ret;
  }

  @Override
  @Nonnull
  public String toString ()
  {
    return new ToStringGenerator (this).append ("inInstantiation", m_bInInstantiation)
                                       .append ("instantiated", m_bInstantiated)
                                       .append ("inDestruction", m_bInDestruction)
                                       .append ("destroyed", m_bDestroyed)
                                       .toString ();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy