/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.xerces.parsers;

import org.apache.xerces.util.ShadowedSymbolTable;
import org.apache.xerces.util.SymbolTable;
import org.apache.xerces.util.SynchronizedSymbolTable;
import org.apache.xerces.util.XMLGrammarPoolImpl;
import org.apache.xerces.xni.grammars.Grammar;
import org.apache.xerces.xni.grammars.XMLGrammarDescription;
import org.apache.xerces.xni.grammars.XMLGrammarPool;

/**
 * A parser pool that enables caching of grammars. The caching parser
 * pool is constructed with a specific symbol table and grammar pool
 * that has already been populated with the grammars used by the
 * application.
 * <p>
 * Once the caching parser pool is constructed, specific parser
 * instances are created by calling the appropriate factory method
 * on the parser pool.
 * <p>
 * <strong>Note:</strong> There is a performance penalty for using
 * a caching parser pool due to thread safety. Access to the symbol 
 * table and grammar pool must be synchronized to ensure the safe
 * operation of the symbol table and grammar pool.
 * <p>
 * <strong>Note:</strong> If performance is critical, then another
 * mechanism needs to be used instead of the caching parser pool.
 * One approach would be to create parser instances that do not
 * share these structures. Instead, each instance would get its
 * own copy to use while parsing. This avoids the synchronization
 * overhead at the expense of more memory and the time required
 * to copy the structures for each new parser instance. And even
 * when a parser instance is re-used, there is a potential for a
 * memory leak due to new symbols being added to the symbol table
 * over time. In other words, always take caution to make sure
 * that your application is thread-safe and avoids leaking memory.
 *
 * @author Andy Clark, IBM
 *
 * @version $Id: CachingParserPool.java 699902 2008-09-28 21:40:49Z mrglavas $
 */
public class CachingParserPool {

    //
    // Constants
    //

    /** Default shadow symbol table (false). */
    public static final boolean DEFAULT_SHADOW_SYMBOL_TABLE = false;

    /** Default shadow grammar pool (false). */
    public static final boolean DEFAULT_SHADOW_GRAMMAR_POOL = false;

    //
    // Data
    //

    /** 
     * Symbol table. The symbol table that the caching parser pool is
     * constructed with is automatically wrapped in a synchronized
     * version for thread-safety.
     */
    protected SymbolTable fSynchronizedSymbolTable;

    /** 
     * Grammar pool. The grammar pool that the caching parser pool is
     * constructed with is automatically wrapped in a synchronized
     * version for thread-safety.
     */
    protected XMLGrammarPool fSynchronizedGrammarPool;

    /** 
     * Shadow the symbol table for new parser instances. If true,
     * new parser instances use shadow copies of the main symbol
     * table and are not allowed to add new symbols to the main
     * symbol table. New symbols are added to the shadow symbol
     * table and are local to the parser instance.
     */
    protected boolean fShadowSymbolTable = DEFAULT_SHADOW_SYMBOL_TABLE;

    /** 
     * Shadow the grammar pool for new parser instances. If true,
     * new parser instances use shadow copies of the main grammar
     * pool and are not allowed to add new grammars to the main
     * grammar pool. New grammars are added to the shadow grammar
     * pool and are local to the parser instance.
     */
    protected boolean fShadowGrammarPool = DEFAULT_SHADOW_GRAMMAR_POOL;

    //
    // Constructors
    //

    /** Default constructor. */
    public CachingParserPool() {
        this(new SymbolTable(), new XMLGrammarPoolImpl());
    } // <init>()

    /**
     * Constructs a caching parser pool with the specified symbol table
     * and grammar pool.
     * 
     * @param symbolTable The symbol table.
     * @param grammarPool The grammar pool.
     */
    public CachingParserPool(SymbolTable symbolTable, XMLGrammarPool grammarPool) {
        fSynchronizedSymbolTable = new SynchronizedSymbolTable(symbolTable);
        fSynchronizedGrammarPool = new SynchronizedGrammarPool(grammarPool);
    } // <init>(SymbolTable,XMLGrammarPool)

    //
    // Public methods
    //

    /** Returns the symbol table. */
    public SymbolTable getSymbolTable() {
        return fSynchronizedSymbolTable;
    } // getSymbolTable():SymbolTable

    /** Returns the grammar pool. */
    public XMLGrammarPool getXMLGrammarPool() {
        return fSynchronizedGrammarPool;
    } // getXMLGrammarPool():XMLGrammarPool

    // setters and getters

    /** 
     * Sets whether new parser instance receive shadow copies of the
     * main symbol table.
     *
     * @param shadow If true, new parser instances use shadow copies 
     *               of the main symbol table and are not allowed to
     *               add new symbols to the main symbol table. New
     *               symbols are added to the shadow symbol table and
     *               are local to the parser instance. If false, new
     *               parser instances are allowed to add new symbols
     *               to the main symbol table.
     */
    public void setShadowSymbolTable(boolean shadow) {
        fShadowSymbolTable = shadow;
    } // setShadowSymbolTable(boolean)

    // factory methods

    /** Creates a new DOM parser. */
    public DOMParser createDOMParser() {
        SymbolTable symbolTable = fShadowSymbolTable
                                ? new ShadowedSymbolTable(fSynchronizedSymbolTable)
                                : fSynchronizedSymbolTable;
        XMLGrammarPool grammarPool = fShadowGrammarPool
                                ? new ShadowedGrammarPool(fSynchronizedGrammarPool)
                                : fSynchronizedGrammarPool;
        return new DOMParser(symbolTable, grammarPool);
    } // createDOMParser():DOMParser

    /** Creates a new SAX parser. */
    public SAXParser createSAXParser() {
        SymbolTable symbolTable = fShadowSymbolTable
                                ? new ShadowedSymbolTable(fSynchronizedSymbolTable)
                                : fSynchronizedSymbolTable;
        XMLGrammarPool grammarPool = fShadowGrammarPool
                                ? new ShadowedGrammarPool(fSynchronizedGrammarPool)
                                : fSynchronizedGrammarPool;
        return new SAXParser(symbolTable, grammarPool);
    } // createSAXParser():SAXParser

    //
    // Classes
    //

    /**
     * Synchronized grammar pool.
     *
     * @author Andy Clark, IBM
     */
    public static final class SynchronizedGrammarPool
        implements XMLGrammarPool {

        //
        // Data
        //

        /** Main grammar pool. */
        private XMLGrammarPool fGrammarPool;

        //
        // Constructors
        //

        /** Constructs a synchronized grammar pool. */
        public SynchronizedGrammarPool(XMLGrammarPool grammarPool) {
            fGrammarPool = grammarPool;
        } // <init>(XMLGrammarPool)

        //
        // GrammarPool methods
        //

        // retrieve the initial set of grammars for the validator
        // to work with.
        // REVISIT:  does this need to be synchronized since it's just reading?
        // @param grammarType type of the grammars to be retrieved.
        // @return the initial grammar set the validator may place in its "bucket"
        public Grammar [] retrieveInitialGrammarSet(String grammarType ) {
            synchronized (fGrammarPool) {
                return fGrammarPool.retrieveInitialGrammarSet(grammarType);
            }
        } // retrieveInitialGrammarSet(String):  Grammar[]

        // retrieve a particular grammar.
        // REVISIT:  does this need to be synchronized since it's just reading?
        // @param gDesc description of the grammar to be retrieved
        // @return Grammar corresponding to gDesc, or null if none exists.
        public Grammar retrieveGrammar(XMLGrammarDescription gDesc) {
            synchronized (fGrammarPool) {
                return fGrammarPool.retrieveGrammar(gDesc);
            }
        } // retrieveGrammar(XMLGrammarDesc):  Grammar

        // give the grammarPool the option of caching these grammars.
        // This certainly must be synchronized.
        // @param grammarType The type of the grammars to be cached.
        // @param grammars the Grammars that may be cached (unordered, Grammars previously
        //  given to the validator may be included).
        public void cacheGrammars(String grammarType, Grammar[] grammars) {
            synchronized (fGrammarPool) {
                fGrammarPool.cacheGrammars(grammarType, grammars);
            }
        } // cacheGrammars(String, Grammar[]);

        /** lock the grammar pool */
        public void lockPool() {
            synchronized (fGrammarPool) {
                fGrammarPool.lockPool();
            }
        } // lockPool()

        /** clear the grammar pool */
        public void clear() {
            synchronized (fGrammarPool) {
                fGrammarPool.clear();
            }
        } // lockPool()

        /** unlock the grammar pool */
        public void unlockPool() {
            synchronized (fGrammarPool) {
                fGrammarPool.unlockPool();
            }
        } // unlockPool()

        /***
         * Methods corresponding to original (pre Xerces2.0.0final)
         * grammarPool have been commented out.
         */
        /**
         * Puts the specified grammar into the grammar pool.
         * 
         * @param key Key to associate with grammar.
         * @param grammar Grammar object.
         */
        /******
        public void putGrammar(String key, Grammar grammar) {
            synchronized (fGrammarPool) {
                fGrammarPool.putGrammar(key, grammar);
            }
        } // putGrammar(String,Grammar)
        *******/

        /**
         * Returns the grammar associated to the specified key.
         * 
         * @param key The key of the grammar.
         */
        /**********
        public Grammar getGrammar(String key) {
            synchronized (fGrammarPool) {
                return fGrammarPool.getGrammar(key);
            }
        } // getGrammar(String):Grammar
        ***********/

        /**
         * Removes the grammar associated to the specified key from the
         * grammar pool and returns the removed grammar.
         * 
         * @param key The key of the grammar.
         */
        /**********
        public Grammar removeGrammar(String key) {
            synchronized (fGrammarPool) {
                return fGrammarPool.removeGrammar(key);
            }
        } // removeGrammar(String):Grammar
        ******/

        /**
         * Returns true if the grammar pool contains a grammar associated
         * to the specified key.
         *
         * @param key The key of the grammar.
         */
        /**********
        public boolean containsGrammar(String key) {
            synchronized (fGrammarPool) {
                return fGrammarPool.containsGrammar(key);
            }
        } // containsGrammar(String):boolean
        ********/

    } // class SynchronizedGrammarPool

    /**
     * Shadowed grammar pool.
     * This class is predicated on the existence of a concrete implementation;
     * so using our own doesn't seem to bad an idea.
     *
     * @author Andy Clark, IBM
     * @author Neil Graham, IBM
     */
    public static final class ShadowedGrammarPool
        extends XMLGrammarPoolImpl {

        //
        // Data
        //

        /** Main grammar pool. */
        private XMLGrammarPool fGrammarPool;

        //
        // Constructors
        //

        /** Constructs a shadowed grammar pool. */
        public ShadowedGrammarPool(XMLGrammarPool grammarPool) {
            fGrammarPool = grammarPool;
        } // <init>(GrammarPool)

        //
        // GrammarPool methods
        //

        /**
         * Retrieve the initial set of grammars for the validator to work with.
         * REVISIT:  does this need to be synchronized since it's just reading?
         * 
         * @param grammarType Type of the grammars to be retrieved.
         * @return            The initial grammar set the validator may place in its "bucket"
         */
        public Grammar [] retrieveInitialGrammarSet(String grammarType ) {
            Grammar [] grammars = super.retrieveInitialGrammarSet(grammarType);
            if (grammars != null) return grammars;
            return fGrammarPool.retrieveInitialGrammarSet(grammarType);
        } // retrieveInitialGrammarSet(String):  Grammar[]

        /**
         * Retrieve a particular grammar.
         * REVISIT:  does this need to be synchronized since it's just reading?
         *
         * @param gDesc Description of the grammar to be retrieved
         * @return      Grammar corresponding to gDesc, or null if none exists.
         */
        public Grammar retrieveGrammar(XMLGrammarDescription gDesc) {
            Grammar g = super.retrieveGrammar(gDesc);
            if(g != null) return g;
            return fGrammarPool.retrieveGrammar(gDesc);
        } // retrieveGrammar(XMLGrammarDesc):  Grammar

        /** 
         * Give the grammarPool the option of caching these grammars.
         * This certainly must be synchronized.
         * 
         * @param grammarType The type of the grammars to be cached.
         * @param grammars    The Grammars that may be cached (unordered, Grammars previously
         *  		      given to the validator may be included).
         */
        public void cacheGrammars(String grammarType, Grammar[] grammars) { 
           // better give both grammars a shot...
           super.cacheGrammars(grammarType, grammars);
           fGrammarPool.cacheGrammars(grammarType, grammars);
        } // cacheGrammars(grammarType, Grammar[]);

        /**
         * Returns the grammar associated to the specified description.
         * 
         * @param desc The description of the grammar.
         */
        public Grammar getGrammar(XMLGrammarDescription desc) {

            if (super.containsGrammar(desc)) {
                return super.getGrammar(desc);
            }
            return null;

        } // getGrammar(XMLGrammarDescription):Grammar

        /**
         * Returns true if the grammar pool contains a grammar associated
         * to the specified description.
         *
         * @param desc The description of the grammar.
         */
        public boolean containsGrammar(XMLGrammarDescription desc) {
            return super.containsGrammar(desc);
        } // containsGrammar(XMLGrammarDescription):boolean

    } // class ShadowedGrammarPool

} // class CachingParserPool
