| // Copyright 2018 The Bazel Authors. All rights reserved. | |
| // | |
| // Licensed 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 com.google.devtools.build.lib.exec; | |
| import com.google.auto.value.AutoValue; | |
| import com.google.common.annotations.VisibleForTesting; | |
| import com.google.common.base.Joiner; | |
| import com.google.common.base.Preconditions; | |
| import com.google.common.base.Strings; | |
| import com.google.common.collect.HashBasedTable; | |
| import com.google.common.collect.ImmutableClassToInstanceMap; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableListMultimap; | |
| import com.google.common.collect.ImmutableMap; | |
| import com.google.common.collect.ImmutableMultimap; | |
| import com.google.common.collect.ImmutableSet; | |
| import com.google.common.collect.ImmutableSortedMap; | |
| import com.google.common.collect.LinkedHashMultimap; | |
| import com.google.common.collect.Table; | |
| import com.google.devtools.build.lib.actions.ActionContext; | |
| import com.google.devtools.build.lib.actions.ActionContextMarker; | |
| import com.google.devtools.build.lib.actions.DynamicStrategyRegistry; | |
| import com.google.devtools.build.lib.actions.ExecutorInitException; | |
| import com.google.devtools.build.lib.actions.SandboxedSpawnStrategy; | |
| import com.google.devtools.build.lib.actions.Spawn; | |
| import com.google.devtools.build.lib.actions.SpawnStrategy; | |
| import com.google.devtools.build.lib.events.Event; | |
| import com.google.devtools.build.lib.events.EventHandler; | |
| import com.google.devtools.build.lib.events.Reporter; | |
| import com.google.devtools.build.lib.util.ExitCode; | |
| import com.google.devtools.build.lib.util.RegexFilter; | |
| import java.util.ArrayList; | |
| import java.util.Collection; | |
| import java.util.Collections; | |
| import java.util.HashMap; | |
| import java.util.HashSet; | |
| import java.util.LinkedHashSet; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.Map.Entry; | |
| import java.util.Set; | |
| import java.util.TreeMap; | |
| import java.util.stream.Collectors; | |
| import javax.annotation.Nullable; | |
| /** | |
| * Container for looking up the {@link ActionContext} to use for a given action. | |
| * | |
| * <p>Holds {@link ActionContext} mappings populated by modules. These include mappings from | |
| * mnemonics and from description patterns. | |
| * | |
| * <p>At startup time, the application provides {@link Builder} to each module to register its | |
| * contexts and mappings. At runtime, the {@link BlazeExecutor} uses the constructed object to find | |
| * the context for each action. | |
| */ | |
| public final class SpawnActionContextMaps | |
| implements DynamicStrategyRegistry, | |
| RemoteLocalFallbackRegistry, | |
| ActionContext.ActionContextRegistry { | |
| /** A stored entry for a {@link RegexFilter} to {@link SpawnStrategy} mapping. */ | |
| @AutoValue | |
| public abstract static class RegexFilterSpawnStrategy { | |
| public abstract RegexFilter regexFilter(); | |
| public abstract ImmutableList<SpawnStrategy> strategies(); | |
| } | |
| private final ImmutableSortedMap<String, List<SpawnStrategy>> mnemonicToSpawnStrategiesMap; | |
| private final ImmutableClassToInstanceMap<ActionContext> strategies; | |
| private final ImmutableList<RegexFilterSpawnStrategy> spawnStrategyRegexList; | |
| private final ImmutableMultimap<String, SandboxedSpawnStrategy> mnemonicToRemoteDynamicStrategies; | |
| private final ImmutableMultimap<String, SandboxedSpawnStrategy> mnemonicToLocalDynamicStrategies; | |
| private final ImmutableMap<Class<? extends ActionContext>, ActionContext> contextMap; | |
| @Nullable private final AbstractSpawnStrategy remoteLocalFallbackStrategy; | |
| private SpawnActionContextMaps( | |
| ImmutableSortedMap<String, List<SpawnStrategy>> mnemonicToSpawnStrategiesMap, | |
| ImmutableClassToInstanceMap<ActionContext> strategies, | |
| ImmutableList<RegexFilterSpawnStrategy> spawnStrategyRegexList, | |
| ImmutableMultimap<String, SandboxedSpawnStrategy> mnemonicToRemoteDynamicStrategies, | |
| ImmutableMultimap<String, SandboxedSpawnStrategy> mnemonicToLocalDynamicStrategies, | |
| AbstractSpawnStrategy remoteLocalFallbackStrategy) { | |
| this.mnemonicToSpawnStrategiesMap = mnemonicToSpawnStrategiesMap; | |
| this.strategies = strategies; | |
| this.spawnStrategyRegexList = spawnStrategyRegexList; | |
| this.mnemonicToRemoteDynamicStrategies = mnemonicToRemoteDynamicStrategies; | |
| this.mnemonicToLocalDynamicStrategies = mnemonicToLocalDynamicStrategies; | |
| this.remoteLocalFallbackStrategy = remoteLocalFallbackStrategy; | |
| contextMap = createContextMap(); | |
| } | |
| /** | |
| * Returns a list of appropriate {@link ActionContext}s to execute the given {@link Spawn} with. | |
| * | |
| * <p>If the reason for selecting the context is worth mentioning to the user, logs a message | |
| * using the given {@link Reporter}. | |
| */ | |
| List<SpawnStrategy> getSpawnActionContexts(Spawn spawn, EventHandler reporter) { | |
| Preconditions.checkNotNull(spawn); | |
| if (!spawnStrategyRegexList.isEmpty() && spawn.getResourceOwner() != null | |
| // Don't override test strategies by --strategy_regexp for backwards compatibility. | |
| && !"TestRunner".equals(spawn.getMnemonic())) { | |
| String description = spawn.getResourceOwner().getProgressMessage(); | |
| if (description != null) { | |
| for (RegexFilterSpawnStrategy entry : spawnStrategyRegexList) { | |
| if (entry.regexFilter().isIncluded(description) && entry.strategies() != null) { | |
| reporter.handle( | |
| Event.progress(description + " with context " + entry.strategies().toString())); | |
| return entry.strategies(); | |
| } | |
| } | |
| } | |
| } | |
| List<SpawnStrategy> strategies = mnemonicToSpawnStrategiesMap.get(spawn.getMnemonic()); | |
| if (strategies != null) { | |
| return strategies; | |
| } | |
| return Preconditions.checkNotNull(mnemonicToSpawnStrategiesMap.get("")); | |
| } | |
| @Override | |
| public List<SandboxedSpawnStrategy> getDynamicSpawnActionContexts( | |
| Spawn spawn, DynamicMode dynamicMode) { | |
| ImmutableMultimap<String, SandboxedSpawnStrategy> mnemonicToDynamicStrategies = | |
| dynamicMode == DynamicStrategyRegistry.DynamicMode.REMOTE | |
| ? mnemonicToRemoteDynamicStrategies | |
| : mnemonicToLocalDynamicStrategies; | |
| return ImmutableList.<SandboxedSpawnStrategy>builder() | |
| .addAll(mnemonicToDynamicStrategies.get(spawn.getMnemonic())) | |
| .addAll(mnemonicToDynamicStrategies.get("")) | |
| .build(); | |
| } | |
| @Nullable | |
| @Override | |
| public AbstractSpawnStrategy getRemoteLocalFallbackStrategy() { | |
| return remoteLocalFallbackStrategy; | |
| } | |
| private ImmutableMap<Class<? extends ActionContext>, ActionContext> createContextMap() { | |
| Map<Class<? extends ActionContext>, ActionContext> contextMap = new HashMap<>(); | |
| for (Map.Entry<Class<? extends ActionContext>, ActionContext> typeToStrategy : | |
| strategies.entrySet()) { | |
| ActionContext strategy = typeToStrategy.getValue(); | |
| contextMap.put(typeToStrategy.getKey(), strategy); | |
| contextMap.put(strategy.getClass(), strategy); | |
| } | |
| contextMap.put(SpawnStrategy.class, new ProxySpawnActionContext(this)); | |
| contextMap.put(DynamicStrategyRegistry.class, this); | |
| contextMap.put(RemoteLocalFallbackRegistry.class, this); | |
| return ImmutableMap.copyOf(contextMap); | |
| } | |
| @Nullable | |
| @Override | |
| public <T extends ActionContext> T getContext(Class<T> identifyingType) { | |
| return identifyingType.cast(contextMap.get(identifyingType)); | |
| } | |
| /** Returns a list of all referenced {@link ActionContext} instances. */ | |
| @VisibleForTesting | |
| public ImmutableList<ActionContext> allContexts() { | |
| // We need to keep only the last occurrences of the entries in contextImplementations | |
| // (so we respect insertion order but also instantiate them only once). | |
| LinkedHashSet<ActionContext> allContexts = new LinkedHashSet<>(strategies.values()); | |
| mnemonicToSpawnStrategiesMap.values().forEach(allContexts::addAll); | |
| spawnStrategyRegexList.forEach(x -> allContexts.addAll(x.strategies())); | |
| return ImmutableList.copyOf(allContexts); | |
| } | |
| /** | |
| * Notifies all (non-dynamic) contexts stored in this context map that they are {@link | |
| * ActionContext#usedContext used}. | |
| */ | |
| public void notifyUsed() { | |
| for (ActionContext context : allContexts()) { | |
| context.usedContext(this); | |
| } | |
| } | |
| @Override | |
| public void notifyUsedDynamic(ActionContext.ActionContextRegistry actionContextRegistry) { | |
| for (SandboxedSpawnStrategy context : mnemonicToRemoteDynamicStrategies.values()) { | |
| context.usedContext(actionContextRegistry); | |
| } | |
| for (SandboxedSpawnStrategy context : mnemonicToLocalDynamicStrategies.values()) { | |
| context.usedContext(actionContextRegistry); | |
| } | |
| } | |
| /** | |
| * Print a sorted list of our (Spawn)ActionContext maps. | |
| * | |
| * <p>Prints out debug information about the mappings. | |
| */ | |
| void debugPrintSpawnActionContextMaps(Reporter reporter) { | |
| for (Entry<String, List<SpawnStrategy>> entry : mnemonicToSpawnStrategiesMap.entrySet()) { | |
| List<String> strategyNames = | |
| entry.getValue().stream() | |
| .map(spawnActionContext -> spawnActionContext.getClass().getSimpleName()) | |
| .collect(Collectors.toList()); | |
| reporter.handle( | |
| Event.info( | |
| String.format( | |
| "SpawnActionContextMap: \"%s\" = [%s]", | |
| entry.getKey(), Joiner.on(", ").join(strategyNames)))); | |
| } | |
| ImmutableMap<Class<? extends ActionContext>, ActionContext> contextMap = createContextMap(); | |
| TreeMap<String, String> sortedContextMapWithSimpleNames = new TreeMap<>(); | |
| for (Map.Entry<Class<? extends ActionContext>, ActionContext> entry : contextMap.entrySet()) { | |
| sortedContextMapWithSimpleNames.put( | |
| entry.getKey().getSimpleName(), entry.getValue().getClass().getSimpleName()); | |
| } | |
| for (Map.Entry<String, String> entry : sortedContextMapWithSimpleNames.entrySet()) { | |
| // Skip uninteresting identity mappings of contexts. | |
| if (!entry.getKey().equals(entry.getValue())) { | |
| reporter.handle( | |
| Event.info(String.format("ContextMap: %s = %s", entry.getKey(), entry.getValue()))); | |
| } | |
| } | |
| for (RegexFilterSpawnStrategy entry : spawnStrategyRegexList) { | |
| reporter.handle( | |
| Event.info( | |
| String.format( | |
| "SpawnActionContextMap: \"%s\" = %s", | |
| entry.regexFilter().toString(), entry.strategies().getClass().getSimpleName()))); | |
| } | |
| } | |
| @VisibleForTesting | |
| public static SpawnActionContextMaps createStub( | |
| Map<Class<? extends ActionContext>, ActionContext> strategies, | |
| Map<String, List<SpawnStrategy>> spawnStrategyMnemonicMap) { | |
| return new SpawnActionContextMaps( | |
| ImmutableSortedMap.copyOf(spawnStrategyMnemonicMap, String.CASE_INSENSITIVE_ORDER), | |
| ImmutableClassToInstanceMap.copyOf(strategies), | |
| ImmutableList.of(), | |
| ImmutableMultimap.of(), | |
| ImmutableMultimap.of(), | |
| /* remoteLocalFallbackStrategy=*/ null); | |
| } | |
| /** A stored entry for a {@link RegexFilter} to {@code strategy} mapping. */ | |
| @AutoValue | |
| public abstract static class RegexFilterStrategy { | |
| public abstract RegexFilter regexFilter(); | |
| public abstract ImmutableList<String> strategy(); | |
| } | |
| /** Builder for {@code SpawnActionContextMaps}. */ | |
| public static final class Builder { | |
| private final LinkedHashMultimap<String, String> strategyByMnemonicMap = | |
| LinkedHashMultimap.create(); | |
| private ImmutableListMultimap.Builder<Class<? extends ActionContext>, String> | |
| strategyByContextMapBuilder = ImmutableListMultimap.builder(); | |
| private final ImmutableList.Builder<RegexFilterStrategy> strategyByRegexpBuilder = | |
| ImmutableList.builder(); | |
| private final LinkedHashMultimap<String, String> remoteDynamicStrategyByMnemonicMap = | |
| LinkedHashMultimap.create(); | |
| private final LinkedHashMultimap<String, String> localDynamicStrategyByMnemonicMap = | |
| LinkedHashMultimap.create(); | |
| private final List<ActionContextInformation<?>> actionContexts = new ArrayList<>(); | |
| @Nullable private String remoteLocalFallbackStrategyName; | |
| /** | |
| * Returns a builder modules can use to add mappings from mnemonics to strategy names. | |
| * | |
| * <p>If a spawn action is executed whose mnemonic maps to the empty string or is not present in | |
| * the map at all, the choice of the implementation is left to Blaze. | |
| * | |
| * <p>Matching on mnemonics is done case-insensitively so it is recommended that any module | |
| * makes sure that no two strategies refer to the same mnemonic. If they do, Blaze will pick the | |
| * last one added. | |
| */ | |
| public LinkedHashMultimap<String, String> strategyByMnemonicMap() { | |
| return strategyByMnemonicMap; | |
| } | |
| /** | |
| * Returns a builder modules can use to add mappings from mnemonics to strategy names for use in | |
| * the remote branch of dynamic execution. | |
| * | |
| * <p>If a spawn action is executed whose mnemonic maps to the empty string or is not present in | |
| * the map at all, the choice of the implementation is left to Blaze. | |
| * | |
| * <p>Matching on mnemonics is done case-insensitively so it is recommended that any module | |
| * makes sure that no two strategies refer to the same mnemonic. If they do, Blaze will pick the | |
| * last one added. | |
| */ | |
| public LinkedHashMultimap<String, String> remoteDynamicStrategyByMnemonicMap() { | |
| return remoteDynamicStrategyByMnemonicMap; | |
| } | |
| /** | |
| * Returns a builder modules can use to add mappings from mnemonics to strategy names for use in | |
| * the local branch of dynamic execution. | |
| * | |
| * <p>If a spawn action is executed whose mnemonic maps to the empty string or is not present in | |
| * the map at all, the choice of the implementation is left to Blaze. | |
| * | |
| * <p>Matching on mnemonics is done case-insensitively so it is recommended that any module | |
| * makes sure that no two strategies refer to the same mnemonic. If they do, Blaze will pick the | |
| * last one added. | |
| */ | |
| public LinkedHashMultimap<String, String> localDynamicStrategyByMnemonicMap() { | |
| return localDynamicStrategyByMnemonicMap; | |
| } | |
| /** | |
| * Sets the command-line identifier of the strategy to be used when falling back from remote to | |
| * local execution. | |
| * | |
| * <p>Note that this is an optional setting, if not provided {@link | |
| * SpawnActionContextMaps#getRemoteLocalFallbackStrategy()} will return {@code null}. If the | |
| * value <b>is</b> provided it must match the commandline identifier of a registered strategy | |
| * (at {@linkplain #build build} time). | |
| */ | |
| public void setRemoteFallbackStrategy(String remoteLocalFallbackStrategy) { | |
| this.remoteLocalFallbackStrategyName = remoteLocalFallbackStrategy; | |
| } | |
| /** | |
| * Returns a builder modules can use to associate {@link ActionContext} classes with strategy | |
| * names. | |
| */ | |
| public ImmutableMultimap.Builder<Class<? extends ActionContext>, String> | |
| strategyByContextMap() { | |
| return strategyByContextMapBuilder; | |
| } | |
| /** Adds a mapping from the given {@link RegexFilter} to a {@code strategy}. */ | |
| public void addStrategyByRegexp(RegexFilter regexFilter, List<String> strategy) { | |
| strategyByRegexpBuilder.add( | |
| new AutoValue_SpawnActionContextMaps_RegexFilterStrategy( | |
| regexFilter, ImmutableList.copyOf(strategy))); | |
| } | |
| /** | |
| * Adds a context implementation to this map with the given identifying type and command-line | |
| * identifiers. | |
| * | |
| * <p>If two contexts are added for the same identifying type and they are not distinguished by | |
| * a restriction to a different command-line identifier then the last registered implementation | |
| * is used. | |
| */ | |
| public <T extends ActionContext> Builder addContext( | |
| Class<T> identifyingType, T context, String... commandLineIdentifiers) { | |
| actionContexts.add( | |
| new AutoValue_SpawnActionContextMaps_ActionContextInformation<>( | |
| context, identifyingType, ImmutableList.copyOf(commandLineIdentifiers))); | |
| return this; | |
| } | |
| /** Builds a {@link SpawnActionContextMaps} instance. */ | |
| public SpawnActionContextMaps build() throws ExecutorInitException { | |
| StrategyConverter strategyConverter = new StrategyConverter(actionContexts); | |
| ImmutableSortedMap.Builder<String, List<SpawnStrategy>> spawnStrategyMap = | |
| ImmutableSortedMap.orderedBy(String.CASE_INSENSITIVE_ORDER); | |
| HashMap<Class<? extends ActionContext>, ActionContext> strategies = new HashMap<>(); | |
| ImmutableList.Builder<RegexFilterSpawnStrategy> spawnStrategyRegexList = | |
| ImmutableList.builder(); | |
| for (String mnemonic : strategyByMnemonicMap.keySet()) { | |
| ImmutableList.Builder<SpawnStrategy> spawnStrategies = ImmutableList.builder(); | |
| Set<String> strategiesForMnemonic = strategyByMnemonicMap.get(mnemonic); | |
| for (String strategy : strategiesForMnemonic) { | |
| SpawnStrategy spawnStrategy = | |
| strategyConverter.getStrategy(SpawnStrategy.class, strategy); | |
| if (spawnStrategy == null) { | |
| String strategyOrNull = Strings.emptyToNull(strategy); | |
| throw makeExceptionForInvalidStrategyValue( | |
| strategy, | |
| Joiner.on(' ').skipNulls().join(strategyOrNull, "spawn"), | |
| strategyConverter.getValidValues(SpawnStrategy.class)); | |
| } | |
| spawnStrategies.add(spawnStrategy); | |
| } | |
| spawnStrategyMap.put(mnemonic, spawnStrategies.build()); | |
| } | |
| Set<ActionContext> seenContext = new HashSet<>(); | |
| for (Map.Entry<Class<? extends ActionContext>, String> entry : | |
| strategyByContextMapBuilder.orderValuesBy(Collections.reverseOrder()).build().entries()) { | |
| ActionContext context = strategyConverter.getStrategy(entry.getKey(), entry.getValue()); | |
| if (context == null) { | |
| throw makeExceptionForInvalidStrategyValue( | |
| entry.getValue(), | |
| strategyConverter.getUserFriendlyName(entry.getKey()), | |
| strategyConverter.getValidValues(entry.getKey())); | |
| } | |
| if (seenContext.contains(context)) { | |
| continue; | |
| } | |
| seenContext.add(context); | |
| strategies.put(entry.getKey(), context); | |
| } | |
| for (RegexFilterStrategy entry : strategyByRegexpBuilder.build()) { | |
| ImmutableList.Builder<SpawnStrategy> spawnStrategies = ImmutableList.builder(); | |
| List<String> strategiesForRegex = entry.strategy(); | |
| for (String strategy : strategiesForRegex) { | |
| SpawnStrategy spawnStrategy = | |
| strategyConverter.getStrategy(SpawnStrategy.class, strategy); | |
| if (spawnStrategy == null) { | |
| strategy = Strings.emptyToNull(strategy); | |
| throw makeExceptionForInvalidStrategyValue( | |
| entry.regexFilter().toString(), | |
| Joiner.on(' ').skipNulls().join(strategy, "spawn"), | |
| strategyConverter.getValidValues(SpawnStrategy.class)); | |
| } | |
| spawnStrategies.add(spawnStrategy); | |
| } | |
| spawnStrategyRegexList.add( | |
| new AutoValue_SpawnActionContextMaps_RegexFilterSpawnStrategy( | |
| entry.regexFilter(), spawnStrategies.build())); | |
| } | |
| AbstractSpawnStrategy remoteLocalFallbackStrategy = null; | |
| if (remoteLocalFallbackStrategyName != null) { | |
| SpawnStrategy strategy = | |
| strategyConverter.getStrategy(SpawnStrategy.class, remoteLocalFallbackStrategyName); | |
| if (!(strategy instanceof AbstractSpawnStrategy)) { | |
| throw makeExceptionForInvalidStrategyValue( | |
| remoteLocalFallbackStrategyName, | |
| "remote local fallback", | |
| strategyConverter.getValidValues(SpawnStrategy.class, "remote")); | |
| } | |
| remoteLocalFallbackStrategy = (AbstractSpawnStrategy) strategy; | |
| } | |
| return new SpawnActionContextMaps( | |
| spawnStrategyMap.build(), | |
| ImmutableClassToInstanceMap.copyOf(strategies), | |
| spawnStrategyRegexList.build(), | |
| toActionContexts(strategyConverter, remoteDynamicStrategyByMnemonicMap), | |
| toActionContexts(strategyConverter, localDynamicStrategyByMnemonicMap), | |
| remoteLocalFallbackStrategy); | |
| } | |
| private ImmutableMultimap<String, SandboxedSpawnStrategy> toActionContexts( | |
| StrategyConverter strategyConverter, | |
| LinkedHashMultimap<String, String> dynamicStrategyByMnemonicMap) | |
| throws ExecutorInitException { | |
| ImmutableMultimap.Builder<String, SandboxedSpawnStrategy> mnemonicToStrategies = | |
| ImmutableMultimap.builder(); | |
| for (Entry<String, Collection<String>> mnemonicToIdentifiers : | |
| dynamicStrategyByMnemonicMap.asMap().entrySet()) { | |
| for (String identifier : mnemonicToIdentifiers.getValue()) { | |
| if (identifier.isEmpty()) { | |
| continue; | |
| } | |
| SpawnStrategy strategy = strategyConverter.getStrategy(SpawnStrategy.class, identifier); | |
| if (strategy == null) { | |
| throw makeExceptionForInvalidStrategyValue( | |
| identifier, | |
| Joiner.on(' ').skipNulls().join(Strings.emptyToNull(identifier), "spawn"), | |
| strategyConverter.getValidValues(SpawnStrategy.class)); | |
| } | |
| if (!(strategy instanceof SandboxedSpawnStrategy)) { | |
| throw new ExecutorInitException( | |
| "Requested strategy " + identifier + " exists but does not support sandboxing"); | |
| } | |
| mnemonicToStrategies.put( | |
| mnemonicToIdentifiers.getKey(), (SandboxedSpawnStrategy) strategy); | |
| } | |
| } | |
| return mnemonicToStrategies.build(); | |
| } | |
| } | |
| private static ExecutorInitException makeExceptionForInvalidStrategyValue( | |
| String value, String strategy, String validValues) { | |
| return new ExecutorInitException( | |
| String.format( | |
| "'%s' is an invalid value for %s strategy. Valid values are: %s", | |
| value, strategy, validValues), | |
| ExitCode.COMMAND_LINE_ERROR); | |
| } | |
| private static class StrategyConverter { | |
| private Table<Class<? extends ActionContext>, String, ActionContext> classMap = | |
| HashBasedTable.create(); | |
| private Map<Class<? extends ActionContext>, ActionContext> defaultClassMap = new HashMap<>(); | |
| /** Aggregates all {@link ActionContext}s that are in {@code contextProviders}. */ | |
| private StrategyConverter(List<ActionContextInformation<?>> actionContexts) { | |
| for (ActionContextInformation<?> contextInformation : actionContexts) { | |
| defaultClassMap.put(contextInformation.identifyingType(), contextInformation.context()); | |
| for (String name : contextInformation.commandLineIdentifiers()) { | |
| classMap.put(contextInformation.identifyingType(), name, contextInformation.context()); | |
| } | |
| } | |
| } | |
| @SuppressWarnings("unchecked") | |
| private <T extends ActionContext> T getStrategy(Class<T> clazz, String name) { | |
| return (T) (name.isEmpty() ? defaultClassMap.get(clazz) : classMap.get(clazz, name)); | |
| } | |
| private String getValidValues(Class<? extends ActionContext> context, String... excludes) { | |
| ImmutableSet<String> excludedNames = ImmutableSet.copyOf(excludes); | |
| return classMap.row(context).keySet().stream() | |
| .filter(s -> !excludedNames.contains(s)) | |
| .sorted() | |
| .collect(Collectors.joining(", ")); | |
| } | |
| private String getUserFriendlyName(Class<? extends ActionContext> context) { | |
| ActionContextMarker marker = context.getAnnotation(ActionContextMarker.class); | |
| return marker != null ? marker.name() : context.getSimpleName(); | |
| } | |
| } | |
| @AutoValue | |
| abstract static class ActionContextInformation<T extends ActionContext> { | |
| abstract T context(); | |
| abstract Class<T> identifyingType(); | |
| abstract ImmutableList<String> commandLineIdentifiers(); | |
| } | |
| } |