/*
 *  @(#)AutoDoc.java
 *
 * Copyright (C) 2002-2003 Matt Albrecht
 * groboclown@users.sourceforge.net
 * http://groboutils.sourceforge.net
 *
 *  Part of the GroboUtils package at:
 *  http://groboutils.sourceforge.net
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a
 *  copy of this software and associated documentation files (the "Software"),
 *  to deal in the Software without restriction, including without limitation
 *  the rights to use, copy, modify, merge, publish, distribute, sublicense,
 *  and/or sell copies of the Software, and to permit persons to whom the 
 *  Software is furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in 
 *  all copies or substantial portions of the Software. 
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL 
 *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 *  DEALINGS IN THE SOFTWARE.
 */
package net.sourceforge.groboutils.autodoc.v1;

import net.sourceforge.groboutils.util.classes.v1.SPISingletonStore;

import net.sourceforge.groboutils.autodoc.v1.defimpl.DefaultAutoDocFactory;
import net.sourceforge.groboutils.autodoc.v1.defimpl.AutoDocTPSet;
import net.sourceforge.groboutils.autodoc.v1.defimpl.AutoDocLogSet;
import net.sourceforge.groboutils.autodoc.v1.defimpl.AutoDocITSet;

import net.sourceforge.groboutils.autodoc.v1.spi.AutoDocFactory;

import java.util.Vector;
 

/**
 * Entry class for loading the AutoDoc pieces for a particular class.  As
 * this class is always instantiated, it does not need an interface. This
 * acts as a central factory for generating all the AutoDoc instances.
 * <P>
 * This default implementation uses a static factory to load each instance's
 * data.  That factory may be set by a using class.  Since initialization of
 * the inner data is lazy-loaded (only loaded when needed, but when needed it
 * is all loaded), the factory may be set at any time after construction, and
 * before a data access member function is called.  If no factory is ever
 * specified, it will load the factory from the fully-qualified class name
 * in the value of the System property specified by the key given by
 * <tt>FACTORY_PROPERTY_NAME</tt>.
 * <P>
 * This class follows a combination of the Abstract Factory and Proxy patterns.
 * AutoDoc acts as a Proxy for an Abstract Factory, allowing the particular
 * implemented factory to be hidden from the user.  However, a "hole" is open
 * to each instance with the method <tt>setFactory()</tt>, allowing the owner
 * to setup the factory to their needs.
 * <P>
 * NOTE: need to update the documentation.  I've since replaced the single
 * factory with an SPI fleet.  The proxied objects are delegators to collections
 * of sub-proxies, which are loaded as late as possible.
 *
 * @author     Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
 * @version    $Date: 2003/02/10 22:52:11 $
 * @since      March 16, 2002
 */
public class AutoDoc
{
    private static final SPISingletonStore s_factoryStore =
        new SPISingletonStore( AutoDocFactory.class,
            SPISingletonStore.NO_MULTIPLES_SILENT );
    
    private volatile boolean notLoaded = true;
    private Vector factories = null;
    private final Class owner;
    private AutoDocLogSet log = new AutoDocLogSet();
    private AutoDocITSet it = new AutoDocITSet();
    private AutoDocTPSet tp = new AutoDocTPSet();
    
    
    /**
     * Base constructor.  Pass in the Class instance which will AutoDoc itself.
     *
     * @param c the class which will be AutoDoced.
     * @exception IllegalArgumentException if the passed-in class is
     *      <tt>null</tt>.
     */
    public AutoDoc( Class c )
    {
        if (c == null)
        {
            throw new IllegalArgumentException("no null arguments");
        }
        this.owner = c;
        
        // lazy load data.
    }
    
    
    /**
     * Get the log implementation.  Each call will return the same object.
     *
     * @return the log for the owning class.
     */
    public AutoDocLog getLog()
    {
        checkLoad();
        return this.log;
    }
    
    
    /**
     * Get the Issue Tracker implementation.  Each call will return the
     * same object.
     *
     * @return the Issue Tracker for the owning class.
     */
    public AutoDocIT getIT()
    {
        checkLoad();
        return this.it;
    }
    
    
    /**
     * Get the Test Procedure Marker implementation.  Each call will return
     * the same object.
     */
    public AutoDocTP getTP()
    {
        checkLoad();
        return this.tp;
    }
    
    //-------------------------------------------------------------------------
    // Protected methods
    
    
    /**
     * Check if the data instances have been loaded.  If they have not, then
     * load the instances, and ensure initialization is not performed again.
     * This lazy-loading is thread-safe.
     */
    protected final void checkLoad()
    {
        if (this.notLoaded)
        {
            // double-check locking pattern.
            // since we're doing this on an atomicly volitile variable,
            // this is fine.  If we did this on a non-atomic variable, this
            // might fail.
            synchronized( this.getClass() )
            {
                if (this.notLoaded)
                {
                    loadInstances();
                    this.notLoaded = false;
                }
            }
        }
    }
    
    
    /**
     * Retrieves the owning class.  Will never return <tt>null</tt>.
     *
     * @return the class which is being AutoDoced.
     */
    protected final Class getOwner()
    {
        return this.owner;
    }
    
    
    /**
     * Loads all class 
     */
    protected void loadInstances()
    {
        AutoDocFactory adf[] = getFactories();
        for (int i = 0; i < adf.length; ++i)
        {
            loadFromFactory( adf[i] );
        }
        cleanUpFactories();
    }
    
    
    /**
     * Adds to all inner instances from the given factory.
     */
    protected void loadFromFactory( AutoDocFactory adf )
    {
        this.log.addLog( adf.createLog( getOwner() ) );
        this.it.addIT( adf.createIT( getOwner() ) );
        this.tp.addTP( adf.createTP( getOwner() ) );
    }
    
    
    /**
     * Returns the factory this implementation knows.  By default, this is the
     * static factory instance.  This is only called at AutoDoc creation time.
     * This is not static, because subclasses may need to re-implement this
     * method.  The factory may be changed through invocation of
     * <tt>setFactory( AutoDocFactory )</tt>.
     *
     * @return the inner factory, or the static singleton if it was never
     *      assigned.
     */
    protected AutoDocFactory[] getFactories()
    {
        AutoDocFactory adf[];
        synchronized( this )
        {
            if (this.factories == null)
            {
                this.factories = new Vector();
                java.util.Enumeration enum = getFactoryStore().getSingletons();
                while (enum.hasMoreElements())
                {
                    addFactory( (AutoDocFactory)enum.nextElement() );
                }
            }
            adf = new AutoDocFactory[ this.factories.size() ];
            this.factories.copyInto( adf );
        }
        return adf;
    }
    
    
    /**
     * After loading the instances, we have no need to keep the memory
     * of the original factories around - they may pollute our loaded
     * proxied objects anyway.  But since we've loaded, we don't want
     * to allow another load.  So keep the vector, but clean it out.
     */
    protected void cleanUpFactories()
    {
        this.factories.removeAllElements();
    }
    
    
    /**
     * Sets the AutoDoc factory instance.  If this method is never invoked, then
     * the default static store will be used instead.
     * <P>
     * The standard factory/singleton pattern used in this framework normally
     * does not allow direct setting of the framework for an instance, but
     * rather for the classloader's class.  Since AutoDoc (or a subclass)
     * is directly instantiated as a central processing point, opening a
     * "hole" into the class allows for an easier method to setup a particular
     * AutoDoc style.  However, this is not the "recommended" usage, since,
     * in general, AutoDoc instances are instantiated independently throughout
     * many different classes, causing independent factory setting to be
     * more difficult.
     *
     * @param adf the new factory to assign.
     * @exception IllegalArgumentException if <tt>adf</tt> is <tt>null</tt>.
     * @exception IllegalStateException if the inner factory has already been
     *      assigned.  This usually indicates that all the objects have already
     *      been loaded.
     */
    protected void addFactory( AutoDocFactory adf )
    {
        if (adf == null)
        {
            throw new IllegalArgumentException("no null args");
        }
        synchronized (this)
        {
            if (!this.notLoaded)
            {
                throw new IllegalStateException("factories already loaded.");
            }
            if (this.factories == null)
            {
                // allow this - it means we will not be using the defaults.
                this.factories = new Vector();
            }
            this.factories.addElement( adf );
        }
    }
    
    
    //-------------------------------------------------------------------------
    // Static methods
        
    /**
     * Retrieve the AutoDocFactory singleton store for setting up the factory
     * to be used for all uninitialized or uncreated AutoDoc instances.
     */
    public static SPISingletonStore getFactoryStore()
    {
        return s_factoryStore;
    }
}

