package org.apache.lucene.analysis.util;

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.Set;
import org.apache.lucene.util.NamedSPILoader.NamedSPI; // javadocs
import org.apache.lucene.util.SPIClassIterator;

/**
 * Helper class for loading named SPIs from classpath (e.g. Tokenizers, TokenStreams).
 * @lucene.internal
 */
public final class AnalysisSPILoader<S extends AbstractAnalysisFactory> {

  private final Map<String,Class<? extends S>> services;
  private final Class<S> clazz;
  
  public AnalysisSPILoader(Class<S> clazz) {
    this(clazz, new String[] { clazz.getSimpleName() });
  }

  public AnalysisSPILoader(Class<S> clazz, ClassLoader loader) {
    this(clazz, new String[] { clazz.getSimpleName() }, loader);
  }

  public AnalysisSPILoader(Class<S> clazz, String[] suffixes) {
    this(clazz, suffixes, Thread.currentThread().getContextClassLoader());
  }
  
  public AnalysisSPILoader(Class<S> clazz, String[] suffixes, ClassLoader classloader) {
    this.clazz = clazz;
    final SPIClassIterator<S> loader = SPIClassIterator.get(clazz, classloader);
    final LinkedHashMap<String,Class<? extends S>> services = new LinkedHashMap<String,Class<? extends S>>();
    while (loader.hasNext()) {
      final Class<? extends S> service = loader.next();
      final String clazzName = service.getSimpleName();
      int suffixIndex = -1;
      for (String suffix : suffixes) {
        suffixIndex = clazzName.lastIndexOf(suffix);
        if (suffixIndex != -1) {
          break;
        }
      }
      final String name = clazzName.substring(0, suffixIndex).toLowerCase(Locale.ROOT);
      // only add the first one for each name, later services will be ignored
      // this allows to place services before others in classpath to make 
      // them used instead of others
      if (!services.containsKey(name)) {
        assert checkServiceName(name);
        services.put(name, service);
      }
    }
    this.services = Collections.unmodifiableMap(services);
  }
  
  /**
   * Validates that a service name meets the requirements of {@link NamedSPI}
   */
  public static boolean checkServiceName(String name) {
    // based on harmony charset.java
    if (name.length() >= 128) {
      throw new IllegalArgumentException("Illegal service name: '" + name + "' is too long (must be < 128 chars).");
    }
    for (int i = 0; i < name.length(); i++) {
      char c = name.charAt(i);
      if (!isLetter(c) && !isDigit(c)) {
        throw new IllegalArgumentException("Illegal service name: '" + name + "' must be simple ascii alphanumeric.");
      }
    }
    return true;
  }
  
  /*
   * Checks whether a character is a letter (ascii) which are defined in the spec.
   */
  private static boolean isLetter(char c) {
      return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
  }

  /*
   * Checks whether a character is a digit (ascii) which are defined in the spec.
   */
  private static boolean isDigit(char c) {
      return ('0' <= c && c <= '9');
  }
  
  // TODO: do we even need this method?
  public S newInstance(String name) {
    final Class<? extends S> service = lookupClass(name);
    try {
      return service.newInstance();
    } catch (Exception e) {
      throw new IllegalArgumentException("SPI class of type "+clazz.getName()+" with name '"+name+"' cannot be instantiated. " +
            "This is likely due to a misconfiguration of the java class '" + service.getName() + "': ", e);
    }
  }
  
  public Class<? extends S> lookupClass(String name) {
    final Class<? extends S> service = services.get(name.toLowerCase(Locale.ROOT));
    if (service != null) {
      return service;
    } else {
      throw new IllegalArgumentException("A SPI class of type "+clazz.getName()+" with name '"+name+"' does not exist. "+
          "You need to add the corresponding JAR file supporting this SPI to your classpath."+
          "The current classpath supports the following names: "+availableServices());
    }
  }

  public Set<String> availableServices() {
    return services.keySet();
  }  
}

@@ -838,16 +838,41 @@
 e.%0A */%0A%0A
+import java.util.Arrays;%0A
 import j
@@ -1005,67 +1005,45 @@
 ort 
-org.apache.lucene.util.NamedSPILoader.NamedSPI; // javadocs
+java.util.ServiceConfigurationError;%0A
 %0Aimp
@@ -2215,28 +2215,26 @@
     
-int suffixIndex = -1
+String name = null
 ;%0A  
@@ -2282,51 +2282,38 @@
     
-suffixIndex = clazzName.lastIndexOf
+if (clazzName.endsWith
 (suffix)
 ;%0A  
@@ -2308,17 +2308,19 @@
 (suffix)
-;
+) %7B
 %0A       
@@ -2324,155 +2324,331 @@
     
-if (suffixIndex != -1) %7B%0A          break;%0A        %7D%0A      %7D%0A      final String name = clazzName.substring(0, suffixIndex).toLowerCase(Locale.ROOT);
+  name = clazzName.substring(0, clazzName.length() - suffix.length()).toLowerCase(Locale.ROOT);%0A          break;%0A        %7D%0A      %7D%0A      if (name == null) %7B%0A        throw new ServiceConfigurationError(%22The class name %22 + service.getName() +%0A          %22 has wrong suffix, allowed are: %22 + Arrays.toString(suffixes));%0A      %7D
 %0A   
@@ -2879,47 +2879,8 @@
 ) %7B%0A
-        assert checkServiceName(name);%0A
     
@@ -2996,1007 +2996,8 @@
 %0A  %0A
-  /**%0A   * Validates that a service name meets the requirements of %7B@link NamedSPI%7D%0A   */%0A  public static boolean checkServiceName(String name) %7B%0A    // based on harmony charset.java%0A    if (name.length() %3E= 128) %7B%0A      throw new IllegalArgumentException(%22Illegal service name: '%22 + name + %22' is too long (must be %3C 128 chars).%22);%0A    %7D%0A    for (int i = 0; i %3C name.length(); i++) %7B%0A      char c = name.charAt(i);%0A      if (!isLetter(c) && !isDigit(c)) %7B%0A        throw new IllegalArgumentException(%22Illegal service name: '%22 + name + %22' must be simple ascii alphanumeric.%22);%0A      %7D%0A    %7D%0A    return true;%0A  %7D%0A  %0A  /*%0A   * Checks whether a character is a letter (ascii) which are defined in the spec.%0A   */%0A  private static boolean isLetter(char c) %7B%0A      return ('a' %3C= c && c %3C= 'z') %7C%7C ('A' %3C= c && c %3C= 'Z');%0A  %7D%0A%0A  /*%0A   * Checks whether a character is a digit (ascii) which are defined in the spec.%0A   */%0A  private static boolean isDigit(char c) %7B%0A      return ('0' %3C= c && c %3C= '9');%0A  %7D%0A  %0A
   //

Improve the Maven-like pattern detection for the named loader. TODO: We should create an ant task that creates the META-INF/services list from filesets