package org.apache.xerces.parsers;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.Properties;

class ObjectFactory {
  private static final String DEFAULT_PROPERTIES_FILENAME = "xerces.properties";
  
  private static final boolean DEBUG = false;
  
  private static Properties fXercesProperties = null;
  
  private static long fLastModified = -1L;
  
  static Object createObject(String factoryId, String fallbackClassName) throws ConfigurationError {
    return createObject(factoryId, null, fallbackClassName);
  }
  
  public static Object createObject(String factoryId, String propertiesFilename, String fallbackClassName) throws ConfigurationError {
    debugPrintln("debug is on");
    SecuritySupport ss = SecuritySupport.getInstance();
    ClassLoader cl = findClassLoader();
    try {
      String systemProp = ss.getSystemProperty(factoryId);
      if (systemProp != null) {
        debugPrintln("found system property, value=" + systemProp);
        return newInstance(systemProp, cl, true);
      } 
    } catch (SecurityException se) {}
    String factoryClassName = null;
    if (propertiesFilename == null) {
      String javah = ss.getSystemProperty("java.home");
      propertiesFilename = javah + File.separator + "lib" + File.separator + "xerces.properties";
      File propertiesFile = new File(propertiesFilename);
      boolean propertiesFileExists = ss.getFileExists(propertiesFile);
      synchronized (ObjectFactory.class) {
        boolean loadProperties = false;
        if (fLastModified >= 0L) {
          if (propertiesFileExists && fLastModified < (fLastModified = ss.getLastModified(propertiesFile))) {
            loadProperties = true;
          } else if (!propertiesFileExists) {
            fLastModified = -1L;
            fXercesProperties = null;
          } 
        } else if (propertiesFileExists) {
          loadProperties = true;
          fLastModified = ss.getLastModified(propertiesFile);
        } 
        if (loadProperties)
          try {
            fXercesProperties = new Properties();
            FileInputStream fis = ss.getFileInputStream(propertiesFile);
            fXercesProperties.load(fis);
            fis.close();
          } catch (Exception x) {
            fXercesProperties = null;
            fLastModified = -1L;
          }  
      } 
      if (fXercesProperties != null)
        factoryClassName = fXercesProperties.getProperty(factoryId); 
    } else {
      try {
        FileInputStream fis = ss.getFileInputStream(new File(propertiesFilename));
        Properties props = new Properties();
        props.load(fis);
        fis.close();
        factoryClassName = props.getProperty(factoryId);
      } catch (Exception x) {}
    } 
    if (factoryClassName != null) {
      debugPrintln("found in " + propertiesFilename + ", value=" + factoryClassName);
      return newInstance(factoryClassName, cl, true);
    } 
    Object provider = findJarServiceProvider(factoryId);
    if (provider != null)
      return provider; 
    if (fallbackClassName == null)
      throw new ConfigurationError("Provider for " + factoryId + " cannot be found", null); 
    debugPrintln("using fallback, value=" + fallbackClassName);
    return newInstance(fallbackClassName, cl, true);
  }
  
  private static void debugPrintln(String msg) {}
  
  static ClassLoader findClassLoader() throws ConfigurationError {
    SecuritySupport ss = SecuritySupport.getInstance();
    ClassLoader cl = ss.getContextClassLoader();
    if (cl == null)
      cl = ObjectFactory.class.getClassLoader(); 
    return cl;
  }
  
  static Object newInstance(String className, ClassLoader cl, boolean doFallback) throws ConfigurationError {
    try {
      Class providerClass = findProviderClass(className, cl, doFallback);
      Object instance = providerClass.newInstance();
      debugPrintln("created new instance of " + providerClass + " using ClassLoader: " + cl);
      return instance;
    } catch (ClassNotFoundException x) {
      throw new ConfigurationError("Provider " + className + " not found", x);
    } catch (Exception x) {
      throw new ConfigurationError("Provider " + className + " could not be instantiated: " + x, x);
    } 
  }
  
  static Class findProviderClass(String className, ClassLoader cl, boolean doFallback) throws ClassNotFoundException, ConfigurationError {
    Class providerClass;
    SecurityManager security = System.getSecurityManager();
    try {
      if (security != null)
        security.checkPackageAccess(className); 
    } catch (SecurityException e) {
      throw e;
    } 
    if (cl == null) {
      providerClass = Class.forName(className);
    } else {
      try {
        providerClass = cl.loadClass(className);
      } catch (ClassNotFoundException x) {
        if (doFallback) {
          cl = ObjectFactory.class.getClassLoader();
          providerClass = cl.loadClass(className);
        } else {
          throw x;
        } 
      } 
    } 
    return providerClass;
  }
  
  private static Object findJarServiceProvider(String factoryId) throws ConfigurationError {
    BufferedReader rd;
    SecuritySupport ss = SecuritySupport.getInstance();
    String serviceId = "META-INF/services/" + factoryId;
    InputStream is = null;
    ClassLoader cl = ss.getContextClassLoader();
    if (cl != null) {
      is = ss.getResourceAsStream(cl, serviceId);
      if (is == null) {
        cl = ObjectFactory.class.getClassLoader();
        is = ss.getResourceAsStream(cl, serviceId);
      } 
    } else {
      cl = ObjectFactory.class.getClassLoader();
      is = ss.getResourceAsStream(cl, serviceId);
    } 
    if (is == null)
      return null; 
    debugPrintln("found jar resource=" + serviceId + " using ClassLoader: " + cl);
    try {
      rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
    } catch (UnsupportedEncodingException e) {
      rd = new BufferedReader(new InputStreamReader(is));
    } 
    String factoryClassName = null;
    try {
      factoryClassName = rd.readLine();
      rd.close();
    } catch (IOException x) {
      return null;
    } 
    if (factoryClassName != null && !"".equals(factoryClassName)) {
      debugPrintln("found in resource, value=" + factoryClassName);
      return newInstance(factoryClassName, cl, false);
    } 
    return null;
  }
  
  static class ConfigurationError extends Error {
    private Exception exception;
    
    ConfigurationError(String msg, Exception x) {
      super(msg);
      this.exception = x;
    }
    
    Exception getException() {
      return this.exception;
    }
  }
}
