// Copyright 1998 The Open Group Research Institute.  All rights reserved.
// Copyright 1999 Memesoft.  All rights reserved.

package krb5.lib.ccache;

import krb5.lib.*;
import java.lang.reflect.*;
import java.util.*;

/**
 * The CCacheFactory is implemented as a "Singleton"  (never
 * more than a single CCacheFactory instance per process).
 * We achieve this by providing a single private constuctor
 * which is used to intialize a private static CCacheFactory
 * field. 
 *
 * The call the newCCacheInstance(...) method on the factory, an
 * application must first invoke the static getFactory method to obtain
 * a CCacheFactory handle and then use that handle to invoke the
 * newCCacheInstance() method.
 */
 
public final class CCacheFactory {
    // Known cache types.  A cache type is actually the name of
    // a class that implements the CredentialsCache Interface.
    // The following constants are provided merely to avoid
    // manifest string constants in application code.
    /**
     * File credentials cache type
     */
    public static final String FileCCacheType = "FileCCacheImpl";
    
    /**
     * Vitual memory credentials cache type
     */
    public static final String VirtualMemoryCCacheType = "VmCCacheImpl";
    
    /**
     * Native credentials cache type
     */
    public static final String NativeCCacheType = "NativeCCacheImpl";
  
    // the CCacheTypeNotFoundException is thrown when
    // the caller provides a type i.e,. class name, that
    // cannot be loaded using the class.forName(type) 
    // reflection mthod.
    /**
     * Credentials cache type not found exception
     */
    public static class TypeNotFoundException extends Exception {}

    // the CCacheCtorNotSuportedException is thrown when
    // the caller provides a type i.e,. class name, that
    // can be loaded, but does not provide the required
    // constructor of the form "myType(String info){}".
    /**
     * Credentials cache constructor not supported exception
     */
    public static class CtorNotSupportedException extends Exception {}
   
    // The NotACCacheTypeException is thrown when an instance
    // of the requested type has been constructed, but the
    // the new instance does not implement the CredentialsCache
    // Interface.
    /**
     * Not a credentials cache exception 
     */
    public static class NotACCacheException extends Exception {};
    
    /**
     * Returns a handle to the single CCacheFactory object instance
     */
    public static CCacheFactory getFactory() {
        return factory_;
    }

   /** NewCCacheInstance(String type, String cacheInfo)
    * "Type" must contain the name of a class that implements
    * the CredentialsCache interface.  The factory makes use of 
    * the reflection package to load the specified class by name.
    * The loaded class must support a constructor that takes a single
    * string argument.  If such a constructor exists, the factory 
    * invokes it, passing cacheInfo as the argument.  The contents 
    * of cacheInfo are specific to the cache implementation class and
    * are not interpreted or modified by the factory.
    *
    * The factory maintains a vector of information about
    * previously loaded types.
    *
	* @return CredentialsCache is a result
	* @param type is of type String
	* @param cacheInfo is of type String
	* @exception TypeNotFoundException an exception
	* @exception CtorNotSupportedException an exception
	* @exception NotACCacheException an exception
	* @exception Exception an exception
    */
    public CredentialsCache newCCacheInstance(String type, String cacheInfo) throws
        TypeNotFoundException, CtorNotSupportedException, NotACCacheException, Exception {
        Constructor ctor = null;
        Object newInst = null;
        ClassHandle ch = null;

        try {
            // check for a previously loaded type
            for (Enumeration e = cacheTypes_.elements(); e.hasMoreElements() &&
                ctor == null;) {
                ch = (ClassHandle)e.nextElement();
                if (ch.className.equals(type))
                    ctor = ch.getCtor();
            }
            // if type hasn't been loaded previously, load it now.
            if (ctor == null) {
                ch = new ClassHandle(type);
                ctor = ch.getCtor();
            }
            
            // create the cache instance
            newInst = ctor.newInstance(new Object[]{cacheInfo});
            
        } catch (TypeNotFoundException e) {
            throw e;
        } catch (CtorNotSupportedException e) {
            throw e;
        } catch (Exception e) {
            // XXX needs work.  The constructor of a cache
            // impementation class may want to throw
            // exceptions unique to the that class.
            // We don't want to lose that information.
            // The best we can do now is to rethrow the exception.
            // Perhaps that about the best we can do in any
            // circumstance.
            throw (Exception)e.fillInStackTrace();
        }
       
        if (!ccacheIfClass_.isInstance(newInst))
            throw new NotACCacheException();
        
        // add this type to our vector of previously loaded types
        cacheTypes_.addElement(ch);
     
        return (CredentialsCache) newInst;
    }     

    /**
     * Returns a new instance of credentials cache
     *
	 * @return CredentialsCache is a result
	 * @param type is of type String
	 * @exception TypeNotFoundException an exception
	 * @exception CtorNotSupportedException an exception
	 * @exception NotACCacheException an exception
	 * @exception Exception an exception
     */
    public CredentialsCache newCCacheInstance(String type) throws TypeNotFoundException,
        CtorNotSupportedException, NotACCacheException, Exception {
        return newCCacheInstance(type, null);
    }
    
    /** The ClassHandle class is private because it encapsulates
     * a factory implementation detail, namely, the mapping of
     * concrete implementation class names to their corresponding
     * prototyping constructor (a prototying constructor is one with a
     * single parameter of type CCacheInfo)
     */
    private class ClassHandle {
        /**
         * Class name
         */
        public String className;
        
        /**
         * Constructor
         */
        public Constructor ctor;
        
        /**
         * Class constructor
         */
        public ClassHandle(String type) {
            className = type;
            ctor = null;
        }

        /**
         * Returns constructor
         */
        public Constructor getCtor() throws TypeNotFoundException,
            CtorNotSupportedException {
            try {
                if (ctor == null) {
                    Class c = Class.forName("krb5.lib.ccache." + className);
                    ctor = c.getConstructor(new Class[]{String.class});
                }
            } catch (ClassNotFoundException e) {
                throw new TypeNotFoundException();
            } catch (NoSuchMethodException e1) {
                throw new CtorNotSupportedException();
            }
            
            return ctor;
        }
    }
    
    /**
     * vector of previously loaded cache types
     */
    private Vector cacheTypes_ = new Vector();
   
    /**
     * Class variable used to check whether the type provided 
     * by the caller supports the CredentialsCache interface
     */
    private static Class ccacheIfClass_ = CredentialsCache.class;
        
    // "Singleton" code
    
    /**
     * Singleton : static factory instance
     */
    static private CCacheFactory factory_ = new CCacheFactory(
        new String[] {
            FileCCacheType,
            VirtualMemoryCCacheType
        }
    );
   
    /**
     * Class constructor
     * Private constructor prevents instantiation of multiple factory objects
     * @param cacheTypes is of type String[]
     */
    private CCacheFactory (String[] cacheTypes) {
        for (int i = 0; i < cacheTypes.length; i++)
            cacheTypes_.addElement(new ClassHandle(cacheTypes[i]));
    }
}