public static <FnT extends DoFn<?, ?>> DoFnSignature getSignature(Class<FnT> fn) {
    return signatureCache.computeIfAbsent(fn, DoFnSignatures::parseSignature);
  }
@Test
  public void testDeclAndUsageOfStateInSuperclass() throws Exception {
    class DoFnOverridingAbstractStateUse extends DoFnDeclaringStateAndAbstractUse {

      @Override
      public void processWithState(ProcessContext c, ValueState<String> state) {}
    }

    DoFnSignature sig =
        DoFnSignatures.getSignature(new DoFnOverridingAbstractStateUse().getClass());

    assertThat(sig.stateDeclarations().size(), equalTo(1));
    assertThat(sig.processElement().extraParameters().size(), equalTo(2));

    DoFnSignature.StateDeclaration decl =
        sig.stateDeclarations().get(DoFnOverridingAbstractStateUse.STATE_ID);
    StateParameter stateParam = (StateParameter) sig.processElement().extraParameters().get(1);
    assertFalse(stateParam.alwaysFetched());
    assertThat(
        decl.field(),
        equalTo(DoFnDeclaringStateAndAbstractUse.class.getDeclaredField("myStateSpec")));

    // The method we pull out is the superclass method; this is what allows validation to remain
    // simple. The later invokeDynamic instruction causes it to invoke the actual implementation.
    assertThat(stateParam.referent(), equalTo(decl));
  }