Index: lucene/analysis/common/src/resources/META-INF/services/org.apache.lucene.analysis.util.TokenizerFactory
===================================================================
--- lucene/analysis/common/src/resources/META-INF/services/org.apache.lucene.analysis.util.TokenizerFactory	(revision 0)
+++ lucene/analysis/common/src/resources/META-INF/services/org.apache.lucene.analysis.util.TokenizerFactory	(working copy)
@@ -0,0 +1,2 @@
+# test
+org.apache.lucene.analysis.util.FooTokenizerFactory
Index: lucene/analysis/common/src/test/org/apache/lucene/analysis/util/TestAnalysisSPILoader.java
===================================================================
--- lucene/analysis/common/src/test/org/apache/lucene/analysis/util/TestAnalysisSPILoader.java	(revision 0)
+++ lucene/analysis/common/src/test/org/apache/lucene/analysis/util/TestAnalysisSPILoader.java	(working copy)
@@ -0,0 +1,46 @@
+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.io.StringReader;
+import java.util.HashMap;
+
+import org.apache.lucene.analysis.Tokenizer;
+import org.apache.lucene.analysis.core.KeywordTokenizer;
+import org.apache.lucene.util.LuceneTestCase;
+
+/**
+ * just a proof of concept
+ */
+public class TestAnalysisSPILoader extends LuceneTestCase {
+  public void test() {
+    TokenizerFactory tf = TokenizerFactory.forName("foo"); // -> FooTokenizerFactory
+    tf.init(new HashMap<String,String>());
+    Tokenizer t = tf.create(new StringReader("test"));
+    assertTrue(t instanceof KeywordTokenizer);
+  }
+  
+  public void testBogus() {
+    try {
+      TokenizerFactory tf = TokenizerFactory.forName("bogus");
+      fail();
+    } catch (IllegalArgumentException expected) {
+      // expected
+    }
+  }
+}

Property changes on: lucene/analysis/common/src/test/org/apache/lucene/analysis/util/TestAnalysisSPILoader.java
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
Index: lucene/analysis/common/src/java/org/apache/lucene/analysis/util/TokenizerFactory.java
===================================================================
--- lucene/analysis/common/src/java/org/apache/lucene/analysis/util/TokenizerFactory.java	(revision 1364848)
+++ lucene/analysis/common/src/java/org/apache/lucene/analysis/util/TokenizerFactory.java	(working copy)
@@ -20,6 +20,7 @@
 import org.apache.lucene.analysis.Tokenizer;
 
 import java.io.Reader;
+import java.util.Set;
 
 /**
  * Abstract parent class for analysis factories that create {@link Tokenizer}
@@ -27,6 +28,19 @@
  */
 public abstract class TokenizerFactory extends AbstractAnalysisFactory {
 
+  private static final AnalysisSPILoader<TokenizerFactory> loader =
+      new AnalysisSPILoader<TokenizerFactory>(TokenizerFactory.class);
+  
+  /** looks up a tokenizer by name */
+  public static TokenizerFactory forName(String name) {
+    return loader.newInstance(name);
+  }
+  
+  /** returns a list of all available tokenizer names */
+  public static Set<String> availableTokenizers() {
+    return loader.availableServices();
+  }
+  
   /** Creates a TokenStream of the specified input */
   public abstract Tokenizer create(Reader input);
 }
Index: lucene/analysis/common/src/java/org/apache/lucene/analysis/util/FooTokenizerFactory.java
===================================================================
--- lucene/analysis/common/src/java/org/apache/lucene/analysis/util/FooTokenizerFactory.java	(revision 0)
+++ lucene/analysis/common/src/java/org/apache/lucene/analysis/util/FooTokenizerFactory.java	(working copy)
@@ -0,0 +1,35 @@
+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.io.Reader;
+
+import org.apache.lucene.analysis.Tokenizer;
+import org.apache.lucene.analysis.core.KeywordTokenizer;
+
+/**
+ * stupid test
+ */
+public final class FooTokenizerFactory extends TokenizerFactory {
+
+  @Override
+  public Tokenizer create(Reader input) {
+    return new KeywordTokenizer(input);
+  }
+  
+}

Property changes on: lucene/analysis/common/src/java/org/apache/lucene/analysis/util/FooTokenizerFactory.java
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
Index: lucene/analysis/common/src/java/org/apache/lucene/analysis/util/CharFilterFactory.java
===================================================================
--- lucene/analysis/common/src/java/org/apache/lucene/analysis/util/CharFilterFactory.java	(revision 1364848)
+++ lucene/analysis/common/src/java/org/apache/lucene/analysis/util/CharFilterFactory.java	(working copy)
@@ -18,6 +18,7 @@
  */
 
 import java.io.Reader;
+import java.util.Set;
 
 import org.apache.lucene.analysis.CharFilter;
 
@@ -27,5 +28,18 @@
  */
 public abstract class CharFilterFactory extends AbstractAnalysisFactory {
 
+  private static final AnalysisSPILoader<CharFilterFactory> loader =
+      new AnalysisSPILoader<CharFilterFactory>(CharFilterFactory.class);
+  
+  /** looks up a charfilter by name */
+  public static CharFilterFactory forName(String name) {
+    return loader.newInstance(name);
+  }
+  
+  /** returns a list of all available charfilter names */
+  public static Set<String> availableCharFilters() {
+    return loader.availableServices();
+  }
+
   public abstract CharFilter create(Reader input);
 }
Index: lucene/analysis/common/src/java/org/apache/lucene/analysis/util/TokenFilterFactory.java
===================================================================
--- lucene/analysis/common/src/java/org/apache/lucene/analysis/util/TokenFilterFactory.java	(revision 1364848)
+++ lucene/analysis/common/src/java/org/apache/lucene/analysis/util/TokenFilterFactory.java	(working copy)
@@ -17,6 +17,8 @@
  * limitations under the License.
  */
 
+import java.util.Set;
+
 import org.apache.lucene.analysis.TokenStream;
 
 /**
@@ -25,6 +27,19 @@
  */
 public abstract class TokenFilterFactory extends AbstractAnalysisFactory {
 
+  private static final AnalysisSPILoader<TokenFilterFactory> loader =
+      new AnalysisSPILoader<TokenFilterFactory>(TokenFilterFactory.class);
+  
+  /** looks up a tokenfilter by name */
+  public static TokenFilterFactory forName(String name) {
+    return loader.newInstance(name);
+  }
+  
+  /** returns a list of all available charfilter names */
+  public static Set<String> availableTokenFilters() {
+    return loader.availableServices();
+  }
+  
   /** Transform the specified input TokenStream */
   public abstract TokenStream create(TokenStream input);
 }
Index: lucene/analysis/common/src/java/org/apache/lucene/analysis/util/AnalysisSPILoader.java
===================================================================
--- lucene/analysis/common/src/java/org/apache/lucene/analysis/util/AnalysisSPILoader.java	(revision 0)
+++ lucene/analysis/common/src/java/org/apache/lucene/analysis/util/AnalysisSPILoader.java	(working copy)
@@ -0,0 +1,115 @@
+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 java.util.ServiceLoader;
+
+/**
+ * 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<S>> services;
+
+  /** This field is a hack for LuceneTestCase to get access
+   * to the modifiable map (to work around bugs in IBM J9) */
+  @SuppressWarnings("unused")
+  @Deprecated
+  // Hackidy-Häck-Hack for bugs in IBM J9 ServiceLoader
+  private final Map<String,Class<S>> modifiableServices;
+  
+  private final Class<S> clazz;
+
+  public AnalysisSPILoader(Class<S> clazz) {
+    this.clazz = clazz;
+    final ServiceLoader<S> loader = ServiceLoader.load(clazz);
+    final LinkedHashMap<String,Class<S>> services = new LinkedHashMap<String,Class<S>>();
+    final String suffix = clazz.getSimpleName();
+    for (final S service : loader) {
+      final String clazzName = service.getClass().getSimpleName();
+      final int suffixIndex = clazzName.lastIndexOf(suffix);
+      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, (Class<S>) service.getClass());
+      }
+    }
+    this.modifiableServices = services; // hack, remove when IBM J9 is fixed!
+    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');
+  }
+  
+  public S newInstance(String name) {
+    final Class<S> service = services.get(name.toLowerCase(Locale.ROOT));
+    if (service != null) {
+      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);
+      }
+    } 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();
+  }  
+}

Property changes on: lucene/analysis/common/src/java/org/apache/lucene/analysis/util/AnalysisSPILoader.java
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
