/**
 * 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 {
                return cl.getResources(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;
            }
          }
        }

}
