/* * 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())); } } }