edu.isi.nlp.AnnotationWrapperModule Maven / Gradle / Ivy
package edu.isi.nlp;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.PrivateModule;
import com.google.inject.TypeLiteral;
import com.google.inject.multibindings.MapBinder;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.util.Types;
import edu.isi.nlp.parameters.Parameters;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import org.immutables.value.Value;
/**
* A module which takes bindings from another module and exposes them with a given annotation.
*
* Suppose you have the need to bind entity type ontologies in your application for multiple
* purposes and that these may differ. To be concrete, suppose you want to bind a {@code
* Set} annotated with an annotation {@code SourceP} and another such set annotated with
* a different annotation {@code TargetP}. You would rather not have to write two sets of otherwise
* identical modules for each type ontology, one binding to each annotation.
*
* Instead, you can use this class. It takes another module and installs it, but only exposes
* from it the bindings requested with the specified {@link #wrappingAnnotation()} applied.
*
*
To return to our example above, you could do:
*
*
{@code install(new AnnotationWrapperModule.Builder()
* .wrappedModuleParam("sourceEntityOntology") .wrappingAnnotation(SourceP.class)
* .wrapSetOf(EntityType.class) .extractFromParams(params));
*
*
* install(new AnnotationWrapperModule.Builder()
* .wrappedModuleParam("targetEntityOntology") .wrappingAnnotation(TargetP.class)
* .wrapSetOf(EntityType.class) .extractFromParams(params)); }
*
*
To make a wrapper module, use {@link Builder}. You will need to specify:
*
*
* - {@link Builder#wrappingAnnotation(Class)}: the annotation which should be applied to all
* the bindings exposed by this module.
*
- {@link Builder#wrappedModuleParam(String)}: a parameter giving the parameter from which to
* read at runtime the fully-qualified name(s) of the module(s) to wrap.
*
- {@link Builder#wrap(Class)}, {@link Builder#wrap(Class, Class)}, {@link
* Builder#wrap(TypeLiteral, Class)}, {@link Builder#wrapSetOf(Class)}, and {@link
* Builder#wrapMapOf(Class, Class)} to specify which things from the wrapped module should be
* marked with an annotation and exposed. The map and set methods will work properly with
* multibindings. There are additional methods for use in more complex cases (e.g. {@link
* Set}s of objects which are themselves generic. Note that any annotations present in the
* wrapped class will be lost, so care must be taken to avoid collisions in the exposed
* mappings, which will result in an {@link IllegalArgumentException}. Note that bindings have
* to be visible to the wrapper module to get wrapped, so bindings done in wrapped private
* modules (or private modules within wrapped modules) will not get wrapped.
*
*
* Finally, call {@link Builder#extractFrom(Parameters)} to create the module.
*
* @author Ryan Gabbard
*/
// we lose type safety doing this. We're okay with it.
@SuppressWarnings("unchecked")
@IsiNlpImmutable
@Value.Immutable
public abstract class AnnotationWrapperModule extends PrivateModule {
abstract String wrappedModuleParam();
abstract Class extends Annotation> wrappingAnnotation();
abstract ImmutableSet> simpleTypesToWrap();
abstract ImmutableSet> setsToWrap();
abstract ImmutableMap, TypeLiteral>> mapsToWrap();
abstract Parameters params();
@Value.Check
protected void check() {
checkArgument(!wrappedModuleParam().isEmpty());
// check for wrapped module bindings which will collide when we strip their own annotations
// and replace them with the wrapping annotation
final ImmutableSetMultimap.Builder, Key>> externalTypesToInternalTypesB =
ImmutableSetMultimap.builder();
for (final Key> key : simpleTypesToWrap()) {
externalTypesToInternalTypesB.put(key.getTypeLiteral(), key);
}
final ImmutableSetMultimap, Key>> externalTypesToInternalTypes =
externalTypesToInternalTypesB.build();
for (final Map.Entry, Collection>> e :
externalTypesToInternalTypes.asMap().entrySet()) {
if (e.getValue().size() > 1) {
throw new IllegalArgumentException(
"You are exposing multiple inner module bindings which"
+ " will have the same external binding key: "
+ e.getValue());
}
}
}
/** See Javadoc for {@link AnnotationWrapperModule}. */
// warning is suppressed because we have many "unused" arguments which exist just to carry
// type information
@SuppressWarnings("unused")
public static class Builder extends ImmutableAnnotationWrapperModule.Builder {
/**
* Request to expose an inner binding of type {@code T} as the external binding {@code
* \@WrappingAnnotation T}.
*/
public Builder wrap(Class clazz) {
return addSimpleTypesToWrap(Key.get(clazz));
}
/**
* Request to expose an inner binding of type {@code @InnerModuleAnnotation T} as the external
* binding {@code \@WrappingAnnotation T}.
*/
public Builder wrap(Class clazz, Class extends Annotation> innerModuleAnnotation) {
return addSimpleTypesToWrap(Key.get(clazz, innerModuleAnnotation));
}
/**
* Request to expose an inner binding of type {@code @InnerModuleAnnotation T} as the external
* binding {@code \@WrappingAnnotation T}.
*/
public Builder wrap(
TypeLiteral typeLiteral, Class extends Annotation> innerModuleAnnotation) {
return addSimpleTypesToWrap(Key.get(typeLiteral, innerModuleAnnotation));
}
/**
* Request to expose an inner binding of type {@code Set} as the external binding {@code
* \@WrappingAnnotation Set}.
*/
public Builder wrapSetOf(Class clazz) {
return addSetsToWrap(TypeLiteral.get(clazz));
}
/**
* Request to expose an inner binding of type {@code Map} as the external binding {@code
* \@WrappingAnnotation Map}.
*/
public Builder wrapMapOf(Class key, Class value) {
return putMapsToWrap(TypeLiteral.get(key), TypeLiteral.get(value));
}
public Module extractFrom(Parameters params) {
return params(params).build();
}
}
@Override
public void configure() {
predeclareMultibindings();
// the wrapped modules are installed, but not that this module itself is a PrivateModule,
// so the bindings from the wrapped module will not be propagated beyond this module
install(ModuleFromParameter.forMultiParameter(wrappedModuleParam()).extractFrom(params()));
wrapAndExposeSimpleTypes();
wrapAndExposeSets();
wrapAndExposeMaps();
}
private void wrapAndExposeMaps() {
for (final Map.Entry, TypeLiteral>> mapItemType : mapsToWrap().entrySet()) {
final Key