/**
 * Copyright (c) 2001, Thai Open Source Software Center Ltd
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 *     Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 
 *     Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 * 
 *     Neither the name of the Thai Open Source Software Center Ltd nor
 *     the names of its contributors may be used to endorse or promote
 *     products derived from this software without specific prior written
 *     permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.relaxng.datatype.helpers;

import org.relaxng.datatype.DatatypeLibraryFactory;
import org.relaxng.datatype.DatatypeLibrary;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.util.Vector;
import java.io.Reader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;

/**
 * Discovers the datatype library implementation from the classpath.
 * 
 * <p>
 * The call of the createDatatypeLibrary method finds an implementation
 * from a given datatype library URI at run-time.
 */
public class DatatypeLibraryLoader implements DatatypeLibraryFactory {
  private final Service service = new Service(DatatypeLibraryFactory.class);

  public DatatypeLibrary createDatatypeLibrary(String uri) {
    for (Enumeration e = service.getProviders();
	 e.hasMoreElements();) {
      DatatypeLibraryFactory factory
	= (DatatypeLibraryFactory)e.nextElement();
      DatatypeLibrary library = factory.createDatatypeLibrary(uri);
      if (library != null)
	return library;
    }
    return null;
  }

	private static class Service {
	  private final Class serviceClass;
	  private final Enumeration configFiles;
	  private Enumeration classNames = null;
	  private final Vector providers = new Vector();
	  private Loader loader;

	  private class ProviderEnumeration implements Enumeration {
	    private int nextIndex = 0;

	    public boolean hasMoreElements() {
	      return nextIndex < providers.size() || moreProviders();
	    }

	    public Object nextElement() {
	      try {
		return providers.elementAt(nextIndex++);
	      }
	      catch (ArrayIndexOutOfBoundsException e) {
		throw new NoSuchElementException();
	      }
	    }
	  }

	  private static class Singleton implements Enumeration {
	    private Object obj;
	    private Singleton(Object obj) {
	      this.obj = obj;
	    }

	    public boolean hasMoreElements() {
	      return obj != null;
	    }

	    public Object nextElement() {
	      if (obj == null)
		throw new NoSuchElementException();
	      Object tem = obj;
	      obj = null;
	      return tem;
	    }
	  }

	  // JDK 1.1
	  private static class Loader {
	    Enumeration getResources(String resName) {
	      ClassLoader cl = Loader.class.getClassLoader();
	      URL url;
	      if (cl == null)
		url = ClassLoader.getSystemResource(resName);
	      else
		url = cl.getResource(resName);
	      return new Singleton(url);
	    }

	    Class loadClass(String name) throws ClassNotFoundException {
	      return Class.forName(name);
	    }
	  }

	  // JDK 1.2+
	  private static class Loader2 extends Loader {
	    private ClassLoader cl;

	    Loader2() {
	      cl = Loader2.class.getClassLoader();
	      // If the thread context class loader has the class loader
	      // of this class as an ancestor, use the thread context class
	      // loader.  Otherwise, the thread context class loader
	      // probably hasn't been set up properly, so don't use it.
	      ClassLoader clt = Thread.currentThread().getContextClassLoader();
	      for (ClassLoader tem = clt; tem != null; tem = tem.getParent())
		if (tem == cl) {
		  cl = clt;
		  break;
		}
	    }

	    Enumeration getResources(String resName) {
	      try {
                Enumeration resources = cl.getResources(resName);
                if (resources.hasMoreElements())
                  return resources;
                // Some application servers apparently do not implement findResources
                // in their class loaders, so fall back to getResource.
                return new Singleton(cl.getResource(resName));
	      }
	      catch (IOException e) {
		return new Singleton(null);
	      }
	    }

	    Class loadClass(String name) throws ClassNotFoundException {
	      return Class.forName(name, true, cl);
	    }
	  }

	  public Service(Class cls) {
	    try {
	      loader = new Loader2();
	    }
	    catch (NoSuchMethodError e) {
	      loader = new Loader();
	    }
	    serviceClass = cls;
	    String resName = "META-INF/services/" + serviceClass.getName();
	    configFiles = loader.getResources(resName);
	  }

	  public Enumeration getProviders() {
	    return new ProviderEnumeration();
	  }

	  synchronized private boolean moreProviders() {
	    for (;;) {
	      while (classNames == null) {
		if (!configFiles.hasMoreElements())
		  return false;
		classNames = parseConfigFile((URL)configFiles.nextElement());
	      }
	      while (classNames.hasMoreElements()) {
		String className = (String)classNames.nextElement();
		try {
		  Class cls = loader.loadClass(className);
		  Object obj = cls.newInstance();
		  if (serviceClass.isInstance(obj)) {
		    providers.addElement(obj);
		    return true;
		  }
		}
		catch (ClassNotFoundException e) { }
		catch (InstantiationException e) { }
		catch (IllegalAccessException e) { }
		catch (LinkageError e) { }
	      }
	      classNames = null;
	    }
	  }

	  private static final int START = 0;
	  private static final int IN_NAME = 1;
	  private static final int IN_COMMENT = 2;

	  private static Enumeration parseConfigFile(URL url) {
	    try {
	      InputStream in = url.openStream();
	      Reader r;
	      try {
		r = new InputStreamReader(in, "UTF-8");
	      }
	      catch (UnsupportedEncodingException e) {
		r = new InputStreamReader(in, "UTF8");
	      }
	      r = new BufferedReader(r);
	      Vector tokens = new Vector();
	      StringBuffer tokenBuf = new StringBuffer();
	      int state = START;
	      for (;;) {
		int n = r.read();
		if (n < 0)
		  break;
		char c = (char)n;
		switch (c) {
		case '\r':
		case '\n':
		  state = START;
		  break;
		case ' ':
		case '\t':
		  break;
		case '#':
		  state = IN_COMMENT;
		  break;
		default:
		  if (state != IN_COMMENT) {
		    state = IN_NAME;
		    tokenBuf.append(c);
		  }
		  break;
		}
		if (tokenBuf.length() != 0 && state != IN_NAME) {
		  tokens.addElement(tokenBuf.toString());
		  tokenBuf.setLength(0);
		}
	      }
	      if (tokenBuf.length() != 0)
		tokens.addElement(tokenBuf.toString());
	      return tokens.elements();
	    }
	    catch (IOException e) {
	      return null;
	    }
	  }
	}
  
}

