/**
 * Copyright (c) 2002-2006 IBM Corporation and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v20.html
 *
 * Contributors:
 *   IBM - Initial API and implementation
 */
package org.eclipse.emf.ecore.xmi.impl;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.eclipse.emf.ecore.xmi.XMLDefaultHandler;
import org.eclipse.emf.ecore.xmi.XMLHelper;
import org.eclipse.emf.ecore.xmi.XMLLoad;
import org.eclipse.emf.ecore.xmi.XMLParserPool;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.xml.sax.SAXException;


/**
 * This is the default thread safe implementation of XMLParserPool. 
 * This implementation is tuned for caching parsers and handlers created using same loading options. 
 * To avoid possible memory leak (in case user is trying to parse documents using different options for every parse), 
 * there is a restriction on the size of the pool. 
 * The key used for handler caching is based on the option map passed to load.
 */
public class XMLParserPoolImpl implements XMLParserPool
{
  private final Map<Map<?, ?>, List<SAXParser>> parserCache = new HashMap<Map<?, ?>, List<SAXParser>>();

  private final Map<Map<?, ?>, List<XMLDefaultHandler>> handlersCache;
  
  private final int size;
  
  /**
   * Creates an instance that caches only parsers but not handlers.
   * @see #XMLParserPoolImpl(boolean)
   */
  public XMLParserPoolImpl()
  {
    this(200, false);
  }

  /**
   * Creates an instance that caches parsers and caches handlers as specified.
   * @param useHandlerCache indicates whether handler caching should be use.
   * @see #XMLParserPoolImpl(boolean)
   */
  public XMLParserPoolImpl(boolean useHandlerCache)
  {
    this(200, useHandlerCache);
  }

  /**
   * Creates an instance that caches parsers and caches handlers as specified.
   * @param size indicates the maximum number of instances parser or handler instances that will be cached.
   * @param useHandlerCache indicates whether handler caching should be use.
   * @see #XMLParserPoolImpl(boolean)
   */
  public XMLParserPoolImpl(int size, boolean useHandlerCache)
  {
    this.size = size;
    handlersCache = useHandlerCache ? new HashMap<Map<?, ?>, List<XMLDefaultHandler>>() : null;
  }
  
  /**
   * @see XMLParserPool#get(Map, Map, boolean)
   */
  public synchronized SAXParser get(Map<String, Boolean> features, Map<String, ?> properties, boolean useLexicalHandler) throws ParserConfigurationException, SAXException
  {
    Map<Object, Object> map = new HashMap<Object, Object>();
    map.putAll(features);
    map.putAll(properties);
    map.put(XMLResource.OPTION_USE_LEXICAL_HANDLER, useLexicalHandler ? Boolean.TRUE : Boolean.FALSE);
    if (parserCache.size() > size)
    {
      parserCache.clear();
    }
    List<SAXParser> list = parserCache.get(map);
    if (list != null)
    {
      int size = list.size();
      if (size > 0)
      {
        return list.remove(size - 1);
      }
      else
      {
        return makeParser(features, properties);
      }
    }
    else
    {
      parserCache.put(map, new ArrayList<SAXParser>());
      return makeParser(features, properties);
    }
  }

  /**
   * @see XMLParserPool#release(SAXParser, Map, Map, boolean)
   */
  public synchronized void release(SAXParser parser, Map<String, Boolean> features, Map<String, ?> properties, boolean useLexicalHandler)
  {
    Map<Object, Object> map = new HashMap<Object, Object>();
    map.putAll(features);
    map.putAll(properties);
    map.put(XMLResource.OPTION_USE_LEXICAL_HANDLER, useLexicalHandler ? Boolean.TRUE : Boolean.FALSE);
    List<SAXParser> list = parserCache.get(map);
    if (list.size() < size)
    {
      list.add(parser);
    }
  }

  protected SAXParser makeParser(Map<String, Boolean> features, Map<String, ?> properties) throws ParserConfigurationException, SAXException
  {
    SAXParserFactory factory = SAXParserFactory.newInstance();
    factory.setValidating(false);
    factory.setNamespaceAware(true);
    SAXParser parser = factory.newSAXParser();
    
    // set parser features and properties
    if (features != null)
    {
      for (Map.Entry<String, Boolean> entry : features.entrySet())
      {
        parser.getXMLReader().setFeature(entry.getKey(), entry.getValue());
      }
    }
    if (properties != null)
    {
      for (Map.Entry<String, ?> entry : properties.entrySet())
      {
        parser.getXMLReader().setProperty(entry.getKey(), entry.getValue());
      }
    }
    return parser;
  }

  public synchronized XMLDefaultHandler getDefaultHandler(XMLResource resource, XMLLoad xmlLoad, XMLHelper helper, Map<?, ?> options)
  {
    if (handlersCache != null)
    {
      if (handlersCache.size() > size)
      {
        handlersCache.clear();
      }
      List<XMLDefaultHandler> list = handlersCache.get(options);
      if (list != null)
      {
        int size = list.size();
        if (size > 0)
        {
          XMLDefaultHandler handler = list.remove(size - 1);
          handler.prepare(resource, helper, options);
          return handler;
        }
      }
      else
      {
        handlersCache.put(options, new ArrayList<XMLDefaultHandler>());
      }
    }
    return xmlLoad.createDefaultHandler();
  }

  public synchronized void releaseDefaultHandler(XMLDefaultHandler handler, Map<?, ?> options)
  {
    if (handlersCache != null)
    {
      handler.reset();
      List<XMLDefaultHandler> list = handlersCache.get(options);
      if (list == null)
      {
        list = new ArrayList<XMLDefaultHandler>();
        handlersCache.put(options, list);
      }
      else if (list.size() < size)
      {
        list.add(handler);
      }
    }
  }
}
