package net.sf.jasperreports.engine.design;

import java.io.Serializable;
import java.util.Map;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.fill.JREvaluator;
import net.sf.jasperreports.engine.util.JRClassLoader;
import net.sf.jasperreports.engine.util.JRProperties;
import org.apache.commons.collections.ReferenceMap;

public abstract class JRAbstractJavaCompiler extends JRAbstractCompiler {
  public static final String PROPERTY_EVALUATOR_CLASS_REFERENCE_FIX_ENABLED = "net.sf.jasperreports.evaluator.class.reference.fix.enabled";
  
  private static ThreadLocal classFromBytesRef = new ThreadLocal();
  
  private static Object CLASS_CACHE_NULL_KEY = new Object();
  
  private static Map classCache = (Map)new ReferenceMap(2, 1);
  
  protected JRAbstractJavaCompiler(boolean needsSourceFiles) {
    super(needsSourceFiles);
  }
  
  protected JREvaluator loadEvaluator(Serializable compileData, String className) throws JRException {
    JREvaluator evaluator = null;
    try {
      Class clazz = getClassFromCache(className);
      if (clazz == null) {
        clazz = JRClassLoader.loadClassFromBytes(className, (byte[])compileData);
        putClassInCache(className, clazz);
      } 
      if (JRProperties.getBooleanProperty("net.sf.jasperreports.evaluator.class.reference.fix.enabled"))
        classFromBytesRef.set(clazz); 
      evaluator = (JREvaluator)clazz.newInstance();
    } catch (Exception e) {
      throw new JRException("Error loading expression class : " + className, e);
    } 
    return evaluator;
  }
  
  protected static Object classCacheKey() {
    ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
    Object key = (contextClassLoader == null) ? CLASS_CACHE_NULL_KEY : contextClassLoader;
    return key;
  }
  
  protected static synchronized Class getClassFromCache(String className) {
    Object key = classCacheKey();
    Map contextMap = (Map)classCache.get(key);
    Class cachedClass = null;
    if (contextMap != null)
      cachedClass = (Class)contextMap.get(className); 
    return cachedClass;
  }
  
  protected static synchronized void putClassInCache(String className, Class loadedClass) {
    ReferenceMap referenceMap;
    Object key = classCacheKey();
    Map contextMap = (Map)classCache.get(key);
    if (contextMap == null) {
      referenceMap = new ReferenceMap(0, 1);
      classCache.put(key, referenceMap);
    } 
    referenceMap.put(className, loadedClass);
  }
}
