/*
 * Copyright 2017-present Facebook, Inc.
 *
 * Licensed 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 com.facebook.buck.jvm.java;

import com.google.common.hash.Hashing;
import com.google.common.hash.HashingInputStream;
import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter; // NOPMD required by API
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Stream;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.util.Textifier;
import org.objectweb.asm.util.TraceClassVisitor;

public class JarDumper {
  private int asmFlags = 0;

  /**
   * Sets the flags that are passed to ASM's {@link ClassReader} when dumping class files. See
   * {@link ClassReader#accept(ClassVisitor, int)};
   *
   * @param asmFlags
   * @return
   */
  public JarDumper setAsmFlags(int asmFlags) {
    this.asmFlags = asmFlags;
    return this;
  }

  public List<String> dump(Path jarPath) throws IOException {
    List<String> result = new ArrayList<>();
    result.add("Directory:");
    try (JarFile abiJar = new JarFile(jarPath.toFile())) {
      abiJar.stream().map(JarEntry::toString).forEach(result::add);

      result.add("");
      result.add("Contents:");
      abiJar
          .stream()
          .flatMap(
              entry ->
                  Stream.concat(
                      Stream.of(String.format("%s:", entry.getName())),
                      Stream.concat(dumpEntry(abiJar, entry), Stream.of(""))))
          .forEach(result::add);
    }

    return result;
  }

  public Stream<String> dumpEntry(JarFile file, JarEntry entry) {
    try (InputStream inputStream = file.getInputStream(entry)) {
      String fileName = entry.getName();
      if (fileName.endsWith(".class")) {
        return dumpClassFile(inputStream);
      } else if (isTextFile(fileName)) {
        return dumpTextFile(inputStream);
      } else {
        return dumpBinaryFile(fileName, inputStream);
      }
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  protected static boolean isTextFile(String fileName) {
    return fileName.equals(JarFile.MANIFEST_NAME)
        || fileName.endsWith(".java")
        || fileName.endsWith(".json")
        || fileName.endsWith(".txt");
  }

  private Stream<String> dumpClassFile(InputStream stream) throws IOException {
    byte[] textifiedClass;
    try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
        PrintWriter pw = new PrintWriter(bos)) { // NOPMD required by API
      ClassReader reader = new ClassReader(stream);
      TraceClassVisitor traceVisitor = new TraceClassVisitor(null, new Textifier(), pw);
      reader.accept(traceVisitor, asmFlags);
      textifiedClass = bos.toByteArray();
    }

    try (InputStreamReader streamReader =
        new InputStreamReader(new ByteArrayInputStream(textifiedClass))) {
      return CharStreams.readLines(streamReader).stream();
    }
  }

  private Stream<String> dumpTextFile(InputStream inputStream) throws IOException {
    try (InputStreamReader streamReader = new InputStreamReader(inputStream)) {
      return CharStreams.readLines(streamReader).stream();
    }
  }

  private Stream<String> dumpBinaryFile(String name, InputStream inputStream) throws IOException {
    try (HashingInputStream is = new HashingInputStream(Hashing.murmur3_128(), inputStream)) {
      ByteStreams.exhaust(is);
      return Stream.of(String.format("Murmur3-128: %s", name, is.hash().toString()));
    }
  }
}

@@ -4255,16 +4255,22 @@
 mur3-128
+ of %25s
 : %25s%22, n

Fix incorrect string format usage