public static <FnT extends DoFn<?, ?>> DoFnSignature getSignature(Class<FnT> fn) {
    return signatureCache.computeIfAbsent(fn, DoFnSignatures::parseSignature);
  }
@Test
  public void testBasicDoFnAllParameters() throws Exception {
    DoFnSignature sig =
        DoFnSignatures.getSignature(
            new DoFn<String, String>() {
              @ProcessElement
              public void process(
                  @Element String element,
                  @Timestamp Instant timestamp,
                  BoundedWindow window,
                  PaneInfo paneInfo,
                  OutputReceiver<String> receiver,
                  PipelineOptions options,
                  @SideInput("tag1") String input1,
                  @SideInput("tag2") Integer input2,
                  BundleFinalizer bundleFinalizer) {}
            }.getClass());

    assertThat(sig.processElement().extraParameters().size(), equalTo(9));
    assertThat(sig.processElement().extraParameters().get(0), instanceOf(ElementParameter.class));
    assertThat(sig.processElement().extraParameters().get(1), instanceOf(TimestampParameter.class));
    assertThat(sig.processElement().extraParameters().get(2), instanceOf(WindowParameter.class));
    assertThat(sig.processElement().extraParameters().get(3), instanceOf(PaneInfoParameter.class));
    assertThat(
        sig.processElement().extraParameters().get(4), instanceOf(OutputReceiverParameter.class));
    assertThat(
        sig.processElement().extraParameters().get(5), instanceOf(PipelineOptionsParameter.class));
    assertThat(sig.processElement().extraParameters().get(6), instanceOf(SideInputParameter.class));
    assertThat(sig.processElement().extraParameters().get(7), instanceOf(SideInputParameter.class));
    assertThat(
        sig.processElement().extraParameters().get(8), instanceOf(BundleFinalizerParameter.class));
  }