package net.sf.jasperreports.engine.util;

import java.util.Map;
import net.sf.jasperreports.engine.JRException;
import org.apache.commons.collections.ReferenceMap;

public class JRSingletonCache {
  private static final Object CONTEXT_KEY_NULL = new Object();
  
  private final ReferenceMap cache;
  
  private final Class itf;
  
  public JRSingletonCache(Class itf) {
    this.cache = new ReferenceMap(2, 1);
    this.itf = itf;
  }
  
  public synchronized Object getCachedInstance(String className) throws JRException {
    Map contextCache = getContextInstanceCache();
    Object instance = contextCache.get(className);
    if (instance == null) {
      instance = createInstance(className);
      contextCache.put(className, instance);
    } 
    return instance;
  }
  
  protected Object createInstance(String className) throws JRException {
    try {
      Class clazz = JRClassLoader.loadClassForName(className);
      if (this.itf != null && !this.itf.isAssignableFrom(clazz))
        throw new JRException("Class \"" + className + "\" should be compatible with \"" + this.itf.getName() + "\""); 
      return clazz.newInstance();
    } catch (ClassNotFoundException e) {
      throw new JRException("Class " + className + " not found.", e);
    } catch (InstantiationException e) {
      throw new JRException("Error instantiating class " + className + ".", e);
    } catch (IllegalAccessException e) {
      throw new JRException("Error instantiating class " + className + ".", e);
    } 
  }
  
  protected Map getContextInstanceCache() {
    ReferenceMap referenceMap;
    Object contextKey = getContextKey();
    Map contextCache = (Map)this.cache.get(contextKey);
    if (contextCache == null) {
      referenceMap = new ReferenceMap();
      this.cache.put(contextKey, referenceMap);
    } 
    return (Map)referenceMap;
  }
  
  protected Object getContextKey() {
    Object key = Thread.currentThread().getContextClassLoader();
    if (key == null)
      key = CONTEXT_KEY_NULL; 
    return key;
  }
}
