code: package morfologik.tools;
  
  import java.io.BufferedInputStream;
  import java.io.InputStream;
  import java.nio.file.Files;
  import java.nio.file.Path;
  import java.util.Locale;
  
  import morfologik.fsa.CFSA;
  import morfologik.fsa.CFSA2;
  import morfologik.fsa.FSA;
  import morfologik.fsa.FSA5;
  
  import com.beust.jcommander.Parameter;
  import com.beust.jcommander.Parameters;
  
  /**
   * Print extra information about a compiled automaton file.
   */
  @Parameters(
      commandNames = "fsa_info",
      commandDescription = "Print extra information about a compiled automaton file.")
  public class FSAInfo extends CliTool {
    @Parameter(
        names = {"-i", "--input"},
        description = "The input automaton.", 
        required = true,
        validateValueWith = ValidateFileExists.class)
    private Path input;
  
    FSAInfo() {
    }
  
    public FSAInfo(Path input) {
      this.input = checkNotNull(input);
    }
  
    @Override
    public ExitStatus call() throws Exception {
      final FSA fsa;
      try (InputStream is = new BufferedInputStream(Files.newInputStream(input))) {
        fsa = FSA.read(is);
      }
  
      printf("%-25s : %s", "FSA implementation", fsa.getClass().getName());
      printf("%-25s : %s", "Compiled with flags", fsa.getFlags().toString());
  
      final morfologik.fsa.builders.FSAInfo info = new morfologik.fsa.builders.FSAInfo(fsa);
      printf("%-25s : %,d", "Number of arcs (merged)", info.arcsCount);
      printf("%-25s : %,d", "Number of arcs (total)", info.arcsCountTotal);
      printf("%-25s : %,d", "Number of nodes", info.nodeCount);
      printf("%-25s : %,d", "Number of final states", info.finalStatesCount);
      printf("");
  
      if (fsa instanceof FSA5) {
        FSA5 fsa5 = (FSA5) fsa;
        printf("%-25s : %d", "Goto length (GTL)", fsa5.gtl);
        printf("%-25s : %d", "Node extra data", fsa5.nodeDataLength);
        printf("%-25s : %s", "Annotation separator", byteAsChar(fsa5.annotation));
        printf("%-25s : %s", "Filler character", byteAsChar(fsa5.filler));
      }
  
      if (fsa instanceof CFSA) {
        CFSA cfsa = (CFSA) fsa;
        printf("%-25s : %d", "Goto length (GTL)", cfsa.gtl);
        printf("%-25s : %d", "Node extra data", cfsa.nodeDataLength);
      }
  
      if (fsa instanceof CFSA2) {
        CFSA2 cfsa2 = (CFSA2) fsa;
  
        byte [] labelMapping = cfsa2.labelMapping;
        if (labelMapping != null && labelMapping.length > 0) {
          printf("%-25s :", "Label mapping");
          for (int i = 0; i < labelMapping.length; i++) {
            printf("%-25s   %2d -> %s", "", i, byteAsChar(labelMapping[i]));
          }
        }
      }
  
      return ExitStatus.SUCCESS;
    }
  
    /**
     * Convert a byte to an informative string.
     */
    static String byteAsChar(byte v) {
      int chr = v & 0xff;
      return String.format(Locale.ROOT, 
          "%s (0x%02x)",
          (Character.isWhitespace(chr) || chr > 127) ? "[non-printable]" : Character.toString((char) chr),
          v & 0xFF);
    }
  
    public static void main(String[] args) {
      main(args, new FSAInfo());
    }
  }
  