001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one or more
003 *  contributor license agreements.  See the NOTICE file distributed with
004 *  this work for additional information regarding copyright ownership.
005 *  The ASF licenses this file to You under the Apache License, Version 2.0
006 *  (the "License"); you may not use this file except in compliance with
007 *  the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 *  Unless required by applicable law or agreed to in writing, software
012 *  distributed under the License is distributed on an "AS IS" BASIS,
013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 *  See the License for the specific language governing permissions and
015 *  limitations under the License.
016 */
017package org.apache.commons.compress.harmony.unpack200;
018
019import java.io.BufferedInputStream;
020import java.io.File;
021import java.io.FileInputStream;
022import java.io.FilterInputStream;
023import java.io.IOException;
024import java.io.InputStream;
025import java.net.URISyntaxException;
026import java.net.URL;
027import java.nio.file.Files;
028import java.nio.file.Path;
029import java.nio.file.Paths;
030import java.util.jar.JarOutputStream;
031
032import org.apache.commons.compress.harmony.pack200.Pack200Adapter;
033import org.apache.commons.compress.harmony.pack200.Pack200Exception;
034import org.apache.commons.compress.java.util.jar.Pack200.Unpacker;
035import org.apache.commons.io.input.BoundedInputStream;
036import org.apache.commons.lang3.reflect.FieldUtils;
037
038/**
039 * This class provides the binding between the standard Pack200 interface and the internal interface for (un)packing.
040 */
041public class Pack200UnpackerAdapter extends Pack200Adapter implements Unpacker {
042
043    /**
044     * Creates a new BoundedInputStream bound by the size of the given file.
045     * <p>
046     * The new BoundedInputStream wraps a new {@link BufferedInputStream}.
047     * </p>
048     *
049     * @param file The file.
050     * @return a new BoundedInputStream
051     * @throws IOException if an I/O error occurs
052     */
053    static BoundedInputStream newBoundedInputStream(final File file) throws IOException {
054        return newBoundedInputStream(file.toPath());
055    }
056
057    private static BoundedInputStream newBoundedInputStream(final FileInputStream fileInputStream) throws IOException {
058        return newBoundedInputStream(readPath(fileInputStream));
059    }
060
061    static BoundedInputStream newBoundedInputStream(final InputStream inputStream) throws IOException {
062        if (inputStream instanceof BoundedInputStream) {
063            return (BoundedInputStream) inputStream;
064        }
065        if (inputStream instanceof FilterInputStream) {
066            return newBoundedInputStream(unwrap((FilterInputStream) inputStream));
067        }
068        if (inputStream instanceof FileInputStream) {
069            return newBoundedInputStream((FileInputStream) inputStream);
070        }
071        // No limit
072        return new BoundedInputStream(inputStream);
073    }
074
075    /**
076     * Creates a new BoundedInputStream bound by the size of the given path.
077     * <p>
078     * The new BoundedInputStream wraps a new {@link BufferedInputStream}.
079     * </p>
080     *
081     * @param path The path.
082     * @return a new BoundedInputStream
083     * @throws IOException if an I/O error occurs
084     */
085    @SuppressWarnings("resource")
086    static BoundedInputStream newBoundedInputStream(final Path path) throws IOException {
087        return new BoundedInputStream(new BufferedInputStream(Files.newInputStream(path)), Files.size(path));
088    }
089
090    /**
091     * Creates a new BoundedInputStream bound by the size of the given file.
092     * <p>
093     * The new BoundedInputStream wraps a new {@link BufferedInputStream}.
094     * </p>
095     *
096     * @param first the path string or initial part of the path string.
097     * @param more  additional strings to be joined to form the path string.
098     * @return a new BoundedInputStream
099     * @throws IOException if an I/O error occurs
100     */
101    static BoundedInputStream newBoundedInputStream(final String first, final String... more) throws IOException {
102        return newBoundedInputStream(Paths.get(first, more));
103    }
104
105    /**
106     * Creates a new BoundedInputStream bound by the size of the given URL to a file.
107     * <p>
108     * The new BoundedInputStream wraps a new {@link BufferedInputStream}.
109     * </p>
110     *
111     * @param path The URL.
112     * @return a new BoundedInputStream
113     * @throws IOException        if an I/O error occurs
114     * @throws URISyntaxException
115     */
116    static BoundedInputStream newBoundedInputStream(final URL url) throws IOException, URISyntaxException {
117        return newBoundedInputStream(Paths.get(url.toURI()));
118    }
119
120    @SuppressWarnings("unchecked")
121    private static <T> T readField(final Object object, final String fieldName) {
122        try {
123            return (T) FieldUtils.readField(object, fieldName, true);
124        } catch (final IllegalAccessException e) {
125            return null;
126        }
127    }
128
129    static String readPath(final FileInputStream fis) {
130        return readField(fis, "path");
131    }
132
133    /**
134     * Unwraps the given FilterInputStream to return its wrapped InputStream.
135     *
136     * @param filterInputStream The FilterInputStream to unwrap.
137     * @return The wrapped InputStream
138     */
139    static InputStream unwrap(final FilterInputStream filterInputStream) {
140        return readField(filterInputStream, "in");
141    }
142
143    /**
144     * Unwraps the given InputStream if it is an FilterInputStream to return its wrapped InputStream.
145     *
146     * @param filterInputStream The FilterInputStream to unwrap.
147     * @return The wrapped InputStream
148     */
149    @SuppressWarnings("resource")
150    static InputStream unwrap(final InputStream inputStream) {
151        return inputStream instanceof FilterInputStream ? unwrap((FilterInputStream) inputStream) : inputStream;
152    }
153
154    @Override
155    public void unpack(final File file, final JarOutputStream out) throws IOException {
156        if (file == null || out == null) {
157            throw new IllegalArgumentException("Must specify both input and output streams");
158        }
159        final long size = file.length();
160        final int bufferSize = size > 0 && size < DEFAULT_BUFFER_SIZE ? (int) size : DEFAULT_BUFFER_SIZE;
161        try (InputStream in = new BufferedInputStream(Files.newInputStream(file.toPath()), bufferSize)) {
162            unpack(in, out);
163        }
164    }
165
166    @Override
167    public void unpack(final InputStream in, final JarOutputStream out) throws IOException {
168        if (in == null || out == null) {
169            throw new IllegalArgumentException("Must specify both input and output streams");
170        }
171        completed(0);
172        try {
173            new Archive(in, out).unpack();
174        } catch (final Pack200Exception e) {
175            throw new IOException("Failed to unpack Jar:" + e);
176        }
177        completed(1);
178    }
179}