
org.apache.lucene.util.NamedSPILoader Maven / Gradle / Ivy
Show all versions of org.apache.servicemix.bundles.lucene
/*
* 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.
*/
package org.apache.lucene.util;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.Set;
/**
* Helper class for loading named SPIs from classpath (e.g. Codec, PostingsFormat).
*
* @lucene.internal
*/
public final class NamedSPILoader implements Iterable {
private volatile Map services = Collections.emptyMap();
private final Class clazz;
public NamedSPILoader(Class clazz) {
this(clazz, null);
}
public NamedSPILoader(Class clazz, ClassLoader classloader) {
this.clazz = clazz;
// if clazz' classloader is not a parent of the given one, we scan clazz's classloader, too:
final ClassLoader clazzClassloader = clazz.getClassLoader();
if (classloader == null) {
classloader = clazzClassloader;
}
if (clazzClassloader != null
&& !ClassLoaderUtils.isParentClassLoader(clazzClassloader, classloader)) {
reload(clazzClassloader);
}
reload(classloader);
}
/**
* Reloads the internal SPI list from the given {@link ClassLoader}. Changes to the service list
* are visible after the method ends, all iterators ({@link #iterator()},...) stay consistent.
*
* NOTE: Only new service providers are added, existing ones are never removed or
* replaced.
*
*
This method is expensive and should only be called for discovery of new service
* providers on the given classpath/classloader!
*/
public void reload(ClassLoader classloader) {
Objects.requireNonNull(classloader, "classloader");
final LinkedHashMap services = new LinkedHashMap<>(this.services);
for (final S service : ServiceLoader.load(clazz, classloader)) {
final String name = service.getName();
// 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)) {
checkServiceName(name);
services.put(name, service);
}
}
this.services = Collections.unmodifiableMap(services);
}
/** Validates that a service name meets the requirements of {@link NamedSPI} */
public static void 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, len = name.length(); i < len; i++) {
char c = name.charAt(i);
if (!isLetterOrDigit(c)) {
throw new IllegalArgumentException(
"Illegal service name: '" + name + "' must be simple ascii alphanumeric.");
}
}
}
/** Checks whether a character is a letter or digit (ascii) which are defined in the spec. */
private static boolean isLetterOrDigit(char c) {
return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9');
}
public S lookup(String name) {
final S service = services.get(name);
if (service != null) return service;
throw new IllegalArgumentException(
"An 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 availableServices() {
return services.keySet();
}
@Override
public Iterator iterator() {
return services.values().iterator();
}
/**
* Interface to support {@link NamedSPILoader#lookup(String)} by name.
*
* Names must be all ascii alphanumeric, and less than 128 characters in length.
*/
public interface NamedSPI {
String getName();
}
}