| /* |
| * Copyright 2016 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.idea.blaze.base.scope; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.collect.ArrayListMultimap; |
| import com.google.common.collect.Lists; |
| import java.util.List; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| /** Scoped operation context. */ |
| public class BlazeContext { |
| @Nullable private BlazeContext parentContext; |
| |
| @NotNull private final List<BlazeScope> scopes = Lists.newArrayList(); |
| |
| @NotNull |
| private final ArrayListMultimap<Class<? extends Output>, OutputSink<?>> outputSinks = |
| ArrayListMultimap.create(); |
| |
| boolean isEnding; |
| |
| boolean isCancelled; |
| |
| private int holdCount; |
| |
| private boolean hasErrors; |
| |
| private boolean propagatesErrors = true; |
| |
| public BlazeContext() { |
| this(null); |
| } |
| |
| public BlazeContext(@Nullable BlazeContext parentContext) { |
| this.parentContext = parentContext; |
| } |
| |
| public BlazeContext push(@NotNull BlazeScope scope) { |
| scopes.add(scope); |
| scope.onScopeBegin(this); |
| return this; |
| } |
| |
| /** Ends the context scope. */ |
| public void endScope() { |
| if (isEnding || holdCount > 0) { |
| return; |
| } |
| isEnding = true; |
| for (int i = scopes.size() - 1; i >= 0; i--) { |
| scopes.get(i).onScopeEnd(this); |
| } |
| |
| if (parentContext != null && hasErrors && propagatesErrors) { |
| parentContext.setHasError(); |
| } |
| } |
| |
| /** |
| * Requests cancellation of the operation. |
| * |
| * <p> |
| * |
| * <p>Each context holder must handle cancellation individually. |
| */ |
| public void setCancelled() { |
| if (isEnding || isCancelled) { |
| return; |
| } |
| |
| isCancelled = true; |
| |
| if (parentContext != null) { |
| parentContext.setCancelled(); |
| } |
| } |
| |
| public void hold() { |
| ++holdCount; |
| } |
| |
| public void release() { |
| if (--holdCount == 0) { |
| endScope(); |
| } |
| } |
| |
| public boolean isEnding() { |
| return isEnding; |
| } |
| |
| public boolean isCancelled() { |
| return isCancelled; |
| } |
| |
| @Nullable |
| public <T extends BlazeScope> T getScope(@NotNull Class<T> scopeClass) { |
| return getScope(scopeClass, scopes.size()); |
| } |
| |
| @Nullable |
| private <T extends BlazeScope> T getScope(@NotNull Class<T> scopeClass, int endIndex) { |
| for (int i = endIndex - 1; i >= 0; i--) { |
| if (scopes.get(i).getClass() == scopeClass) { |
| return scopeClass.cast(scopes.get(i)); |
| } |
| } |
| if (parentContext != null) { |
| return parentContext.getScope(scopeClass); |
| } |
| return null; |
| } |
| |
| @Nullable |
| public <T extends BlazeScope> T getParentScope(@NotNull T scope) { |
| int index = scopes.indexOf(scope); |
| if (index == -1) { |
| throw new IllegalArgumentException("Scope does not belong to this context."); |
| } |
| @SuppressWarnings("unchecked") |
| Class<T> scopeClass = (Class<T>) scope.getClass(); |
| return getScope(scopeClass, index); |
| } |
| |
| /** |
| * Find all instances of {@param scopeClass} that are on the stack starting with this context. |
| * That includes this context and all parent contexts recursively. |
| * |
| * @param scopeClass type of scopes to locate |
| * @return The ordered list of all scopes of type {@param scopeClass}, ordered from {@param |
| * startingScope} to the root. |
| */ |
| @NotNull |
| public <T extends BlazeScope> List<T> getScopes(@NotNull Class<T> scopeClass) { |
| List<T> scopesCollector = Lists.newArrayList(); |
| getScopes(scopesCollector, scopeClass, scopes.size()); |
| return scopesCollector; |
| } |
| |
| /** |
| * Find all instances of {@param scopeClass} that are above {@param startingScope} on the stack. |
| * That includes this context and all parent contexts recursively. {@param startingScope} must be |
| * in the this {@link BlazeContext}. |
| * |
| * @param scopeClass type of scopes to locate |
| * @param startingScope scope to start our search from |
| * @return If {@param startingScope} is in this context, the ordered list of all scopes of type |
| * {@param scopeClass}, ordered from {@param startingScope} to the root. Otherwise, an empty |
| * list. |
| */ |
| @NotNull |
| public <T extends BlazeScope> List<T> getScopes( |
| @NotNull Class<T> scopeClass, @NotNull BlazeScope startingScope) { |
| List<T> scopesCollector = Lists.newArrayList(); |
| int index = scopes.indexOf(startingScope); |
| if (index == -1) { |
| return scopesCollector; |
| } |
| |
| // index + 1 so we include startingScope |
| getScopes(scopesCollector, scopeClass, index + 1); |
| return scopesCollector; |
| } |
| |
| /** Add matching scopes to {@param scopesCollector}. Search from {@param maxIndex} - 1 to 0. */ |
| @VisibleForTesting |
| <T extends BlazeScope> void getScopes( |
| @NotNull List<T> scopesCollector, @NotNull Class<T> scopeClass, int maxIndex) { |
| for (int i = maxIndex - 1; i >= 0; --i) { |
| BlazeScope scope = scopes.get(i); |
| if (scope.getClass() == scopeClass) { |
| scopesCollector.add((T) scope); |
| } |
| } |
| if (parentContext != null) { |
| parentContext.getScopes(scopesCollector, scopeClass, parentContext.scopes.size()); |
| } |
| } |
| |
| public <T extends Output> BlazeContext addOutputSink( |
| @NotNull Class<T> outputClass, @NotNull OutputSink<T> outputSink) { |
| outputSinks.put(outputClass, outputSink); |
| return this; |
| } |
| |
| /** Produces output by sending it to any registered sinks. */ |
| @SuppressWarnings("unchecked") |
| public synchronized <T extends Output> void output(@NotNull T output) { |
| Class<? extends Output> outputClass = output.getClass(); |
| List<OutputSink<?>> outputSinks = this.outputSinks.get(outputClass); |
| |
| boolean continuePropagation = true; |
| for (int i = outputSinks.size() - 1; i >= 0; --i) { |
| OutputSink<?> outputSink = outputSinks.get(i); |
| OutputSink.Propagation propagation = ((OutputSink<T>) outputSink).onOutput(output); |
| continuePropagation = propagation == OutputSink.Propagation.Continue; |
| if (!continuePropagation) { |
| break; |
| } |
| } |
| if (continuePropagation && parentContext != null) { |
| parentContext.output(output); |
| } |
| } |
| |
| /** |
| * Sets the error state. |
| * |
| * <p> |
| * |
| * <p>The error state will be propagated to any parents. |
| */ |
| public void setHasError() { |
| this.hasErrors = true; |
| } |
| |
| /** Returns true if there were errors */ |
| public boolean hasErrors() { |
| return hasErrors; |
| } |
| |
| public boolean isRoot() { |
| return parentContext == null; |
| } |
| |
| /** Returns true if no errors and isn't cancelled. */ |
| public boolean shouldContinue() { |
| return !hasErrors() && !isCancelled(); |
| } |
| |
| /** Sets whether errors are propagated to the parent context. */ |
| public void setPropagatesErrors(boolean propagatesErrors) { |
| this.propagatesErrors = propagatesErrors; |
| } |
| } |