| /* |
| * Copyright 2022 The Android Open Source Project |
| * |
| * 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 androidx.wear.protolayout.expression.pipeline; |
| |
| import static java.util.Collections.emptyMap; |
| |
| import android.annotation.SuppressLint; |
| import android.icu.util.ULocale; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.RestrictTo; |
| import androidx.annotation.RestrictTo.Scope; |
| import androidx.annotation.VisibleForTesting; |
| import androidx.collection.ArrayMap; |
| import androidx.wear.protolayout.expression.DynamicBuilders; |
| import androidx.wear.protolayout.expression.PlatformDataKey; |
| import androidx.wear.protolayout.expression.pipeline.BoolNodes.ComparisonFloatNode; |
| import androidx.wear.protolayout.expression.pipeline.BoolNodes.ComparisonInt32Node; |
| import androidx.wear.protolayout.expression.pipeline.BoolNodes.FixedBoolNode; |
| import androidx.wear.protolayout.expression.pipeline.BoolNodes.LogicalBoolOp; |
| import androidx.wear.protolayout.expression.pipeline.BoolNodes.NotBoolOp; |
| import androidx.wear.protolayout.expression.pipeline.BoolNodes.StateBoolNode; |
| import androidx.wear.protolayout.expression.pipeline.ColorNodes.AnimatableFixedColorNode; |
| import androidx.wear.protolayout.expression.pipeline.ColorNodes.DynamicAnimatedColorNode; |
| import androidx.wear.protolayout.expression.pipeline.ColorNodes.FixedColorNode; |
| import androidx.wear.protolayout.expression.pipeline.ColorNodes.StateColorSourceNode; |
| import androidx.wear.protolayout.expression.pipeline.DurationNodes.BetweenInstancesNode; |
| import androidx.wear.protolayout.expression.pipeline.DurationNodes.FixedDurationNode; |
| import androidx.wear.protolayout.expression.pipeline.DurationNodes.StateDurationSourceNode; |
| import androidx.wear.protolayout.expression.pipeline.FloatNodes.AnimatableFixedFloatNode; |
| import androidx.wear.protolayout.expression.pipeline.FloatNodes.ArithmeticFloatNode; |
| import androidx.wear.protolayout.expression.pipeline.FloatNodes.DynamicAnimatedFloatNode; |
| import androidx.wear.protolayout.expression.pipeline.FloatNodes.FixedFloatNode; |
| import androidx.wear.protolayout.expression.pipeline.FloatNodes.Int32ToFloatNode; |
| import androidx.wear.protolayout.expression.pipeline.FloatNodes.StateFloatSourceNode; |
| import androidx.wear.protolayout.expression.pipeline.InstantNodes.FixedInstantNode; |
| import androidx.wear.protolayout.expression.pipeline.InstantNodes.PlatformTimeSourceNode; |
| import androidx.wear.protolayout.expression.pipeline.InstantNodes.StateInstantSourceNode; |
| import androidx.wear.protolayout.expression.pipeline.Int32Nodes.AnimatableFixedInt32Node; |
| import androidx.wear.protolayout.expression.pipeline.Int32Nodes.ArithmeticInt32Node; |
| import androidx.wear.protolayout.expression.pipeline.Int32Nodes.DynamicAnimatedInt32Node; |
| import androidx.wear.protolayout.expression.pipeline.Int32Nodes.FixedInt32Node; |
| import androidx.wear.protolayout.expression.pipeline.Int32Nodes.FloatToInt32Node; |
| import androidx.wear.protolayout.expression.pipeline.Int32Nodes.GetDurationPartOpNode; |
| import androidx.wear.protolayout.expression.pipeline.Int32Nodes.GetZonedDateTimePartOpNode; |
| import androidx.wear.protolayout.expression.pipeline.Int32Nodes.LegacyPlatformInt32SourceNode; |
| import androidx.wear.protolayout.expression.pipeline.Int32Nodes.StateInt32SourceNode; |
| import androidx.wear.protolayout.expression.pipeline.StringNodes.FixedStringNode; |
| import androidx.wear.protolayout.expression.pipeline.StringNodes.FloatFormatNode; |
| import androidx.wear.protolayout.expression.pipeline.StringNodes.Int32FormatNode; |
| import androidx.wear.protolayout.expression.pipeline.StringNodes.StateStringNode; |
| import androidx.wear.protolayout.expression.pipeline.StringNodes.StringConcatOpNode; |
| import androidx.wear.protolayout.expression.pipeline.ZonedDateTimeNodes.InstantToZonedDateTimeOpNode; |
| import androidx.wear.protolayout.expression.proto.DynamicProto; |
| import androidx.wear.protolayout.expression.proto.DynamicProto.AnimatableDynamicColor; |
| import androidx.wear.protolayout.expression.proto.DynamicProto.AnimatableDynamicFloat; |
| import androidx.wear.protolayout.expression.proto.DynamicProto.AnimatableDynamicInt32; |
| import androidx.wear.protolayout.expression.proto.DynamicProto.ConditionalColorOp; |
| import androidx.wear.protolayout.expression.proto.DynamicProto.ConditionalDurationOp; |
| import androidx.wear.protolayout.expression.proto.DynamicProto.ConditionalFloatOp; |
| import androidx.wear.protolayout.expression.proto.DynamicProto.ConditionalInstantOp; |
| import androidx.wear.protolayout.expression.proto.DynamicProto.ConditionalInt32Op; |
| import androidx.wear.protolayout.expression.proto.DynamicProto.ConditionalStringOp; |
| import androidx.wear.protolayout.expression.proto.DynamicProto.DynamicBool; |
| import androidx.wear.protolayout.expression.proto.DynamicProto.DynamicColor; |
| import androidx.wear.protolayout.expression.proto.DynamicProto.DynamicDuration; |
| import androidx.wear.protolayout.expression.proto.DynamicProto.DynamicFloat; |
| import androidx.wear.protolayout.expression.proto.DynamicProto.DynamicInstant; |
| import androidx.wear.protolayout.expression.proto.DynamicProto.DynamicInt32; |
| import androidx.wear.protolayout.expression.proto.DynamicProto.DynamicString; |
| import androidx.wear.protolayout.expression.proto.DynamicProto.DynamicZonedDateTime; |
| |
| import java.time.Duration; |
| import java.time.Instant; |
| import java.time.ZonedDateTime; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.Executor; |
| import java.util.function.Supplier; |
| |
| /** |
| * Evaluates protolayout dynamic types. |
| * |
| * <p>Given a dynamic ProtoLayout data source, this builds up a sequence of {@link DynamicDataNode} |
| * instances, which can source the required data, and transform it into its final form. |
| * |
| * <p>Data source can include animations which will then emit value transitions. |
| * |
| * <p>In order to evaluate dynamic types, the caller needs to create a {@link |
| * DynamicTypeBindingRequest}, bind it using {@link #bind(DynamicTypeBindingRequest)} method and |
| * then call {@link BoundDynamicType#startEvaluation()} on the resulted {@link BoundDynamicType} to |
| * start evaluation. Starting evaluation can be done for batches of dynamic types. |
| * |
| * <p>It's the callers responsibility to destroy those dynamic types after use, with {@link |
| * BoundDynamicType#close()}. |
| */ |
| public class DynamicTypeEvaluator { |
| private static final String TAG = "DynamicTypeEvaluator"; |
| private static final QuotaManager NO_OP_QUOTA_MANAGER = |
| new FixedQuotaManagerImpl(Integer.MAX_VALUE, "dynamic nodes noop"); |
| |
| @NonNull |
| private static final QuotaManager DISABLED_ANIMATIONS_QUOTA_MANAGER = |
| new QuotaManager() { |
| @Override |
| public boolean tryAcquireQuota(int quota) { |
| return false; |
| } |
| |
| @Override |
| public void releaseQuota(int quota) { |
| throw new IllegalStateException( |
| "releaseQuota method is called when no quota is acquired!"); |
| } |
| }; |
| |
| /** Exception thrown when the binding of a {@link DynamicTypeBindingRequest} fails. */ |
| public static class EvaluationException extends Exception { |
| public EvaluationException(@NonNull String message) { |
| super(message); |
| } |
| } |
| |
| @NonNull private static final StateStore EMPTY_STATE_STORE = new StateStore(emptyMap()); |
| |
| @NonNull private final StateStore mStateStore; |
| @NonNull private final PlatformDataStore mPlatformDataStore; |
| @NonNull private final QuotaManager mAnimationQuotaManager; |
| @NonNull private final QuotaManager mDynamicTypesQuotaManager; |
| @NonNull private final EpochTimePlatformDataSource mTimeDataSource; |
| |
| /** Configuration for creating {@link DynamicTypeEvaluator}. */ |
| public static final class Config { |
| @Nullable private final StateStore mStateStore; |
| @Nullable private final QuotaManager mAnimationQuotaManager; |
| @Nullable private final QuotaManager mDynamicTypesQuotaManager; |
| |
| @NonNull |
| private final Map<PlatformDataKey<?>, PlatformDataProvider> mSourceKeyToDataProviders = |
| new ArrayMap<>(); |
| |
| @Nullable private final PlatformTimeUpdateNotifier mPlatformTimeUpdateNotifier; |
| @Nullable private final Supplier<Instant> mClock; |
| |
| Config( |
| @Nullable StateStore stateStore, |
| @Nullable QuotaManager animationQuotaManager, |
| @Nullable QuotaManager dynamicTypesQuotaManager, |
| @NonNull Map<PlatformDataKey<?>, PlatformDataProvider> sourceKeyToDataProviders, |
| @Nullable PlatformTimeUpdateNotifier platformTimeUpdateNotifier, |
| @Nullable Supplier<Instant> clock) { |
| this.mStateStore = stateStore; |
| this.mAnimationQuotaManager = animationQuotaManager; |
| this.mDynamicTypesQuotaManager = dynamicTypesQuotaManager; |
| this.mSourceKeyToDataProviders.putAll(sourceKeyToDataProviders); |
| this.mPlatformTimeUpdateNotifier = platformTimeUpdateNotifier; |
| this.mClock = clock; |
| } |
| |
| /** Builds a {@link DynamicTypeEvaluator.Config}. */ |
| public static final class Builder { |
| @Nullable private StateStore mStateStore = null; |
| @Nullable private QuotaManager mAnimationQuotaManager = null; |
| @Nullable private QuotaManager mDynamicTypesQuotaManager = null; |
| |
| @NonNull |
| private final Map<PlatformDataKey<?>, PlatformDataProvider> mSourceKeyToDataProviders = |
| new ArrayMap<>(); |
| |
| @Nullable private PlatformTimeUpdateNotifier mPlatformTimeUpdateNotifier = null; |
| @Nullable private Supplier<Instant> mClock = null; |
| |
| /** |
| * Sets the state store that will be used for dereferencing the state keys in the |
| * dynamic types. |
| * |
| * <p>If not set, it's the equivalent of setting an empty state store (state bindings |
| * will trigger {@link DynamicTypeValueReceiver#onInvalidated()}). |
| */ |
| @NonNull |
| public Builder setStateStore(@NonNull StateStore value) { |
| mStateStore = value; |
| return this; |
| } |
| |
| /** |
| * Sets the quota manager used for limiting the number of concurrently running |
| * animations. |
| * |
| * <p>If not set, animations are disabled and non-infinite animations will have the end |
| * value immediately. |
| */ |
| @NonNull |
| public Builder setAnimationQuotaManager(@NonNull QuotaManager value) { |
| mAnimationQuotaManager = value; |
| return this; |
| } |
| |
| /** |
| * Sets the quota manager used for limiting the total size of dynamic types in the |
| * pipeline. |
| * |
| * <p>If not set, number of dynamic types will not be restricted. |
| */ |
| @NonNull |
| public Builder setDynamicTypesQuotaManager(@NonNull QuotaManager value) { |
| mDynamicTypesQuotaManager = value; |
| return this; |
| } |
| |
| /** |
| * Add a platform data provider and specify the keys it can provide dynamic data for. |
| * |
| * <p>The provider must support at least one key. If the provider supports multiple |
| * keys, they should not be independent, as their values should always update together. |
| * One data key must not have multiple providers, or an exception will be thrown. |
| * |
| * @throws IllegalArgumentException If a PlatformDataProvider supports an empty key set |
| * or if a key has multiple data providers. |
| */ |
| @SuppressLint("MissingGetterMatchingBuilder") |
| @NonNull |
| public Builder addPlatformDataProvider( |
| @NonNull PlatformDataProvider platformDataProvider, |
| @NonNull Set<PlatformDataKey<?>> supportedDataKeys) { |
| if (supportedDataKeys.isEmpty()) { |
| throw new IllegalArgumentException( |
| "The PlatformDataProvider must support at least one key"); |
| } |
| for (PlatformDataKey<?> dataKey : supportedDataKeys) { |
| // Throws exception when one data key has multiple providers. |
| if (mSourceKeyToDataProviders.containsKey(dataKey)) { |
| throw new IllegalArgumentException( |
| String.format( |
| "Multiple data providers for PlatformDataKey (%s)", |
| dataKey)); |
| } |
| mSourceKeyToDataProviders.put(dataKey, platformDataProvider); |
| } |
| |
| return this; |
| } |
| |
| /** |
| * Sets the notifier used for updating the platform time data. If not set, by default |
| * platform time will be updated at 1Hz using a {@code Handler} on the main thread. |
| */ |
| @NonNull |
| public Builder setPlatformTimeUpdateNotifier( |
| @NonNull PlatformTimeUpdateNotifier notifier) { |
| this.mPlatformTimeUpdateNotifier = notifier; |
| return this; |
| } |
| |
| /** |
| * Sets the clock ({@link Instant} supplier) used for providing time data to bindings. |
| * If not set, on every reevaluation, platform time for dynamic values will be set to |
| * {@link Instant#now()}. |
| */ |
| @VisibleForTesting |
| @NonNull |
| public Builder setClock(@NonNull Supplier<Instant> clock) { |
| this.mClock = clock; |
| return this; |
| } |
| |
| @NonNull |
| public Config build() { |
| return new Config( |
| mStateStore, |
| mAnimationQuotaManager, |
| mDynamicTypesQuotaManager, |
| mSourceKeyToDataProviders, |
| mPlatformTimeUpdateNotifier, |
| mClock); |
| } |
| } |
| |
| /** |
| * Gets the state store that will be used for dereferencing the state keys in the dynamic |
| * types, or {@code null} which is equivalent to an empty state store (state bindings will |
| * trigger {@link DynamicTypeValueReceiver#onInvalidated()}). |
| */ |
| @Nullable |
| public StateStore getStateStore() { |
| return mStateStore; |
| } |
| |
| /** |
| * Gets the quota manager used for limiting the number of concurrently running animations, |
| * or {@code null} if animations are disabled, causing non-infinite animations to have to |
| * the end value immediately. |
| */ |
| @Nullable |
| public QuotaManager getAnimationQuotaManager() { |
| return mAnimationQuotaManager; |
| } |
| |
| /** |
| * Gets the quota manager used for limiting the total number of dynamic types in the |
| * pipeline, or {@code null} if there are no restriction on the number of dynamic types. If |
| * present, the quota manager is used to prevent unreasonably expensive expressions. |
| */ |
| @Nullable |
| public QuotaManager getDynamicTypesQuotaManager() { |
| return mDynamicTypesQuotaManager; |
| } |
| |
| /** Returns any available mapping between source key and its data provider. */ |
| @NonNull |
| public Map<PlatformDataKey<?>, PlatformDataProvider> getPlatformDataProviders() { |
| return new ArrayMap<>( |
| (ArrayMap<PlatformDataKey<?>, PlatformDataProvider>) mSourceKeyToDataProviders); |
| } |
| |
| /** |
| * Returns the clock ({@link Instant} supplier) used for providing time data to bindings, or |
| * {@code null} which means on every reevaluation, platform time for dynamic values will be |
| * set to {@link Instant#now()}. |
| */ |
| @VisibleForTesting |
| @Nullable |
| public Supplier<Instant> getClock() { |
| return mClock; |
| } |
| |
| /** Gets the notifier used for updating the platform time data. */ |
| @Nullable |
| public PlatformTimeUpdateNotifier getPlatformTimeUpdateNotifier() { |
| return mPlatformTimeUpdateNotifier; |
| } |
| } |
| |
| /** Constructs a {@link DynamicTypeEvaluator}. */ |
| public DynamicTypeEvaluator(@NonNull Config config) { |
| this.mStateStore = |
| config.getStateStore() != null ? config.getStateStore() : EMPTY_STATE_STORE; |
| this.mAnimationQuotaManager = |
| config.getAnimationQuotaManager() != null |
| ? config.getAnimationQuotaManager() |
| : DISABLED_ANIMATIONS_QUOTA_MANAGER; |
| this.mDynamicTypesQuotaManager = |
| config.getDynamicTypesQuotaManager() != null |
| ? config.getDynamicTypesQuotaManager() |
| : NO_OP_QUOTA_MANAGER; |
| this.mPlatformDataStore = new PlatformDataStore(config.getPlatformDataProviders()); |
| PlatformTimeUpdateNotifier notifier = config.getPlatformTimeUpdateNotifier(); |
| if (notifier == null) { |
| notifier = new PlatformTimeUpdateNotifierImpl(); |
| ((PlatformTimeUpdateNotifierImpl) notifier).setUpdatesEnabled(true); |
| } |
| Supplier<Instant> clock = config.getClock() != null ? config.getClock() : Instant::now; |
| this.mTimeDataSource = new EpochTimePlatformDataSource(clock, notifier); |
| } |
| |
| /** |
| * Binds a {@link DynamicTypeBindingRequest}. |
| * |
| * <p>Evaluation of this request will start when {@link BoundDynamicType#startEvaluation()} is |
| * called on the returned object. |
| * |
| * @throws EvaluationException when {@link QuotaManager} fails to allocate enough quota to bind |
| * the {@link DynamicTypeBindingRequest}. |
| */ |
| @NonNull |
| public BoundDynamicType bind(@NonNull DynamicTypeBindingRequest request) |
| throws EvaluationException { |
| BoundDynamicTypeImpl boundDynamicType = request.callBindOn(this); |
| if (!mDynamicTypesQuotaManager.tryAcquireQuota(boundDynamicType.getDynamicNodeCount())) { |
| throw new EvaluationException( |
| "Dynamic type expression limit reached. Try making the dynamic type expression" |
| + " shorter or reduce the number of dynamic type expressions."); |
| } |
| return boundDynamicType; |
| } |
| |
| @NonNull |
| BoundDynamicTypeImpl bindInternal( |
| @NonNull DynamicBuilders.DynamicString stringSource, |
| @NonNull ULocale locale, |
| @NonNull Executor executor, |
| @NonNull DynamicTypeValueReceiver<String> consumer) { |
| return bindInternal( |
| stringSource.toDynamicStringProto(), |
| locale, |
| new DynamicTypeValueReceiverOnExecutor<>(executor, consumer)); |
| } |
| |
| @NonNull |
| @RestrictTo(Scope.LIBRARY_GROUP) |
| BoundDynamicTypeImpl bindInternal( |
| @NonNull DynamicString stringSource, |
| @NonNull ULocale locale, |
| @NonNull DynamicTypeValueReceiver<String> consumer) { |
| List<DynamicDataNode<?>> resultBuilder = new ArrayList<>(); |
| bindRecursively( |
| stringSource, |
| new DynamicTypeValueReceiverOnExecutor<>(consumer), |
| locale, |
| resultBuilder); |
| return new BoundDynamicTypeImpl(resultBuilder, mDynamicTypesQuotaManager); |
| } |
| |
| @NonNull |
| BoundDynamicTypeImpl bindInternal( |
| @NonNull DynamicBuilders.DynamicInt32 int32Source, |
| @NonNull Executor executor, |
| @NonNull DynamicTypeValueReceiver<Integer> consumer) { |
| return bindInternal( |
| int32Source.toDynamicInt32Proto(), |
| new DynamicTypeValueReceiverOnExecutor<>(executor, consumer)); |
| } |
| |
| @NonNull |
| @RestrictTo(Scope.LIBRARY_GROUP) |
| BoundDynamicTypeImpl bindInternal( |
| @NonNull DynamicInt32 int32Source, |
| @NonNull DynamicTypeValueReceiver<Integer> consumer) { |
| List<DynamicDataNode<?>> resultBuilder = new ArrayList<>(); |
| bindRecursively( |
| int32Source, new DynamicTypeValueReceiverOnExecutor<>(consumer), resultBuilder); |
| return new BoundDynamicTypeImpl(resultBuilder, mDynamicTypesQuotaManager); |
| } |
| |
| @NonNull |
| BoundDynamicTypeImpl bindInternal( |
| @NonNull DynamicBuilders.DynamicFloat floatSource, |
| @NonNull Executor executor, |
| @NonNull DynamicTypeValueReceiver<Float> consumer) { |
| return bindInternal( |
| floatSource.toDynamicFloatProto(), |
| new DynamicTypeValueReceiverOnExecutor<>(executor, consumer)); |
| } |
| |
| @NonNull |
| @RestrictTo(Scope.LIBRARY_GROUP) |
| BoundDynamicTypeImpl bindInternal( |
| @NonNull DynamicFloat floatSource, @NonNull DynamicTypeValueReceiver<Float> consumer) { |
| List<DynamicDataNode<?>> resultBuilder = new ArrayList<>(); |
| bindRecursively( |
| floatSource, new DynamicTypeValueReceiverOnExecutor<>(consumer), resultBuilder); |
| return new BoundDynamicTypeImpl(resultBuilder, mDynamicTypesQuotaManager); |
| } |
| |
| @NonNull |
| BoundDynamicTypeImpl bindInternal( |
| @NonNull DynamicBuilders.DynamicColor colorSource, |
| @NonNull Executor executor, |
| @NonNull DynamicTypeValueReceiver<Integer> consumer) { |
| return bindInternal( |
| colorSource.toDynamicColorProto(), |
| new DynamicTypeValueReceiverOnExecutor<>(executor, consumer)); |
| } |
| |
| @NonNull |
| @RestrictTo(Scope.LIBRARY_GROUP) |
| BoundDynamicTypeImpl bindInternal( |
| @NonNull DynamicColor colorSource, |
| @NonNull DynamicTypeValueReceiver<Integer> consumer) { |
| List<DynamicDataNode<?>> resultBuilder = new ArrayList<>(); |
| bindRecursively( |
| colorSource, new DynamicTypeValueReceiverOnExecutor<>(consumer), resultBuilder); |
| return new BoundDynamicTypeImpl(resultBuilder, mDynamicTypesQuotaManager); |
| } |
| |
| @NonNull |
| BoundDynamicTypeImpl bindInternal( |
| @NonNull DynamicBuilders.DynamicDuration durationSource, |
| @NonNull Executor executor, |
| @NonNull DynamicTypeValueReceiver<Duration> consumer) { |
| return bindInternal( |
| durationSource.toDynamicDurationProto(), |
| new DynamicTypeValueReceiverOnExecutor<>(executor, consumer)); |
| } |
| |
| @NonNull |
| @RestrictTo(Scope.LIBRARY_GROUP) |
| BoundDynamicTypeImpl bindInternal( |
| @NonNull DynamicDuration durationSource, |
| @NonNull DynamicTypeValueReceiver<Duration> consumer) { |
| List<DynamicDataNode<?>> resultBuilder = new ArrayList<>(); |
| bindRecursively( |
| durationSource, new DynamicTypeValueReceiverOnExecutor<>(consumer), resultBuilder); |
| return new BoundDynamicTypeImpl(resultBuilder, mDynamicTypesQuotaManager); |
| } |
| |
| @NonNull |
| BoundDynamicTypeImpl bindInternal( |
| @NonNull DynamicBuilders.DynamicInstant instantSource, |
| @NonNull Executor executor, |
| @NonNull DynamicTypeValueReceiver<Instant> consumer) { |
| return bindInternal( |
| instantSource.toDynamicInstantProto(), |
| new DynamicTypeValueReceiverOnExecutor<>(executor, consumer)); |
| } |
| |
| @NonNull |
| @RestrictTo(Scope.LIBRARY_GROUP) |
| BoundDynamicTypeImpl bindInternal( |
| @NonNull DynamicInstant instantSource, |
| @NonNull DynamicTypeValueReceiver<Instant> consumer) { |
| List<DynamicDataNode<?>> resultBuilder = new ArrayList<>(); |
| bindRecursively( |
| instantSource, new DynamicTypeValueReceiverOnExecutor<>(consumer), resultBuilder); |
| return new BoundDynamicTypeImpl(resultBuilder, mDynamicTypesQuotaManager); |
| } |
| |
| @NonNull |
| @RestrictTo(Scope.LIBRARY_GROUP) |
| BoundDynamicTypeImpl bindInternal( |
| @NonNull DynamicZonedDateTime zdtSource, |
| @NonNull DynamicTypeValueReceiver<ZonedDateTime> consumer) { |
| List<DynamicDataNode<?>> resultBuilder = new ArrayList<>(); |
| bindRecursively( |
| zdtSource, new DynamicTypeValueReceiverOnExecutor<>(consumer), resultBuilder); |
| return new BoundDynamicTypeImpl(resultBuilder, mDynamicTypesQuotaManager); |
| } |
| |
| @NonNull |
| BoundDynamicTypeImpl bindInternal( |
| @NonNull DynamicBuilders.DynamicBool boolSource, |
| @NonNull Executor executor, |
| @NonNull DynamicTypeValueReceiver<Boolean> consumer) { |
| return bindInternal( |
| boolSource.toDynamicBoolProto(), |
| new DynamicTypeValueReceiverOnExecutor<>(executor, consumer)); |
| } |
| |
| @NonNull |
| @RestrictTo(Scope.LIBRARY_GROUP) |
| BoundDynamicTypeImpl bindInternal( |
| @NonNull DynamicBool boolSource, @NonNull DynamicTypeValueReceiver<Boolean> consumer) { |
| List<DynamicDataNode<?>> resultBuilder = new ArrayList<>(); |
| bindRecursively( |
| boolSource, new DynamicTypeValueReceiverOnExecutor<>(consumer), resultBuilder); |
| return new BoundDynamicTypeImpl(resultBuilder, mDynamicTypesQuotaManager); |
| } |
| |
| /** |
| * Same as {@link #bind}, but instead of returning one {@link BoundDynamicType}, all {@link |
| * DynamicDataNode} produced by evaluating given dynamic type are added to the given list. |
| */ |
| private void bindRecursively( |
| @NonNull DynamicString stringSource, |
| @NonNull DynamicTypeValueReceiverWithPreUpdate<String> consumer, |
| @NonNull ULocale locale, |
| @NonNull List<DynamicDataNode<?>> resultBuilder) { |
| DynamicDataNode<?> node; |
| |
| switch (stringSource.getInnerCase()) { |
| case FIXED: |
| node = new FixedStringNode(stringSource.getFixed(), consumer); |
| break; |
| case INT32_FORMAT_OP: |
| { |
| NumberFormatter formatter = |
| new NumberFormatter(stringSource.getInt32FormatOp(), locale); |
| Int32FormatNode int32FormatNode = new Int32FormatNode(formatter, consumer); |
| node = int32FormatNode; |
| bindRecursively( |
| stringSource.getInt32FormatOp().getInput(), |
| int32FormatNode.getIncomingCallback(), |
| resultBuilder); |
| break; |
| } |
| case FLOAT_FORMAT_OP: |
| { |
| NumberFormatter formatter = |
| new NumberFormatter(stringSource.getFloatFormatOp(), locale); |
| FloatFormatNode floatFormatNode = new FloatFormatNode(formatter, consumer); |
| node = floatFormatNode; |
| bindRecursively( |
| stringSource.getFloatFormatOp().getInput(), |
| floatFormatNode.getIncomingCallback(), |
| resultBuilder); |
| break; |
| } |
| case STATE_SOURCE: |
| { |
| DynamicProto.StateStringSource stateSource = stringSource.getStateSource(); |
| node = |
| new StateStringNode( |
| stateSource.getSourceNamespace().isEmpty() |
| ? mStateStore |
| : mPlatformDataStore, |
| stateSource, |
| consumer); |
| break; |
| } |
| case CONDITIONAL_OP: |
| { |
| ConditionalOpNode<String> conditionalNode = new ConditionalOpNode<>(consumer); |
| |
| ConditionalStringOp op = stringSource.getConditionalOp(); |
| bindRecursively( |
| op.getCondition(), |
| conditionalNode.getConditionIncomingCallback(), |
| resultBuilder); |
| bindRecursively( |
| op.getValueIfTrue(), |
| conditionalNode.getTrueValueIncomingCallback(), |
| locale, |
| resultBuilder); |
| bindRecursively( |
| op.getValueIfFalse(), |
| conditionalNode.getFalseValueIncomingCallback(), |
| locale, |
| resultBuilder); |
| |
| node = conditionalNode; |
| break; |
| } |
| case CONCAT_OP: |
| { |
| StringConcatOpNode concatNode = new StringConcatOpNode(consumer); |
| node = concatNode; |
| bindRecursively( |
| stringSource.getConcatOp().getInputLhs(), |
| concatNode.getLhsUpstreamCallback(), |
| locale, |
| resultBuilder); |
| bindRecursively( |
| stringSource.getConcatOp().getInputRhs(), |
| concatNode.getRhsUpstreamCallback(), |
| locale, |
| resultBuilder); |
| break; |
| } |
| case INNER_NOT_SET: |
| throw new IllegalArgumentException("DynamicString has no inner source set"); |
| default: |
| throw new IllegalArgumentException("Unknown DynamicString source type"); |
| } |
| |
| resultBuilder.add(node); |
| } |
| |
| /** |
| * Same as {@link #bind}, but instead of returning one {@link BoundDynamicType}, all {@link |
| * DynamicDataNode} produced by evaluating given dynamic type are added to the given list. |
| */ |
| private void bindRecursively( |
| @NonNull DynamicInt32 int32Source, |
| @NonNull DynamicTypeValueReceiverWithPreUpdate<Integer> consumer, |
| @NonNull List<DynamicDataNode<?>> resultBuilder) { |
| DynamicDataNode<Integer> node; |
| |
| switch (int32Source.getInnerCase()) { |
| case FIXED: |
| node = new FixedInt32Node(int32Source.getFixed(), consumer); |
| break; |
| case PLATFORM_SOURCE: |
| { |
| node = |
| new LegacyPlatformInt32SourceNode( |
| mPlatformDataStore, int32Source.getPlatformSource(), consumer); |
| break; |
| } |
| case ARITHMETIC_OPERATION: |
| { |
| ArithmeticInt32Node arithmeticNode = |
| new ArithmeticInt32Node(int32Source.getArithmeticOperation(), consumer); |
| node = arithmeticNode; |
| |
| bindRecursively( |
| int32Source.getArithmeticOperation().getInputLhs(), |
| arithmeticNode.getLhsUpstreamCallback(), |
| resultBuilder); |
| bindRecursively( |
| int32Source.getArithmeticOperation().getInputRhs(), |
| arithmeticNode.getRhsUpstreamCallback(), |
| resultBuilder); |
| |
| break; |
| } |
| case STATE_SOURCE: |
| { |
| DynamicProto.StateInt32Source stateSource = int32Source.getStateSource(); |
| node = |
| new StateInt32SourceNode( |
| stateSource.getSourceNamespace().isEmpty() |
| ? mStateStore |
| : mPlatformDataStore, |
| stateSource, |
| consumer); |
| break; |
| } |
| case CONDITIONAL_OP: |
| { |
| ConditionalOpNode<Integer> conditionalNode = new ConditionalOpNode<>(consumer); |
| |
| ConditionalInt32Op op = int32Source.getConditionalOp(); |
| bindRecursively( |
| op.getCondition(), |
| conditionalNode.getConditionIncomingCallback(), |
| resultBuilder); |
| bindRecursively( |
| op.getValueIfTrue(), |
| conditionalNode.getTrueValueIncomingCallback(), |
| resultBuilder); |
| bindRecursively( |
| op.getValueIfFalse(), |
| conditionalNode.getFalseValueIncomingCallback(), |
| resultBuilder); |
| |
| node = conditionalNode; |
| break; |
| } |
| case FLOAT_TO_INT: |
| { |
| FloatToInt32Node conversionNode = |
| new FloatToInt32Node(int32Source.getFloatToInt(), consumer); |
| node = conversionNode; |
| |
| bindRecursively( |
| int32Source.getFloatToInt().getInput(), |
| conversionNode.getIncomingCallback(), |
| resultBuilder); |
| break; |
| } |
| case DURATION_PART: |
| { |
| GetDurationPartOpNode durationPartOpNode = |
| new GetDurationPartOpNode(int32Source.getDurationPart(), consumer); |
| node = durationPartOpNode; |
| |
| bindRecursively( |
| int32Source.getDurationPart().getInput(), |
| durationPartOpNode.getIncomingCallback(), |
| resultBuilder); |
| break; |
| } |
| case ZONED_DATE_TIME_PART: |
| { |
| GetZonedDateTimePartOpNode zdtPartOpNode = |
| new GetZonedDateTimePartOpNode( |
| int32Source.getZonedDateTimePart(), consumer); |
| node = zdtPartOpNode; |
| |
| bindRecursively( |
| int32Source.getZonedDateTimePart().getInput(), |
| zdtPartOpNode.getIncomingCallback(), |
| resultBuilder); |
| break; |
| } |
| case ANIMATABLE_FIXED: |
| |
| // We don't have to check if enableAnimations is true, because if it's false and |
| // we didn't have static value set, constructor has put QuotaManager that don't |
| // have any quota, so animations won't be played and they would jump to the end |
| // value. |
| node = |
| new AnimatableFixedInt32Node( |
| int32Source.getAnimatableFixed(), consumer, mAnimationQuotaManager); |
| break; |
| case ANIMATABLE_DYNAMIC: |
| // We don't have to check if enableAnimations is true, because if it's false and |
| // we didn't have static value set, constructor has put QuotaManager that don't |
| // have any quota, so animations won't be played and they would jump to the end |
| // value. |
| AnimatableDynamicInt32 dynamicNode = int32Source.getAnimatableDynamic(); |
| DynamicAnimatedInt32Node animationNode = |
| new DynamicAnimatedInt32Node( |
| consumer, dynamicNode.getAnimationSpec(), mAnimationQuotaManager); |
| node = animationNode; |
| |
| bindRecursively( |
| dynamicNode.getInput(), animationNode.getInputCallback(), resultBuilder); |
| break; |
| case INNER_NOT_SET: |
| throw new IllegalArgumentException("DynamicInt32 has no inner source set"); |
| default: |
| throw new IllegalArgumentException("Unknown DynamicInt32 source type"); |
| } |
| |
| resultBuilder.add(node); |
| } |
| |
| /** |
| * Same as {@link #bind}, but instead of returning one {@link BoundDynamicType}, all {@link |
| * DynamicDataNode} produced by evaluating given dynamic type are added to the given list. |
| */ |
| private void bindRecursively( |
| @NonNull DynamicDuration durationSource, |
| @NonNull DynamicTypeValueReceiverWithPreUpdate<Duration> consumer, |
| @NonNull List<DynamicDataNode<?>> resultBuilder) { |
| DynamicDataNode<?> node; |
| |
| switch (durationSource.getInnerCase()) { |
| case BETWEEN: |
| BetweenInstancesNode betweenInstancesNode = new BetweenInstancesNode(consumer); |
| node = betweenInstancesNode; |
| bindRecursively( |
| durationSource.getBetween().getStartInclusive(), |
| betweenInstancesNode.getLhsUpstreamCallback(), |
| resultBuilder); |
| bindRecursively( |
| durationSource.getBetween().getEndExclusive(), |
| betweenInstancesNode.getRhsUpstreamCallback(), |
| resultBuilder); |
| break; |
| case FIXED: |
| node = new FixedDurationNode(durationSource.getFixed(), consumer); |
| break; |
| case CONDITIONAL_OP: |
| ConditionalOpNode<Duration> conditionalNode = new ConditionalOpNode<>(consumer); |
| |
| ConditionalDurationOp op = durationSource.getConditionalOp(); |
| bindRecursively( |
| op.getCondition(), |
| conditionalNode.getConditionIncomingCallback(), |
| resultBuilder); |
| bindRecursively( |
| op.getValueIfTrue(), |
| conditionalNode.getTrueValueIncomingCallback(), |
| resultBuilder); |
| bindRecursively( |
| op.getValueIfFalse(), |
| conditionalNode.getFalseValueIncomingCallback(), |
| resultBuilder); |
| |
| node = conditionalNode; |
| break; |
| case STATE_SOURCE: |
| { |
| DynamicProto.StateDurationSource stateSource = durationSource.getStateSource(); |
| node = |
| new StateDurationSourceNode( |
| stateSource.getSourceNamespace().isEmpty() |
| ? mStateStore |
| : mPlatformDataStore, |
| stateSource, |
| consumer); |
| break; |
| } |
| case INNER_NOT_SET: |
| throw new IllegalArgumentException("DynamicDuration has no inner source set"); |
| default: |
| throw new IllegalArgumentException("Unknown DynamicDuration source type"); |
| } |
| |
| resultBuilder.add(node); |
| } |
| |
| /** |
| * Same as {@link #bind}, but instead of returning one {@link BoundDynamicType}, all {@link |
| * DynamicDataNode} produced by evaluating given dynamic type are added to the given list. |
| */ |
| private void bindRecursively( |
| @NonNull DynamicZonedDateTime zdtSource, |
| @NonNull DynamicTypeValueReceiverWithPreUpdate<ZonedDateTime> consumer, |
| @NonNull List<DynamicDataNode<?>> resultBuilder) { |
| DynamicDataNode<?> node; |
| |
| switch (zdtSource.getInnerCase()) { |
| case INSTANT_TO_ZONED_DATE_TIME: |
| { |
| InstantToZonedDateTimeOpNode conversionNode = |
| new InstantToZonedDateTimeOpNode( |
| zdtSource.getInstantToZonedDateTime(), consumer); |
| node = conversionNode; |
| |
| bindRecursively( |
| zdtSource.getInstantToZonedDateTime().getInstant(), |
| conversionNode.getIncomingCallback(), |
| resultBuilder); |
| break; |
| } |
| case INNER_NOT_SET: |
| throw new IllegalArgumentException("DynamicZonedDateTime has no inner source set"); |
| default: |
| throw new IllegalArgumentException("Unknown DynamicZonedDateTime source type"); |
| } |
| |
| resultBuilder.add(node); |
| } |
| |
| /** |
| * Same as {@link #bind}, but instead of returning one {@link BoundDynamicType}, all {@link |
| * DynamicDataNode} produced by evaluating given dynamic type are added to the given list. |
| */ |
| private void bindRecursively( |
| @NonNull DynamicInstant instantSource, |
| @NonNull DynamicTypeValueReceiverWithPreUpdate<Instant> consumer, |
| @NonNull List<DynamicDataNode<?>> resultBuilder) { |
| DynamicDataNode<?> node; |
| |
| switch (instantSource.getInnerCase()) { |
| case FIXED: |
| node = new FixedInstantNode(instantSource.getFixed(), consumer); |
| break; |
| case PLATFORM_SOURCE: |
| node = new PlatformTimeSourceNode(mTimeDataSource, consumer); |
| break; |
| case CONDITIONAL_OP: |
| ConditionalOpNode<Instant> conditionalNode = new ConditionalOpNode<>(consumer); |
| |
| ConditionalInstantOp op = instantSource.getConditionalOp(); |
| bindRecursively( |
| op.getCondition(), |
| conditionalNode.getConditionIncomingCallback(), |
| resultBuilder); |
| bindRecursively( |
| op.getValueIfTrue(), |
| conditionalNode.getTrueValueIncomingCallback(), |
| resultBuilder); |
| bindRecursively( |
| op.getValueIfFalse(), |
| conditionalNode.getFalseValueIncomingCallback(), |
| resultBuilder); |
| |
| node = conditionalNode; |
| break; |
| |
| case STATE_SOURCE: |
| { |
| DynamicProto.StateInstantSource stateSource = instantSource.getStateSource(); |
| node = |
| new StateInstantSourceNode( |
| stateSource.getSourceNamespace().isEmpty() |
| ? mStateStore |
| : mPlatformDataStore, |
| stateSource, |
| consumer); |
| break; |
| } |
| case INNER_NOT_SET: |
| throw new IllegalArgumentException("DynamicInstant has no inner source set"); |
| default: |
| throw new IllegalArgumentException("Unknown DynamicInstant source type"); |
| } |
| |
| resultBuilder.add(node); |
| } |
| |
| /** |
| * Same as {@link #bind}, but instead of returning one {@link BoundDynamicType}, all {@link |
| * DynamicDataNode} produced by evaluating given dynamic type are added to the given list. |
| */ |
| private void bindRecursively( |
| @NonNull DynamicFloat floatSource, |
| @NonNull DynamicTypeValueReceiverWithPreUpdate<Float> consumer, |
| @NonNull List<DynamicDataNode<?>> resultBuilder) { |
| DynamicDataNode<?> node; |
| |
| switch (floatSource.getInnerCase()) { |
| case FIXED: |
| node = new FixedFloatNode(floatSource.getFixed(), consumer); |
| break; |
| case STATE_SOURCE: |
| { |
| DynamicProto.StateFloatSource stateSource = floatSource.getStateSource(); |
| node = |
| new StateFloatSourceNode( |
| stateSource.getSourceNamespace().isEmpty() |
| ? mStateStore |
| : mPlatformDataStore, |
| stateSource, |
| consumer); |
| break; |
| } |
| case ARITHMETIC_OPERATION: |
| { |
| ArithmeticFloatNode arithmeticNode = |
| new ArithmeticFloatNode(floatSource.getArithmeticOperation(), consumer); |
| node = arithmeticNode; |
| |
| bindRecursively( |
| floatSource.getArithmeticOperation().getInputLhs(), |
| arithmeticNode.getLhsUpstreamCallback(), |
| resultBuilder); |
| bindRecursively( |
| floatSource.getArithmeticOperation().getInputRhs(), |
| arithmeticNode.getRhsUpstreamCallback(), |
| resultBuilder); |
| |
| break; |
| } |
| case INT32_TO_FLOAT_OPERATION: |
| { |
| Int32ToFloatNode toFloatNode = new Int32ToFloatNode(consumer); |
| node = toFloatNode; |
| |
| bindRecursively( |
| floatSource.getInt32ToFloatOperation().getInput(), |
| toFloatNode.getIncomingCallback(), |
| resultBuilder); |
| break; |
| } |
| case CONDITIONAL_OP: |
| { |
| ConditionalOpNode<Float> conditionalNode = new ConditionalOpNode<>(consumer); |
| |
| ConditionalFloatOp op = floatSource.getConditionalOp(); |
| bindRecursively( |
| op.getCondition(), |
| conditionalNode.getConditionIncomingCallback(), |
| resultBuilder); |
| bindRecursively( |
| op.getValueIfTrue(), |
| conditionalNode.getTrueValueIncomingCallback(), |
| resultBuilder); |
| bindRecursively( |
| op.getValueIfFalse(), |
| conditionalNode.getFalseValueIncomingCallback(), |
| resultBuilder); |
| |
| node = conditionalNode; |
| break; |
| } |
| case ANIMATABLE_FIXED: |
| // We don't have to check if enableAnimations is true, because if it's false and |
| // we didn't have static value set, constructor has put QuotaManager that don't |
| // have any quota, so animations won't be played and they would jump to the end |
| // value. |
| node = |
| new AnimatableFixedFloatNode( |
| floatSource.getAnimatableFixed(), consumer, mAnimationQuotaManager); |
| break; |
| case ANIMATABLE_DYNAMIC: |
| // We don't have to check if enableAnimations is true, because if it's false and |
| // we didn't have static value set, constructor has put QuotaManager that don't |
| // have any quota, so animations won't be played and they would jump to the end |
| // value. |
| AnimatableDynamicFloat dynamicNode = floatSource.getAnimatableDynamic(); |
| DynamicAnimatedFloatNode animationNode = |
| new DynamicAnimatedFloatNode( |
| consumer, dynamicNode.getAnimationSpec(), mAnimationQuotaManager); |
| node = animationNode; |
| |
| bindRecursively( |
| dynamicNode.getInput(), animationNode.getInputCallback(), resultBuilder); |
| break; |
| |
| case INNER_NOT_SET: |
| throw new IllegalArgumentException("DynamicFloat has no inner source set"); |
| default: |
| throw new IllegalArgumentException("Unknown DynamicFloat source type"); |
| } |
| |
| resultBuilder.add(node); |
| } |
| |
| /** |
| * Same as {@link #bind}, but instead of returning one {@link BoundDynamicType}, all {@link |
| * DynamicDataNode} produced by evaluating given dynamic type are added to the given list. |
| */ |
| private void bindRecursively( |
| @NonNull DynamicColor colorSource, |
| @NonNull DynamicTypeValueReceiverWithPreUpdate<Integer> consumer, |
| @NonNull List<DynamicDataNode<?>> resultBuilder) { |
| DynamicDataNode<?> node; |
| |
| switch (colorSource.getInnerCase()) { |
| case FIXED: |
| node = new FixedColorNode(colorSource.getFixed(), consumer); |
| break; |
| case STATE_SOURCE: |
| DynamicProto.StateColorSource stateSource = colorSource.getStateSource(); |
| node = |
| new StateColorSourceNode( |
| stateSource.getSourceNamespace().isEmpty() |
| ? mStateStore |
| : mPlatformDataStore, |
| stateSource, |
| consumer); |
| break; |
| case ANIMATABLE_FIXED: |
| // We don't have to check if enableAnimations is true, because if it's false and |
| // we didn't have static value set, constructor has put QuotaManager that don't |
| // have any quota, so animations won't be played and they would jump to the end |
| // value. |
| node = |
| new AnimatableFixedColorNode( |
| colorSource.getAnimatableFixed(), consumer, mAnimationQuotaManager); |
| break; |
| case ANIMATABLE_DYNAMIC: |
| // We don't have to check if enableAnimations is true, because if it's false and |
| // we didn't have static value set, constructor has put QuotaManager that don't |
| // have any quota, so animations won't be played and they would jump to the end |
| // value. |
| AnimatableDynamicColor dynamicNode = colorSource.getAnimatableDynamic(); |
| DynamicAnimatedColorNode animationNode = |
| new DynamicAnimatedColorNode( |
| consumer, dynamicNode.getAnimationSpec(), mAnimationQuotaManager); |
| node = animationNode; |
| |
| bindRecursively( |
| dynamicNode.getInput(), animationNode.getInputCallback(), resultBuilder); |
| break; |
| case CONDITIONAL_OP: |
| ConditionalOpNode<Integer> conditionalNode = new ConditionalOpNode<>(consumer); |
| |
| ConditionalColorOp op = colorSource.getConditionalOp(); |
| bindRecursively( |
| op.getCondition(), |
| conditionalNode.getConditionIncomingCallback(), |
| resultBuilder); |
| bindRecursively( |
| op.getValueIfTrue(), |
| conditionalNode.getTrueValueIncomingCallback(), |
| resultBuilder); |
| bindRecursively( |
| op.getValueIfFalse(), |
| conditionalNode.getFalseValueIncomingCallback(), |
| resultBuilder); |
| |
| node = conditionalNode; |
| break; |
| case INNER_NOT_SET: |
| throw new IllegalArgumentException("DynamicColor has no inner source set"); |
| default: |
| throw new IllegalArgumentException("Unknown DynamicColor source type"); |
| } |
| |
| resultBuilder.add(node); |
| } |
| |
| /** |
| * Same as {@link #bind}, but instead of returning one {@link BoundDynamicType}, all {@link |
| * DynamicDataNode} produced by evaluating given dynamic type are added to the given list. |
| */ |
| private void bindRecursively( |
| @NonNull DynamicBool boolSource, |
| @NonNull DynamicTypeValueReceiverWithPreUpdate<Boolean> consumer, |
| @NonNull List<DynamicDataNode<?>> resultBuilder) { |
| DynamicDataNode<?> node; |
| |
| switch (boolSource.getInnerCase()) { |
| case FIXED: |
| node = new FixedBoolNode(boolSource.getFixed(), consumer); |
| break; |
| case STATE_SOURCE: |
| { |
| DynamicProto.StateBoolSource stateSource = boolSource.getStateSource(); |
| node = |
| new StateBoolNode( |
| stateSource.getSourceNamespace().isEmpty() |
| ? mStateStore |
| : mPlatformDataStore, |
| stateSource, |
| consumer); |
| break; |
| } |
| case INT32_COMPARISON: |
| { |
| ComparisonInt32Node compNode = |
| new ComparisonInt32Node(boolSource.getInt32Comparison(), consumer); |
| node = compNode; |
| |
| bindRecursively( |
| boolSource.getInt32Comparison().getInputLhs(), |
| compNode.getLhsUpstreamCallback(), |
| resultBuilder); |
| bindRecursively( |
| boolSource.getInt32Comparison().getInputRhs(), |
| compNode.getRhsUpstreamCallback(), |
| resultBuilder); |
| |
| break; |
| } |
| case LOGICAL_OP: |
| { |
| LogicalBoolOp logicalNode = |
| new LogicalBoolOp(boolSource.getLogicalOp(), consumer); |
| node = logicalNode; |
| |
| bindRecursively( |
| boolSource.getLogicalOp().getInputLhs(), |
| logicalNode.getLhsUpstreamCallback(), |
| resultBuilder); |
| bindRecursively( |
| boolSource.getLogicalOp().getInputRhs(), |
| logicalNode.getRhsUpstreamCallback(), |
| resultBuilder); |
| |
| break; |
| } |
| case NOT_OP: |
| { |
| NotBoolOp notNode = new NotBoolOp(consumer); |
| node = notNode; |
| bindRecursively( |
| boolSource.getNotOp().getInput(), |
| notNode.getIncomingCallback(), |
| resultBuilder); |
| break; |
| } |
| case FLOAT_COMPARISON: |
| { |
| ComparisonFloatNode compNode = |
| new ComparisonFloatNode(boolSource.getFloatComparison(), consumer); |
| node = compNode; |
| |
| bindRecursively( |
| boolSource.getFloatComparison().getInputLhs(), |
| compNode.getLhsUpstreamCallback(), |
| resultBuilder); |
| bindRecursively( |
| boolSource.getFloatComparison().getInputRhs(), |
| compNode.getRhsUpstreamCallback(), |
| resultBuilder); |
| |
| break; |
| } |
| case INNER_NOT_SET: |
| throw new IllegalArgumentException("DynamicBool has no inner source set"); |
| default: |
| throw new IllegalArgumentException("Unknown DynamicBool source type"); |
| } |
| |
| resultBuilder.add(node); |
| } |
| |
| /** |
| * Wraps {@link DynamicTypeValueReceiver} and executes its methods on the given {@link |
| * Executor}. |
| */ |
| private static class DynamicTypeValueReceiverOnExecutor<T> |
| implements DynamicTypeValueReceiverWithPreUpdate<T> { |
| |
| @NonNull private final Executor mExecutor; |
| @NonNull private final DynamicTypeValueReceiver<T> mConsumer; |
| |
| DynamicTypeValueReceiverOnExecutor(@NonNull DynamicTypeValueReceiver<T> consumer) { |
| this(Runnable::run, consumer); |
| } |
| |
| DynamicTypeValueReceiverOnExecutor( |
| @NonNull Executor executor, @NonNull DynamicTypeValueReceiver<T> consumer) { |
| this.mConsumer = consumer; |
| this.mExecutor = executor; |
| } |
| |
| /** This method is noop in this class. */ |
| @Override |
| @SuppressWarnings("ExecutorTaskName") |
| public void onPreUpdate() {} |
| |
| @Override |
| @SuppressWarnings("ExecutorTaskName") |
| public void onData(@NonNull T newData) { |
| mExecutor.execute(() -> mConsumer.onData(newData)); |
| } |
| |
| @Override |
| @SuppressWarnings("ExecutorTaskName") |
| public void onInvalidated() { |
| mExecutor.execute(mConsumer::onInvalidated); |
| } |
| } |
| } |