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}