1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 package org.apache.commons.compress.archivers.examples; 20 21 import java.io.BufferedInputStream; 22 import java.io.File; 23 import java.io.IOException; 24 import java.io.InputStream; 25 import java.io.OutputStream; 26 import java.nio.channels.Channels; 27 import java.nio.channels.FileChannel; 28 import java.nio.channels.SeekableByteChannel; 29 import java.nio.file.Files; 30 import java.nio.file.StandardOpenOption; 31 32 import org.apache.commons.compress.archivers.ArchiveEntry; 33 import org.apache.commons.compress.archivers.ArchiveException; 34 import org.apache.commons.compress.archivers.ArchiveOutputStream; 35 import org.apache.commons.compress.archivers.ArchiveStreamFactory; 36 import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile; 37 import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; 38 import org.apache.commons.compress.utils.IOUtils; 39 40 /** 41 * Provides a high level API for creating archives. 42 * @since 1.17 43 */ 44 public class Archiver { 45 46 private interface ArchiveEntryCreator { 47 ArchiveEntry create(File f, String entryName) throws IOException; 48 } 49 50 private interface ArchiveEntryConsumer { 51 void accept(File source, ArchiveEntry entry) throws IOException; 52 } 53 54 private interface Finisher { 55 void finish() throws IOException; 56 } 57 58 /** 59 * Creates an archive {@code target} using the format {@code 60 * format} by recursively including all files and directories in 61 * {@code directory}. 62 * 63 * @param format the archive format. This uses the same format as 64 * accepted by {@link ArchiveStreamFactory}. 65 * @param target the file to write the new archive to. 66 * @param directory the directory that contains the files to archive. 67 * @throws IOException if an I/O error occurs 68 * @throws ArchiveException if the archive cannot be created for other reasons 69 */ 70 public void create(String format, File target, File directory) throws IOException, ArchiveException { 71 if (prefersSeekableByteChannel(format)) { 72 try (SeekableByteChannel c = FileChannel.open(target.toPath(), StandardOpenOption.WRITE, 73 StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { 74 create(format, c, directory); 75 } 76 return; 77 } 78 try (OutputStream o = Files.newOutputStream(target.toPath())) { 79 create(format, o, directory); 80 } 81 } 82 83 /** 84 * Creates an archive {@code target} using the format {@code 85 * format} by recursively including all files and directories in 86 * {@code directory}. 87 * 88 * @param format the archive format. This uses the same format as 89 * accepted by {@link ArchiveStreamFactory}. 90 * @param target the stream to write the new archive to. 91 * @param directory the directory that contains the files to archive. 92 * @throws IOException if an I/O error occurs 93 * @throws ArchiveException if the archive cannot be created for other reasons 94 */ 95 public void create(String format, OutputStream target, File directory) throws IOException, ArchiveException { 96 create(new ArchiveStreamFactory().createArchiveOutputStream(format, target), directory); 97 } 98 99 /** 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 } 218