package edu.stanford.nlp.pipeline;
import java.util.*;
import java.util.function.Supplier;
import edu.stanford.nlp.util.Generics;
import edu.stanford.nlp.util.Lazy;
import edu.stanford.nlp.util.PropertiesUtils;
import edu.stanford.nlp.util.logging.Redwood;
/**
* An object for keeping track of Annotators. Typical use is to allow multiple
* pipelines to share any Annotators in common.
*
* For example, if multiple pipelines exist, and they both need a
* ParserAnnotator, it would be bad to load two such Annotators into memory.
* Instead, an AnnotatorPool will only create one Annotator and allow both
* pipelines to share it.
*
* @author bethard
*/
public class AnnotatorPool {
/** A logger for this class */
private static Redwood.RedwoodChannels log = Redwood.channels(AnnotatorPool.class);
/**
* A cached annotator, including the signature it should cache on.
*/
private static class CachedAnnotator {
/** The signature of the annotator. */
public final String signature;
/** The cached annotator. */
public final Lazy<Annotator> annotator;
/**
* The straightforward constructor.
*/
private CachedAnnotator(String signature, Lazy<Annotator> annotator) {
this.signature = signature;
this.annotator = annotator;
}
/** {@inheritDoc} */
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CachedAnnotator that = (CachedAnnotator) o;
return signature != null ? signature.equals(that.signature) : that.signature == null && (annotator != null ? annotator.equals(that.annotator) : that.annotator == null);
}
/** {@inheritDoc} */
@Override
public int hashCode() {
int result = signature != null ? signature.hashCode() : 0;
result = 31 * result + (annotator != null ? annotator.hashCode() : 0);
return result;
}
}
/** The set of factories we know about defining how we should create new annotators of each name */
private final Map<String, CachedAnnotator> factories;
/**
* Create an empty AnnotatorPool.
*/
public AnnotatorPool() {
this.factories = Generics.newHashMap();
}
/**
* Register an Annotator that can be created by the pool.
*
* Note that factories are used here so that many possible annotators can
* be defined within the AnnotatorPool, but an Annotator is only created
* when one is actually needed.
*
* @param name The name to be associated with the Annotator.
* @param props The properties we are using to create the annotator
* @param annotator A factory that creates an instance of the desired Annotator.
* This should be an instance of {@link Lazy#cache(Supplier)}, if we want
* the annotator pool to behave as a cache (i.e., evict old annotators
* when the GC requires it).
*
* @return true if a new annotator was created; false if we reuse an existing one
*/
public boolean register(String name, Properties props, Lazy<Annotator> annotator) {
boolean newAnnotator = false;
synchronized (this.factories) {
CachedAnnotator oldAnnotator = this.factories.get(name);
String newSig = PropertiesUtils.getSignature(name, props);
if (oldAnnotator == null || !Objects.equals(oldAnnotator.signature, newSig)) {
// the new annotator uses different properties so we need to update!
if (oldAnnotator != null) {
log.info("Replacing old annotator \"" + name + "\" with signature ["
+ oldAnnotator.signature + "] with new annotator with signature [" + newSig + "]");
}
// Add the new annotator
this.factories.put(name, new CachedAnnotator(newSig, annotator));
// Unmount the old annotator
Optional.ofNullable(oldAnnotator).flatMap(ann -> Optional.ofNullable(ann.annotator.getIfDefined())).ifPresent(Annotator::unmount);
// Register that we added an annotator
newAnnotator = true;
}
// nothing to do if an annotator with same name and signature already exists
}
return newAnnotator;
}
/**
* Clear this pool, and unmount all the annotators mounted on it.
*/
public synchronized void clear() {
synchronized (this.factories) {
for (Map.Entry<String, CachedAnnotator> entry : new HashSet<>(this.factories.entrySet())) {
// Unmount the annotator
Optional.ofNullable(entry.getValue()).flatMap(ann -> Optional.ofNullable(ann.annotator.getIfDefined())).ifPresent(Annotator::unmount);
// Remove the annotator
this.factories.remove(entry.getKey());
}
}
}
/**
* Retrieve an Annotator from the pool. If the named Annotator has not yet
* been requested, it will be created. Otherwise, the existing instance of
* the Annotator will be returned.
*
* @param name The annotator to retrieve from the pool
* @return The annotator
* @throws IllegalArgumentException If the annotator cannot be created
*/
public synchronized Annotator get(String name) {
CachedAnnotator factory = this.factories.get(name);
if (factory != null) {
return factory.annotator.get();
} else {
throw new IllegalArgumentException("No annotator named " + name);
}
}
/**
* A global singleton annotator pool, so that we can cache globally on a JVM instance.
*/
public static final AnnotatorPool SINGLETON = new AnnotatorPool();
}