content: /**
   * Copyright (c) 2013, Andre Steingress
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
   * following conditions are met:
   *
   * 1.) Redistributions of source code must retain the above copyright notice, this list of conditions and the following
   * disclaimer.
   * 2.) 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.
   * 3.) Neither the name of Andre Steingress 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 COPYRIGHT HOLDER 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.gcontracts.util;
  
  import java.io.BufferedReader;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.net.URL;
  import java.util.*;
  
  /**
   * <p>Finds and loads implementation classes of interface {@link org.gcontracts.common.spi.Lifecycle}.</p>
   *
   * @author ast
   **/
  public final class LifecycleImplementationLoader<S>
      implements Iterable<S>
  {
  
      private static final String PREFIX = "META-INF/services/";
  
      private Class<S> service;
      private ClassLoader loader;
      private LinkedHashMap<String,S> providers = new LinkedHashMap<String,S>();
      private LazyIterator lookupIterator;
  
      public void reload() {
          providers.clear();
          lookupIterator = new LazyIterator(service, loader);
      }
  
      private LifecycleImplementationLoader(Class<S> svc, ClassLoader cl) {
          service = svc;
          loader = cl;
          reload();
      }
  
      private static void fail(Class service, String msg, Throwable cause)
          throws ServiceConfigurationError
      {
          throw new ServiceConfigurationError(service.getName() + ": " + msg,
                                              cause);
      }
  
      private static void fail(Class service, String msg)
          throws ServiceConfigurationError
      {
          throw new ServiceConfigurationError(service.getName() + ": " + msg);
      }
  
      private static void fail(Class service, URL u, int line, String msg)
          throws ServiceConfigurationError
      {
          fail(service, u + ":" + line + ": " + msg);
      }
  
      private int parseLine(Class service, URL u, BufferedReader r, int lc,
                            List<String> names)
          throws IOException, ServiceConfigurationError
      {
          String ln = r.readLine();
          if (ln == null) {
              return -1;
          }
          int ci = ln.indexOf('#');
          if (ci >= 0) ln = ln.substring(0, ci);
          ln = ln.trim();
          int n = ln.length();
          if (n != 0) {
              if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
                  fail(service, u, lc, "Illegal configuration-file syntax");
              int cp = ln.codePointAt(0);
              if (!Character.isJavaIdentifierStart(cp))
                  fail(service, u, lc, "Illegal provider-class name: " + ln);
              for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
                  cp = ln.codePointAt(i);
                  if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                      fail(service, u, lc, "Illegal provider-class name: " + ln);
              }
              if (!providers.containsKey(ln) && !names.contains(ln))
                  names.add(ln);
          }
          return lc + 1;
      }
  
      private Iterator<String> parse(Class service, URL u)
          throws ServiceConfigurationError
      {
          InputStream in = null;
          BufferedReader r = null;
          ArrayList<String> names = new ArrayList<String>();
          try {
              in = u.openStream();
              r = new BufferedReader(new InputStreamReader(in, "utf-8"));
              int lc = 1;
              while ((lc = parseLine(service, u, r, lc, names)) >= 0);
          } catch (IOException x) {
              fail(service, "Error reading configuration file", x);
          } finally {
              try {
                  if (r != null) r.close();
                  if (in != null) in.close();
              } catch (IOException y) {
                  fail(service, "Error closing configuration file", y);
              }
          }
          return names.iterator();
      }
  
      private class LazyIterator
          implements Iterator<S>
      {
  
          Class<S> service;
          ClassLoader loader;
          Enumeration<URL> configs = null;
          Iterator<String> pending = null;
          String nextName = null;
  
          private LazyIterator(Class<S> service, ClassLoader loader) {
              this.service = service;
              this.loader = loader;
          }
  
          public boolean hasNext() {
              if (nextName != null) {
                  return true;
              }
              if (configs == null) {
                  try {
                      String fullName = PREFIX + service.getName();
                      if (loader == null)
                          configs = ClassLoader.getSystemResources(fullName);
                      else
                          configs = loader.getResources(fullName);
                  } catch (IOException x) {
                      fail(service, "Error locating configuration files", x);
                  }
              }
              while ((pending == null) || !pending.hasNext()) {
                  if (!configs.hasMoreElements()) {
                      return false;
                  }
                  pending = parse(service, configs.nextElement());
              }
              nextName = pending.next();
              return true;
          }
  
          public S next() {
              if (!hasNext()) {
                  throw new NoSuchElementException();
              }
              String cn = nextName;
              nextName = null;
              try {
                  S p = service.cast(Class.forName(cn, true, loader)
                                     .newInstance());
                  providers.put(cn, p);
                  return p;
              } catch (ClassNotFoundException x) {
                  fail(service,
                       "Provider " + cn + " not found");
              } catch (Throwable x) {
                  fail(service,
                       "Provider " + cn + " could not be instantiated: " + x,
                       x);
              }
              throw new Error();          // This cannot happen
          }
  
          public void remove() {
              throw new UnsupportedOperationException();
          }
  
      }
  
      public Iterator<S> iterator() {
          return new Iterator<S>() {
  
              Iterator<Map.Entry<String,S>> knownProviders
                  = providers.entrySet().iterator();
  
              public boolean hasNext() {
                  if (knownProviders.hasNext())
                      return true;
                  return lookupIterator.hasNext();
              }
  
              public S next() {
                  if (knownProviders.hasNext())
                      return knownProviders.next().getValue();
                  return lookupIterator.next();
              }
  
              public void remove() {
                  throw new UnsupportedOperationException();
              }
  
          };
      }
  
      /**
       * Creates a new {@link org.gcontracts.common.spi.Lifecycle} for the given type and class
       * loader.
       */
      public static <S> LifecycleImplementationLoader<S> load(Class<S> service,
                                              ClassLoader loader)
      {
          return new LifecycleImplementationLoader<S>(service, loader);
      }
  }
  