public void printFields(PrintStream ps, String prefix) {
    ps.println(prefix + " sbSig: \"" + getSbSigAsString() + "\"");
    ps.println(prefix + " sbBlkSize: " + getSbBlkSize());
    ps.println(prefix + " sbBlkCount: " + getSbBlkCount());
    ps.println(prefix + " sbDevType: " + getSbDevType());
    ps.println(prefix + " sbDevId: " + getSbDevId());
    ps.println(prefix + " sbData: " + getSbData());
    ps.println(prefix + " sbDrvrCount: " + getSbDrvrCount());
    ps.println(prefix + " entries (" + entries.length + " elements):");
    for (int i = 0; i < entries.length; ++i) {
      ps.println(prefix + "  entries[" + i + "]: ");
      entries[i].print(ps, prefix + "   ");
    }
    if (entries.length == 0) ps.println(prefix + "  <empty>");
    ps.println(prefix + " ddPad:");
    ps.print(prefix + "  byte[" + ddPad.length + "] {");
    for (int i = 0; i < ddPad.length; ++i) {
      if (i % 16 == 0) {
        ps.println();
        ps.print(prefix + "  ");
      }
      ps.print(" " + Util.toHexStringBE(ddPad[i]));
    }
    ps.println();
    ps.println(prefix + "  }");

    try {
      byte[] md5sum = MessageDigest.getInstance("MD5").digest(ddPad);
      ps.println(prefix + "  MD5: " + Util.byteArrayToHexString(md5sum));
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    }
  }
  public VisualizeUnicodeNormalization() {
    super("HFS+ Unicode Decomposition Table");
    JPanel mainPanel = new JPanel();
    JScrollPane mainPanelScroller =
        new JScrollPane(
            mainPanel,
            ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
            ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
    mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
    mainPanelScroller.getVerticalScrollBar().setUnitIncrement(20);

    UnicodeNormalizationToolkit unt = UnicodeNormalizationToolkit.getDefaultInstance();
    Map<Character, char[]> table = unt.getDecompositionTable();

    StringBuilder sb = new StringBuilder();
    Comparator<Map.Entry<Character, char[]>> cmp =
        new Comparator<Map.Entry<Character, char[]>>() {

          public int compare(Map.Entry<Character, char[]> o1, Map.Entry<Character, char[]> o2) {
            return o1.getKey().compareTo(o2.getKey());
          }

          @Override
          public boolean equals(Object obj) {
            return super.equals(obj);
          }
        };
    TreeSet<Map.Entry<Character, char[]>> ts = new TreeSet<Map.Entry<Character, char[]>>(cmp);
    for (Map.Entry<Character, char[]> ent : table.entrySet()) ts.add(ent);
    // ts.addAll(table.entrySet());
    for (Map.Entry<Character, char[]> ent : ts) {
      Character key = ent.getKey();
      char[] value = ent.getValue();
      sb.append(Util.toHexStringBE(key.charValue()));
      sb.append(": \" ");
      sb.append(key.toString());
      sb.append(" \" -> \" ");
      sb.append(value[0]);
      for (int i = 1; i < value.length; ++i) {
        sb.append(" \", \" ");
        sb.append(value[i]);
      }
      sb.append(" \"");
      JLabel cur = new JLabel(sb.toString());
      cur.setFont(new java.awt.Font("Monospaced", 0, 20));
      mainPanel.add(cur);
      sb.setLength(0);
    }

    add(mainPanelScroller, BorderLayout.CENTER);
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    pack();
    setLocationRelativeTo(null);
  }
  public static void main(String[] args) {
    final boolean verbose;
    final String fsPath;

    if (args.length == 2 && args[0].equals("-v")) {
      verbose = true;
      fsPath = args[1];
    } else if (args.length == 1) {
      verbose = false;
      fsPath = args[0];
    } else {
      System.err.println("usage: ScanDecmpfs [-v] <device|file>");
      System.err.println();
      System.err.println(
          "    Scans an HFS+/HFSX volume for decmpfs " + "compressed files and prints the");
      System.err.println("    file's CNID, compression type and " + "decompressed size.");
      System.err.println(
          "    If '-v' is supplied, the path of the " + "compressed file is also resolved and");
      System.err.println("    printed after the decompressed size.");
      System.exit(1);
      return;
    }

    final ReadableRandomAccessStream fsStream =
        ReadableWin32FileStream.isSystemSupported()
            ? new ReadableWin32FileStream(fsPath)
            : new ReadableFileStream(fsPath);

    final FileSystemHandlerFactory fsHandlerFactory;
    switch (HFSCommonFileSystemRecognizer.detectFileSystem(fsStream, 0)) {
      case HFS_WRAPPED_HFS_PLUS:
      case HFS_PLUS:
        fsHandlerFactory = FileSystemMajorType.APPLE_HFS_PLUS.createDefaultHandlerFactory();
        break;
      case HFSX:
        fsHandlerFactory = FileSystemMajorType.APPLE_HFSX.createDefaultHandlerFactory();
        break;
      default:
        System.err.println("No HFS+/HFSX filesystem detected.");
        System.exit(1);
        return;
    }

    final FileSystemHandler fsHandlerGeneric =
        fsHandlerFactory.createHandler(new ReadableStreamDataLocator(fsStream));
    if (!(fsHandlerGeneric instanceof HFSPlusFileSystemHandler)) {
      System.err.println(
          "Unexpected: File system handler object is "
              + "not of HFSPlusFileSystemHandler class (class: "
              + fsHandlerGeneric.getClass()
              + ").");
      System.exit(1);
      return;
    }

    final HFSPlusFileSystemHandler fsHandler = (HFSPlusFileSystemHandler) fsHandlerGeneric;
    final AttributesFile attributesFile = fsHandler.getFSView().getAttributesFile();

    LinkedList<Long> nodeQueue = new LinkedList<Long>();
    nodeQueue.addLast(attributesFile.getRootNodeNumber());

    /* Depth-first search for "com.apple.decmpfs" attribute records. */
    while (!nodeQueue.isEmpty()) {
      long curNodeNumber = nodeQueue.removeFirst();
      CommonBTNode curNode = attributesFile.getNode(curNodeNumber);

      if (curNode instanceof CommonHFSAttributesIndexNode) {
        CommonHFSAttributesIndexNode indexNode = (CommonHFSAttributesIndexNode) curNode;

        List<CommonBTIndexRecord<CommonHFSAttributesKey>> records = indexNode.getBTKeyedRecords();
        ListIterator<CommonBTIndexRecord<CommonHFSAttributesKey>> it =
            records.listIterator(records.size());

        /* For the search to be depth first, add elements in reverse
         * order. */
        while (it.hasPrevious()) {
          nodeQueue.addFirst(it.previous().getIndex());
        }
      } else if (curNode instanceof CommonHFSAttributesLeafNode) {
        final CommonHFSAttributesLeafNode leafNode = (CommonHFSAttributesLeafNode) curNode;
        for (CommonHFSAttributesLeafRecord rec : leafNode.getLeafRecords()) {
          final CommonHFSAttributesKey k = rec.getKey();
          if (!new String(k.getAttrName(), 0, k.getAttrNameLen()).equals("com.apple.decmpfs")) {
            continue;
          } else if (k.getStartBlock() != 0) {
            System.err.println(
                "[WARNING] "
                    + k.getFileID().toLong()
                    + " has "
                    + "com.apple.decmpfs attribute with non-0 "
                    + "start block ("
                    + k.getStartBlock()
                    + "). "
                    + "Skipping...");
            continue;
          }

          final HFSPlusAttributesLeafRecordData data = rec.getRecordData();
          if (!(data instanceof HFSPlusAttributesData)) {
            System.err.println(
                "[WARNING] "
                    + k.getFileID().toLong()
                    + " has "
                    + "com.apple.decmpfs attribute without inline "
                    + "data ("
                    + data.getRecordTypeAsString()
                    + "). Skipping...");
            continue;
          }

          final DecmpfsHeader header =
              new DecmpfsHeader(((HFSPlusAttributesData) data).getAttrData(), 0);
          if (header.getMagic() != DecmpfsHeader.MAGIC) {
            System.err.println(
                "[WARNING] "
                    + k.getFileID().toLong()
                    + " has "
                    + "com.apple.decmpfs attribute with "
                    + "mismatching magic (expected: 0x"
                    + Util.toHexStringBE((int) DecmpfsHeader.MAGIC)
                    + ", actual: 0x"
                    + Util.toHexStringBE(header.getRawMagic())
                    + "). Skipping...");
            continue;
          }

          final StringBuilder pathBuilder;
          if (verbose) {
            pathBuilder = new StringBuilder();

            boolean firstComponent = true;
            for (CommonHFSCatalogLeafRecord pathComponent :
                fsHandler.getFSView().getCatalogFile().getPathTo(k.getFileID())) {
              /* Skip name of root directory. */
              if (!firstComponent) {
                final char[] nodeName =
                    fsHandler
                        .getFSView()
                        .decodeString(pathComponent.getKey().getNodeName())
                        .toCharArray();

                for (int i = 0; i < nodeName.length; ++i) {
                  /* '/' transformed into ':' and vice versa.
                   * This is part of the POSIX-translation of
                   * filenames in HFS+ (original Mac OS had
                   * ':' as a reserved character, while '/' is
                   * reserved in Mac OS X/POSIX). */
                  if (nodeName[i] == '/') {
                    nodeName[i] = ':';
                  } else if (nodeName[i] == ':') {
                    /* Note: This should really be
                     *       considered an illegal HFS+
                     *       character. */
                    nodeName[i] = '/';
                  }
                }

                pathBuilder.append('/').append(nodeName);
              } else {
                firstComponent = false;
              }
            }
          } else {
            pathBuilder = null;
          }

          System.out.println(
              "CNID: "
                  + k.getFileID().toLong()
                  + " "
                  + "Type: "
                  + header.getCompressionType()
                  + " "
                  + "Size: "
                  + header.getFileSize()
                  + (pathBuilder != null ? " Path: " + pathBuilder.toString() : ""));
        }
      } else {
        System.err.println(
            "[WARNING] Unexpected attributes B-tree " + "node type: " + curNode.getClass());
      }
    }

    fsHandler.close();
    System.exit(0);
  }