All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.pantsbuild.tools.jar.JarEntryCopier Maven / Gradle / Ivy

There is a newer version: 0.0.18
Show newest version
// Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
// Licensed under the Apache License, Version 2.0 (see LICENSE).

package org.pantsbuild.tools.jar;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import com.google.common.base.Preconditions;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closer;

/**
 * Copies jar entries from one jar to another without de-compressing and re-compressing when the
 * entries are {@link ZipEntry#DEFLATED}.
 *
 * TODO(John Sirois): Add support for detecting applicability of the reflection tricks used and thus
 * allow for falling back to slow jar entry copying.
 */
final class JarEntryCopier {

  private static class FieldReader {
    public static  FieldReader create(Class clazz, Class fieldType, String name) {
      try {
        Field field = clazz.getDeclaredField(name);
        field.setAccessible(true);
        return new FieldReader(field, fieldType);
      } catch (NoSuchFieldException e) {
        throw new RuntimeException(e);
      }
    }

    protected final Field field;
    private final Class fieldType;

    FieldReader(Field field, Class fieldType) {
      Preconditions.checkArgument(fieldType.isAssignableFrom(field.getType()));
      this.field = field;
      this.fieldType = fieldType;
    }

    @SuppressWarnings("unchecked") // already verified fieldType
    public V get(T instance) {
      try {
        return (V) field.get(instance);
      } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
      }
    }

    public Class getType() {
      return fieldType;
    }
  }

  private static class FieldAccessor extends FieldReader {
    public static  FieldAccessor create(
        Class clazz,
        Class fieldType,
        String name) {

      try {
        Field field = clazz.getDeclaredField(name);
        field.setAccessible(true);
        return new FieldAccessor(field, fieldType);
      } catch (NoSuchFieldException e) {
        throw new RuntimeException(e);
      }
    }

    FieldAccessor(Field field, Class fieldType) {
      super(field, fieldType);
      Preconditions.checkArgument(field.getType().isAssignableFrom(fieldType));
    }

    public void set(T instance, V value) {
      try {
        field.set(instance, value);
      } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
      }
    }
  }

  private static final FieldReader FIS_IN =
      FieldReader.create(FilterInputStream.class, InputStream.class, "in");

  private static final FieldReader ZOS_CRC =
      FieldReader.create(ZipOutputStream.class, CRC32.class, "crc");

  private static final FieldAccessor CRC_VALUE =
      FieldAccessor.create(CRC32.class, int.class, "crc");

  private static final FieldAccessor ZE_NAME =
      FieldAccessor.create(ZipEntry.class, String.class, "name");

  /**
   * Copy a a jar entry to an output file without decompressing and re-compressing the entry when
   * it is {@link ZipEntry#DEFLATED}.
   *
   * @param jarOut The jar file being created or appended to.
   * @param name The resource name to write.
   * @param jarIn The input JarFile.
   * @param jarEntry The entry extracted from jarIn.  The compression method passed in
   *     to this entry is preserved in the output file.
   * @throws IOException if there is a problem reading from {@code jarIn} or writing to
   *     {@code jarOut}.
   */
  static void copyEntry(JarOutputStream jarOut, String name, JarFile jarIn, JarEntry jarEntry)
      throws IOException {

    JarEntry outEntry = new JarEntry(jarEntry);
    ZE_NAME.set(outEntry, name);

    if (outEntry.isDirectory()) {
      outEntry.setMethod(ZipEntry.STORED);
      outEntry.setSize(0);
      outEntry.setCompressedSize(0);
      outEntry.setCrc(0);
      jarOut.putNextEntry(outEntry);
      jarOut.closeEntry();
    } else if (jarEntry.getMethod() == ZipEntry.STORED) {
      Closer closer = Closer.create();
      try {
        InputStream is = closer.register(jarIn.getInputStream(jarEntry));
        jarOut.putNextEntry(outEntry);
        ByteStreams.copy(is, jarOut);
      } catch (IOException e) {
        throw closer.rethrow(e);
      } finally {
        closer.close();
      }
      jarOut.closeEntry();
    } else {
      Closer closer = Closer.create();
      try {
        // Grab the underlying stream so we can read the compressed bytes.
        FilterInputStream zis = (FilterInputStream) closer.register(jarIn.getInputStream(jarEntry));
        InputStream is = FIS_IN.get(zis);

        // Start it as a DEFLATE....
        jarOut.putNextEntry(outEntry);

        // But swap out the method to STORE to the bytes don't get compressed.
        // This works because ZipFile doesn't make a defensive copy.
        outEntry.setMethod(ZipEntry.STORED);
        outEntry.setSize(jarEntry.getCompressedSize());
        ByteStreams.copy(is, jarOut);
      } catch (IOException e) {
        throw closer.rethrow(e);
      } finally {
        closer.close();
      }

      // The internal CRC is now wrong, so hack it before we close the entry.
      CRC_VALUE.set(ZOS_CRC.get(jarOut), (int) jarEntry.getCrc());
      jarOut.closeEntry();

      // Restore entry back to normal, so it will be written out correctly at the end.
      outEntry.setMethod(ZipEntry.DEFLATED);
      outEntry.setSize(jarEntry.getSize());
    }
  }

  private JarEntryCopier() {
    // utility
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy