001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.commons.compress.archivers.examples;
020
021import java.io.BufferedInputStream;
022import java.io.File;
023import java.io.IOException;
024import java.io.InputStream;
025import java.io.OutputStream;
026import java.nio.channels.Channels;
027import java.nio.channels.FileChannel;
028import java.nio.channels.SeekableByteChannel;
029import java.nio.file.Files;
030import java.nio.file.StandardOpenOption;
031
032import org.apache.commons.compress.archivers.ArchiveEntry;
033import org.apache.commons.compress.archivers.ArchiveException;
034import org.apache.commons.compress.archivers.ArchiveOutputStream;
035import org.apache.commons.compress.archivers.ArchiveStreamFactory;
036import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile;
037import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
038import org.apache.commons.compress.utils.IOUtils;
039
040/**
041 * Provides a high level API for creating archives.
042 * @since 1.17
043 */
044public class Archiver {
045
046    private interface ArchiveEntryCreator {
047        ArchiveEntry create(File f, String entryName) throws IOException;
048    }
049
050    private interface ArchiveEntryConsumer {
051        void accept(File source, ArchiveEntry entry) throws IOException;
052    }
053
054    private interface Finisher {
055        void finish() throws IOException;
056    }
057
058    /**
059     * Creates an archive {@code target} using the format {@code
060     * format} by recursively including all files and directories in
061     * {@code directory}.
062     *
063     * @param format the archive format. This uses the same format as
064     * accepted by {@link ArchiveStreamFactory}.
065     * @param target the file to write the new archive to.
066     * @param directory the directory that contains the files to archive.
067     * @throws IOException if an I/O error occurs
068     * @throws ArchiveException if the archive cannot be created for other reasons
069     */
070    public void create(String format, File target, File directory) throws IOException, ArchiveException {
071        if (prefersSeekableByteChannel(format)) {
072            try (SeekableByteChannel c = FileChannel.open(target.toPath(), StandardOpenOption.WRITE,
073                StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
074                create(format, c, directory);
075            }
076            return;
077        }
078        try (OutputStream o = Files.newOutputStream(target.toPath())) {
079            create(format, o, directory);
080        }
081    }
082
083    /**
084     * Creates an archive {@code target} using the format {@code
085     * format} by recursively including all files and directories in
086     * {@code directory}.
087     *
088     * @param format the archive format. This uses the same format as
089     * accepted by {@link ArchiveStreamFactory}.
090     * @param target the stream to write the new archive to.
091     * @param directory the directory that contains the files to archive.
092     * @throws IOException if an I/O error occurs
093     * @throws ArchiveException if the archive cannot be created for other reasons
094     */
095    public void create(String format, OutputStream target, File directory) throws IOException, ArchiveException {
096        create(new ArchiveStreamFactory().createArchiveOutputStream(format, target), directory);
097    }
098
099    /**
100     * Creates an archive {@code target} using the format {@code
101     * format} by recursively including all files and directories in
102     * {@code directory}.
103     *
104     * @param format the archive format. This uses the same format as
105     * accepted by {@link ArchiveStreamFactory}.
106     * @param target the channel to write the new archive to.
107     * @param directory the directory that contains the files to archive.
108     * @throws IOException if an I/O error occurs
109     * @throws ArchiveException if the archive cannot be created for other reasons
110     */
111    public void create(String format, SeekableByteChannel target, File directory)
112        throws IOException, ArchiveException {
113        if (!prefersSeekableByteChannel(format)) {
114            create(format, Channels.newOutputStream(target), directory);
115        } else if (ArchiveStreamFactory.ZIP.equalsIgnoreCase(format)) {
116            create(new ZipArchiveOutputStream(target), directory);
117        } else if (ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(format)) {
118            create(new SevenZOutputFile(target), directory);
119        } else {
120            // never reached as prefersSeekableByteChannel only returns true for ZIP and 7z
121            throw new ArchiveException("don't know how to handle format " + format);
122        }
123    }
124
125    /**
126     * Creates an archive {@code target} by recursively including all
127     * files and directories in {@code directory}.
128     *
129     * @param target the stream to write the new archive to.
130     * @param directory the directory that contains the files to archive.
131     * @throws IOException if an I/O error occurs
132     * @throws ArchiveException if the archive cannot be created for other reasons
133     */
134    public void create(final ArchiveOutputStream target, File directory)
135        throws IOException, ArchiveException {
136        create(directory, new ArchiveEntryCreator() {
137            public ArchiveEntry create(File f, String entryName) throws IOException {
138                return target.createArchiveEntry(f, entryName);
139            }
140        }, new ArchiveEntryConsumer() {
141            public void accept(File source, ArchiveEntry e) throws IOException {
142                target.putArchiveEntry(e);
143                if (!e.isDirectory()) {
144                    try (InputStream in = new BufferedInputStream(Files.newInputStream(source.toPath()))) {
145                        IOUtils.copy(in, target);
146                    }
147                }
148                target.closeArchiveEntry();
149            }
150        }, new Finisher() {
151            public void finish() throws IOException {
152                target.finish();
153            }
154        });
155    }
156
157    /**
158     * Creates an archive {@code target} by recursively including all
159     * files and directories in {@code directory}.
160     *
161     * @param target the file to write the new archive to.
162     * @param directory the directory that contains the files to archive.
163     * @throws IOException if an I/O error occurs
164     */
165    public void create(final SevenZOutputFile target, File directory) throws IOException {
166        create(directory, new ArchiveEntryCreator() {
167            public ArchiveEntry create(File f, String entryName) throws IOException {
168                return target.createArchiveEntry(f, entryName);
169            }
170        }, new ArchiveEntryConsumer() {
171            public void accept(File source, ArchiveEntry e) throws IOException {
172                target.putArchiveEntry(e);
173                if (!e.isDirectory()) {
174                    final byte[] buffer = new byte[8024];
175                    int n = 0;
176                    long count = 0;
177                    try (InputStream in = new BufferedInputStream(Files.newInputStream(source.toPath()))) {
178                        while (-1 != (n = in.read(buffer))) {
179                            target.write(buffer, 0, n);
180                            count += n;
181                        }
182                    }
183                }
184                target.closeArchiveEntry();
185            }
186        }, new Finisher() {
187            public void finish() throws IOException {
188                target.finish();
189            }
190        });
191    }
192
193    private boolean prefersSeekableByteChannel(String format) {
194        return ArchiveStreamFactory.ZIP.equalsIgnoreCase(format) || ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(format);
195    }
196
197    private void create(File directory, ArchiveEntryCreator creator, ArchiveEntryConsumer consumer,
198        Finisher finisher) throws IOException {
199        create("", directory, creator, consumer);
200        finisher.finish();
201    }
202
203    private void create(String prefix, File directory, ArchiveEntryCreator creator, ArchiveEntryConsumer consumer)
204        throws IOException {
205        File[] children = directory.listFiles();
206        if (children == null) {
207            return;
208        }
209        for (File f : children) {
210            String entryName = prefix + f.getName() + (f.isDirectory() ? "/" : "");
211            consumer.accept(f, creator.create(f, entryName));
212            if (f.isDirectory()) {
213                create(entryName, f, creator, consumer);
214            }
215        }
216    }
217}