/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.beam.runners.direct.portable;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.beam.runners.core.construction.PTransformTranslation;
import org.apache.beam.runners.core.construction.graph.PipelineNode.PTransformNode;
import org.apache.beam.sdk.transforms.PTransform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A {@link TransformEvaluatorFactory} that delegates to primitive {@link TransformEvaluatorFactory}
 * implementations based on the type of {@link PTransform} of the application.
 */
class TransformEvaluatorRegistry {
  private static final Logger LOG = LoggerFactory.getLogger(TransformEvaluatorRegistry.class);

  // the TransformEvaluatorFactories can construct instances of all generic types of transform,
  // so all instances of a primitive can be handled with the same evaluator factory.
  private final Map<String, TransformEvaluatorFactory> factories;

  private final AtomicBoolean finished = new AtomicBoolean(false);

  private TransformEvaluatorRegistry(
      @SuppressWarnings("rawtypes") Map<String, TransformEvaluatorFactory> factories) {
    this.factories = factories;
  }

  public <InputT> TransformEvaluator<InputT> forApplication(
      PTransformNode application, CommittedBundle<?> inputBundle) throws Exception {
    checkState(
        !finished.get(), "Tried to get an evaluator for a finished TransformEvaluatorRegistry");

    String urn = PTransformTranslation.urnForTransformOrNull(application.getTransform());

    TransformEvaluatorFactory factory =
        checkNotNull(factories.get(urn), "No evaluator for PTransform \"%s\"", urn);
    return factory.forApplication(application, inputBundle);
  }

  public void cleanup() throws Exception {
    Collection<Exception> thrownInCleanup = new ArrayList<>();
    for (TransformEvaluatorFactory factory : factories.values()) {
      try {
        factory.cleanup();
      } catch (Exception e) {
        if (e instanceof InterruptedException) {
          Thread.currentThread().interrupt();
        }
        thrownInCleanup.add(e);
      }
    }
    finished.set(true);
    if (!thrownInCleanup.isEmpty()) {
      LOG.error("Exceptions {} thrown while cleaning up evaluators", thrownInCleanup);
      Exception toThrow = null;
      for (Exception e : thrownInCleanup) {
        if (toThrow == null) {
          toThrow = e;
        } else {
          toThrow.addSuppressed(e);
        }
      }
      throw toThrow;
    }
  }
}

@@ -974,24 +974,71 @@
 heckState;%0A%0A
+import com.google.common.collect.ImmutableMap;%0A
 import java.
@@ -1154,16 +1154,79 @@
 oolean;%0A
+import org.apache.beam.model.pipeline.v1.RunnerApi.Components;%0A
 import o
@@ -1348,35 +1348,316 @@
 aph.
-PipelineNode.PTransformNode
+ExecutableStage;%0Aimport org.apache.beam.runners.core.construction.graph.PipelineNode.PCollectionNode;%0Aimport org.apache.beam.runners.core.construction.graph.PipelineNode.PTransformNode;%0Aimport org.apache.beam.runners.direct.ExecutableGraph;%0Aimport org.apache.beam.runners.fnexecution.control.JobBundleFactory
 ;%0Aim
@@ -2081,16 +2081,1211 @@
 lass);%0A%0A
+  static TransformEvaluatorRegistry portableRegistry(%0A      Components components,%0A      ExecutableGraph%3CPTransformNode, PCollectionNode%3E graph,%0A      BundleFactory bundleFactory,%0A      JobBundleFactory jobBundleFactory,%0A      StateAndTimerProvider stateAndTimerProvider) %7B%0A    return new TransformEvaluatorRegistry(%0A        ImmutableMap.%3CString, TransformEvaluatorFactory%3Ebuilder()%0A            .put(%0A                PTransformTranslation.IMPULSE_TRANSFORM_URN,%0A                new ImpulseEvaluatorFactory(bundleFactory, graph))%0A            .put(%0A                PTransformTranslation.FLATTEN_TRANSFORM_URN,%0A                new FlattenEvaluatorFactory(bundleFactory, graph))%0A            .put(%0A                DirectGroupByKey.DIRECT_GBKO_URN,%0A                new GroupByKeyOnlyEvaluatorFactory(components, bundleFactory, graph))%0A            .put(%0A                DirectGroupByKey.DIRECT_GABW_URN,%0A                new GroupAlsoByWindowEvaluatorFactory(%0A                    bundleFactory, graph, components, stateAndTimerProvider))%0A            .put(%0A                ExecutableStage.URN,%0A                new RemoteStageEvaluatorFactory(bundleFactory, jobBundleFactory))%0A            .build());%0A  %7D%0A%0A
   // the

Add a Portable TransformEvaluatorRegistry