Archive.java

  1. /*
  2.  *  Licensed to the Apache Software Foundation (ASF) under one or more
  3.  *  contributor license agreements.  See the NOTICE file distributed with
  4.  *  this work for additional information regarding copyright ownership.
  5.  *  The ASF licenses this file to You under the Apache License, Version 2.0
  6.  *  (the "License"); you may not use this file except in compliance with
  7.  *  the License.  You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  *  Unless required by applicable law or agreed to in writing, software
  12.  *  distributed under the License is distributed on an "AS IS" BASIS,
  13.  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  *  See the License for the specific language governing permissions and
  15.  *  limitations under the License.
  16.  */
  17. package org.apache.commons.compress.harmony.unpack200;

  18. import java.io.BufferedInputStream;
  19. import java.io.BufferedOutputStream;
  20. import java.io.FileInputStream;
  21. import java.io.FileNotFoundException;
  22. import java.io.FileOutputStream;
  23. import java.io.IOException;
  24. import java.io.InputStream;
  25. import java.io.OutputStream;
  26. import java.nio.file.Files;
  27. import java.nio.file.Path;
  28. import java.nio.file.Paths;
  29. import java.util.jar.JarEntry;
  30. import java.util.jar.JarInputStream;
  31. import java.util.jar.JarOutputStream;
  32. import java.util.zip.GZIPInputStream;

  33. import org.apache.commons.compress.harmony.pack200.Pack200Exception;
  34. import org.apache.commons.io.IOUtils;
  35. import org.apache.commons.io.input.BoundedInputStream;

  36. /**
  37.  * Archive is the main entry point to unpack200. An archive is constructed with either two file names, a pack file and an output file name or an input stream
  38.  * and an output streams. Then {@code unpack()} is called, to unpack the pack200 archive.
  39.  */
  40. public class Archive {

  41.     private static final int[] MAGIC = { 0xCA, 0xFE, 0xD0, 0x0D };

  42.     private BoundedInputStream inputStream;

  43.     private final JarOutputStream outputStream;

  44.     private boolean removePackFile;

  45.     private int logLevel = Segment.LOG_LEVEL_STANDARD;

  46.     private FileOutputStream logFile;

  47.     private boolean overrideDeflateHint;

  48.     private boolean deflateHint;

  49.     private final Path inputPath;

  50.     private final long inputSize;

  51.     private final String outputFileName;

  52.     private final boolean closeStreams;

  53.     /**
  54.      * Creates an Archive with streams for the input and output files. Note: If you use this method then calling {@link #setRemovePackFile(boolean)} will have
  55.      * no effect.
  56.      *
  57.      * @param inputStream  the input stream, preferably a {@link BoundedInputStream}. The bound can the the file size.
  58.      * @param outputStream the JAR output stream.
  59.      * @throws IOException if an I/O error occurs
  60.      */
  61.     public Archive(final InputStream inputStream, final JarOutputStream outputStream) throws IOException {
  62.         this.inputStream = Pack200UnpackerAdapter.newBoundedInputStream(inputStream);
  63.         this.outputStream = outputStream;
  64.         if (inputStream instanceof FileInputStream) {
  65.             inputPath = Paths.get(Pack200UnpackerAdapter.readPathString((FileInputStream) inputStream));
  66.         } else {
  67.             inputPath = null;
  68.         }
  69.         this.outputFileName = null;
  70.         this.inputSize = -1;
  71.         this.closeStreams = false;
  72.     }

  73.     /**
  74.      * Creates an Archive with the given input and output file names.
  75.      *
  76.      * @param inputFileName  the input file name.
  77.      * @param outputFileName the output file name
  78.      * @throws FileNotFoundException if the input file does not exist
  79.      * @throws IOException           if an I/O error occurs
  80.      */
  81.     @SuppressWarnings("resource")
  82.     public Archive(final String inputFileName, final String outputFileName) throws FileNotFoundException, IOException {
  83.         this.inputPath = Paths.get(inputFileName);
  84.         this.inputSize = Files.size(this.inputPath);
  85.         this.inputStream = new BoundedInputStream(Files.newInputStream(inputPath), inputSize);
  86.         this.outputStream = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(outputFileName)));
  87.         this.outputFileName = outputFileName;
  88.         this.closeStreams = true;
  89.     }

  90.     private boolean available(final InputStream inputStream) throws IOException {
  91.         inputStream.mark(1);
  92.         final int check = inputStream.read();
  93.         inputStream.reset();
  94.         return check != -1;
  95.     }

  96.     public void setDeflateHint(final boolean deflateHint) {
  97.         overrideDeflateHint = true;
  98.         this.deflateHint = deflateHint;
  99.     }

  100.     public void setLogFile(final String logFileName) throws FileNotFoundException {
  101.         this.logFile = new FileOutputStream(logFileName);
  102.     }

  103.     public void setLogFile(final String logFileName, final boolean append) throws FileNotFoundException {
  104.         logFile = new FileOutputStream(logFileName, append);
  105.     }

  106.     public void setQuiet(final boolean quiet) {
  107.         if (quiet || logLevel == Segment.LOG_LEVEL_QUIET) {
  108.             logLevel = Segment.LOG_LEVEL_QUIET;
  109.         }
  110.     }

  111.     /**
  112.      * If removePackFile is set to true, the input file is deleted after unpacking.
  113.      *
  114.      * @param removePackFile If true, the input file is deleted after unpacking.
  115.      */
  116.     public void setRemovePackFile(final boolean removePackFile) {
  117.         this.removePackFile = removePackFile;
  118.     }

  119.     public void setVerbose(final boolean verbose) {
  120.         if (verbose) {
  121.             logLevel = Segment.LOG_LEVEL_VERBOSE;
  122.         } else if (logLevel == Segment.LOG_LEVEL_VERBOSE) {
  123.             logLevel = Segment.LOG_LEVEL_STANDARD;
  124.         }
  125.     }

  126.     /**
  127.      * Unpacks the Archive from the input file to the output file
  128.      *
  129.      * @throws Pack200Exception TODO
  130.      * @throws IOException      TODO
  131.      */
  132.     public void unpack() throws Pack200Exception, IOException {
  133.         outputStream.setComment("PACK200");
  134.         try {
  135.             if (!inputStream.markSupported()) {
  136.                 inputStream = new BoundedInputStream(new BufferedInputStream(inputStream));
  137.                 if (!inputStream.markSupported()) {
  138.                     throw new IllegalStateException();
  139.                 }
  140.             }
  141.             inputStream.mark(2);
  142.             if ((inputStream.read() & 0xFF | (inputStream.read() & 0xFF) << 8) == GZIPInputStream.GZIP_MAGIC) {
  143.                 inputStream.reset();
  144.                 inputStream = new BoundedInputStream(new BufferedInputStream(new GZIPInputStream(inputStream)));
  145.             } else {
  146.                 inputStream.reset();
  147.             }
  148.             inputStream.mark(MAGIC.length);
  149.             // pack200
  150.             final int[] word = new int[MAGIC.length];
  151.             for (int i = 0; i < word.length; i++) {
  152.                 word[i] = inputStream.read();
  153.             }
  154.             boolean compressedWithE0 = false;
  155.             for (int m = 0; m < MAGIC.length; m++) {
  156.                 if (word[m] != MAGIC[m]) {
  157.                     compressedWithE0 = true;
  158.                     break;
  159.                 }
  160.             }
  161.             inputStream.reset();
  162.             if (compressedWithE0) { // The original Jar was not packed, so just
  163.                 // copy it across
  164.                 final JarInputStream jarInputStream = new JarInputStream(inputStream);
  165.                 JarEntry jarEntry;
  166.                 while ((jarEntry = jarInputStream.getNextJarEntry()) != null) {
  167.                     outputStream.putNextEntry(jarEntry);
  168.                     final byte[] bytes = new byte[16_384];
  169.                     int bytesRead = jarInputStream.read(bytes);
  170.                     while (bytesRead != -1) {
  171.                         outputStream.write(bytes, 0, bytesRead);
  172.                         bytesRead = jarInputStream.read(bytes);
  173.                     }
  174.                     outputStream.closeEntry();
  175.                 }
  176.             } else {
  177.                 int i = 0;
  178.                 while (available(inputStream)) {
  179.                     i++;
  180.                     final Segment segment = new Segment();
  181.                     segment.setLogLevel(logLevel);
  182.                     segment.setLogStream(logFile != null ? (OutputStream) logFile : (OutputStream) System.out);
  183.                     segment.setPreRead(false);

  184.                     if (i == 1) {
  185.                         segment.log(Segment.LOG_LEVEL_VERBOSE, "Unpacking from " + inputPath + " to " + outputFileName);
  186.                     }
  187.                     segment.log(Segment.LOG_LEVEL_VERBOSE, "Reading segment " + i);
  188.                     if (overrideDeflateHint) {
  189.                         segment.overrideDeflateHint(deflateHint);
  190.                     }
  191.                     segment.unpack(inputStream, outputStream);
  192.                     outputStream.flush();
  193.                 }
  194.             }
  195.         } finally {
  196.             if (closeStreams) {
  197.                 IOUtils.closeQuietly(inputStream);
  198.                 IOUtils.closeQuietly(outputStream);
  199.             }
  200.             IOUtils.closeQuietly(logFile);
  201.         }
  202.         if (removePackFile && inputPath != null) {
  203.             Files.delete(inputPath);
  204.         }
  205.     }

  206. }