All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.inject.spi.BindingSourceRestriction Maven / Gradle / Ivy

There is a newer version: 7.0.0
Show newest version
package com.google.inject.spi;

import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.stream.Collectors.toList;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.inject.Binding;
import com.google.inject.Key;
import com.google.inject.RestrictedBindingSource;
import com.google.inject.RestrictedBindingSource.RestrictionLevel;
import com.google.inject.internal.Errors;
import com.google.inject.internal.GuiceInternal;
import java.util.regex.Pattern;
import java.lang.annotation.Annotation;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

/**
 * Contains abstractions for enforcing {@link RestrictedBindingSource}.
 *
 * 

Enforcement happens in two phases: * *

    *
  1. Data structures for enforcement are built during Binder configuration. {@link * PermitMapConstruction} encapsulates this process, and the {@link PermitMap} is the end * result. *
  2. Restrictions are enforced by checking each binding for violations with {@link #check}, * which uses the {@link PermitMap}(s) built during Binder configuration. *
* *

Note: None of this is thread-safe because it's only used while the Injector is being built, * which happens on a single thread. * * @author [email protected] (Vladimir Makaric) * @since 5.0 */ public final class BindingSourceRestriction { private BindingSourceRestriction() {} private static final Logger logger = Logger.getLogger(RestrictedBindingSource.class.getName()); /** Mapping between an element source and its permit annotations. */ interface PermitMap { ImmutableSet> getPermits(ElementSource elementSource); void clear(); } /** Returns a suggestion for how a restricted binding should be created in case it's missing. */ public static Optional getMissingImplementationSuggestion( GuiceInternal guiceInternal, Key key) { checkNotNull(guiceInternal); RestrictedBindingSource restriction = getRestriction(key); if (restriction == null) { return Optional.empty(); } return Optional.of( String.format( "%nHint: This key is restricted and cannot be bound directly. Restriction explanation:" + " %s", restriction.explanation())); } /** * Returns all the restriction violations found on the given Module Elements, as error messages. * *

Note: Intended to be used on Module Elements, not Injector Elements, ie. the result of * {@link Elements#getElements} not {@code Injector.getElements}. The Module Elements this check * cares about are: * *

    *
  • Module Bindings, which are always explicit and always have an {@link ElementSource} (with * a Module Stack), unlike Injector Bindings, which may be implicit and bereft of an * ElementSource. *
  • {@link PrivateElements}, which represent the recursive case of this check. They contain a * list of elements that this check is recursively called on. *
*/ public static ImmutableList check(GuiceInternal guiceInternal, List elements) { checkNotNull(guiceInternal); ImmutableList errorMessages = check(elements); // Clear all the permit maps after the checks are done. elements.forEach(BindingSourceRestriction::clear); return errorMessages; } private static ImmutableList check(List elements) { ImmutableList.Builder errorMessagesBuilder = ImmutableList.builder(); for (Element element : elements) { errorMessagesBuilder.addAll(check(element)); } return errorMessagesBuilder.build(); } private static ImmutableList check(Element element) { return element.acceptVisitor( new DefaultElementVisitor>() { // Base case. @Override protected ImmutableList visitOther(Element element) { return ImmutableList.of(); } // Base case. @Override public ImmutableList visit(Binding binding) { Optional errorMessage = check(binding); if (errorMessage.isPresent()) { return ImmutableList.of(errorMessage.get()); } return ImmutableList.of(); } // Recursive case. @Override public ImmutableList visit(PrivateElements privateElements) { return check(privateElements.getElements()); } }); } private static Optional check(Binding binding) { Key key = binding.getKey(); // Module Bindings are all explicit and have an ElementSource. ElementSource elementSource = (ElementSource) binding.getSource(); RestrictedBindingSource restriction = getRestriction(key); if (restriction == null) { return Optional.empty(); } ImmutableSet> permits = getAllPermits(elementSource); ImmutableSet> acceptablePermits = ImmutableSet.copyOf(restriction.permits()); boolean bindingPermitted = permits.stream().anyMatch(acceptablePermits::contains); if (bindingPermitted || isExempt(elementSource, restriction.exemptModules())) { return Optional.empty(); } String violationMessage = getViolationMessage( key, restriction.explanation(), acceptablePermits, key.getAnnotationType() != null); if (restriction.restrictionLevel() == RestrictionLevel.WARNING) { Formatter sourceFormatter = new Formatter(); Errors.formatSource(sourceFormatter, elementSource); logger.log(Level.WARNING, violationMessage + "\n" + sourceFormatter); return Optional.empty(); } return Optional.of(new Message(elementSource, violationMessage)); } private static String getViolationMessage( Key key, String explanation, ImmutableSet> acceptablePermits, boolean annotationRestricted) { return String.format( "Unable to bind key: %s. One of the modules that created this binding has to be annotated" + " with one of %s, because the key's %s is annotated with @RestrictedBindingSource." + " %s", key, acceptablePermits.stream().map(a -> "@" + a.getName()).collect(toList()), annotationRestricted ? "annotation" : "type", explanation); } /** Get all permits on the element source chain. */ private static ImmutableSet> getAllPermits( ElementSource elementSource) { ImmutableSet.Builder> permitsBuilder = ImmutableSet.builder(); permitsBuilder.addAll(elementSource.moduleSource.getPermitMap().getPermits(elementSource)); if (elementSource.scanner != null) { getPermits(elementSource.scanner.getClass()).forEach(permitsBuilder::add); } if (elementSource.getOriginalElementSource() != null && elementSource.trustedOriginalElementSource) { permitsBuilder.addAll(getAllPermits(elementSource.getOriginalElementSource())); } return permitsBuilder.build(); } private static boolean isExempt(ElementSource elementSource, String exemptModulesRegex) { if (exemptModulesRegex.isEmpty()) { return false; } Pattern exemptModulePattern = Pattern.compile(exemptModulesRegex); // TODO(b/156759807): Switch to Streams.stream (instead of inlining it). return StreamSupport.stream(getAllModules(elementSource).spliterator(), false) .anyMatch(moduleName -> exemptModulePattern.matcher(moduleName).matches()); } private static Iterable getAllModules(ElementSource elementSource) { List modules = elementSource.getModuleClassNames(); if (elementSource.getOriginalElementSource() == null || !elementSource.trustedOriginalElementSource) { return modules; } return Iterables.concat(modules, getAllModules(elementSource.getOriginalElementSource())); } private static void clear(Element element) { element.acceptVisitor( new DefaultElementVisitor() { // Base case. @Override protected Void visitOther(Element element) { Object source = element.getSource(); // Some Module Elements, like Message, don't always have an ElementSource. if (source instanceof ElementSource) { clear((ElementSource) source); } return null; } // Recursive case. @Override public Void visit(PrivateElements privateElements) { privateElements.getElements().forEach(BindingSourceRestriction::clear); return null; } }); } private static void clear(ElementSource elementSource) { while (elementSource != null) { elementSource.moduleSource.getPermitMap().clear(); elementSource = elementSource.getOriginalElementSource(); } } /* * Returns the restriction on the given key (null if there is none). * * If the key is annotated then only the annotation restriction matters, the type restriction is * ignored (an annotated type is essentially a new type). **/ private static RestrictedBindingSource getRestriction(Key key) { return key.getAnnotationType() == null ? key.getTypeLiteral().getRawType().getAnnotation(RestrictedBindingSource.class) : key.getAnnotationType().getAnnotation(RestrictedBindingSource.class); } /** * Builds the map from each module to all the permit annotations on its module stack. * *

Bindings refer to the module that created them via a {@link ModuleSource}. The map built * here maps a module's {@link ModuleSource} to all the {@link RestrictedBindingSource.Permit} * annotations found on the path from the root of the module hierarchy to it. This path contains * all the modules that transitively install the module (including the module itself). This path * is also known as the module stack. * *

The map is built by piggybacking on the depth-first traversal of the module hierarchy during * Binder configuration. */ static final class PermitMapConstruction { private static final class PermitMapImpl implements PermitMap { Map>> modulePermits; @Override public ImmutableSet> getPermits(ElementSource elementSource) { return modulePermits.get(elementSource.moduleSource); } @Override public void clear() { modulePermits = null; } } final Map>> modulePermits = new HashMap<>(); // Maintains the permits on the current module installation path. ImmutableSet> currentModulePermits = ImmutableSet.of(); // Stack tracking the currentModulePermits during module traversal. final Deque>> modulePermitsStack = new ArrayDeque<>(); final PermitMapImpl permitMap = new PermitMapImpl(); /** * Returns a possibly unfinished map. The map should only be used after the construction is * finished. */ PermitMap getPermitMap() { return permitMap; } /** * Sets the permits on the current module installation path to the permits on the given module * source so that subsequently installed modules may inherit them. Used only for method * scanning, so that modules installed by scanners inherit permits from the method's module. */ void restoreCurrentModulePermits(ModuleSource moduleSource) { currentModulePermits = modulePermits.get(moduleSource); } /** Called by the Binder prior to entering a module's configure method. */ void pushModule(Class module, ModuleSource moduleSource) { List> newModulePermits = getPermits(module) .filter(permit -> !currentModulePermits.contains(permit)) .collect(toList()); // Save the parent module's permits so that they can be restored when the Binder exits this // new (child) module's configure method. modulePermitsStack.push(currentModulePermits); if (!newModulePermits.isEmpty()) { currentModulePermits = ImmutableSet.>builder() .addAll(currentModulePermits) .addAll(newModulePermits) .build(); } modulePermits.put(moduleSource, currentModulePermits); } /** Called by the Binder when it exits a module's configure method. */ void popModule() { // Restore the parent module's permits. currentModulePermits = modulePermitsStack.pop(); } /** Finishes the {@link PermitMap}. Called by the Binder when all modules are installed. */ void finish() { permitMap.modulePermits = modulePermits; } @VisibleForTesting static boolean isElementSourceCleared(ElementSource elementSource) { PermitMapImpl permitMap = (PermitMapImpl) elementSource.moduleSource.getPermitMap(); return permitMap.modulePermits == null; } } private static Stream> getPermits(Class clazz) { Stream annotations = Arrays.stream(clazz.getAnnotations()); // Pick up annotations on anonymous classes (e.g. new @Bar Foo() { ... }): if (clazz.getAnnotatedSuperclass() != null) { annotations = Stream.concat( annotations, Arrays.stream(clazz.getAnnotatedSuperclass().getAnnotations())); } return annotations .map(Annotation::annotationType) .filter(a -> a.isAnnotationPresent(RestrictedBindingSource.Permit.class)); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy