blob: 56ef574c87eeb70005d02c9bd3bef20d3b724cdd [file] [log] [blame]
/*
* 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.Map;
import java.util.Objects;
import java.util.LinkedHashMap;
import java.util.Set;
import java.util.ServiceConfigurationError;
/**
* Helper class for loading named SPIs from classpath (e.g. Codec, PostingsFormat).
* @lucene.internal
*/
public final class NamedSPILoader<S extends NamedSPILoader.NamedSPI> implements Iterable<S> {
private volatile Map<String,S> services = Collections.emptyMap();
private final Class<S> clazz;
public NamedSPILoader(Class<S> clazz) {
this(clazz, null);
}
public NamedSPILoader(Class<S> 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 && !SPIClassIterator.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.
*
* <p><b>NOTE:</b> Only new service providers are added, existing ones are
* never removed or replaced.
*
* <p><em>This method is expensive and should only be called for discovery
* of new service providers on the given classpath/classloader!</em>
*/
public void reload(ClassLoader classloader) {
Objects.requireNonNull(classloader, "classloader");
final LinkedHashMap<String,S> services = new LinkedHashMap<>(this.services);
final SPIClassIterator<S> loader = SPIClassIterator.get(clazz, classloader);
while (loader.hasNext()) {
final Class<? extends S> c = loader.next();
try {
final S service = c.newInstance();
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);
}
} catch (Exception e) {
throw new ServiceConfigurationError("Cannot instantiate SPI class: " + c.getName(), e);
}
}
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<String> availableServices() {
return services.keySet();
}
@Override
public Iterator<S> iterator() {
return services.values().iterator();
}
/**
* Interface to support {@link NamedSPILoader#lookup(String)} by name.
* <p>
* Names must be all ascii alphanumeric, and less than 128 characters in length.
*/
public static interface NamedSPI {
String getName();
}
}