001 /*
002 * Copyright (c) 2011, Cloudera, Inc. All Rights Reserved.
003 *
004 * Cloudera, Inc. licenses this file to you under the Apache License,
005 * Version 2.0 (the "License"). You may not use this file except in
006 * compliance with the License. You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * This software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
011 * CONDITIONS OF ANY KIND, either express or implied. See the License for
012 * the specific language governing permissions and limitations under the
013 * License.
014 */
015 package com.cloudera.lib.io;
016
017 import com.cloudera.lib.util.Check;
018
019 import java.io.File;
020 import java.io.FileInputStream;
021 import java.io.IOException;
022 import java.io.InputStream;
023 import java.io.OutputStream;
024 import java.io.Reader;
025 import java.io.StringWriter;
026 import java.io.Writer;
027 import java.text.MessageFormat;
028 import java.util.zip.ZipEntry;
029 import java.util.zip.ZipOutputStream;
030
031 public abstract class IOUtils {
032
033 /**
034 * Deletes the specified path recursively.
035 * <p/>
036 * As a safety mechanism, it converts the specified file to an absolute
037 * path and it throws an exception if the path is shorter than 5 character
038 * (you dont want to delete things like /tmp, /usr, /etc, /var, /opt, /sbin, /dev).
039 *
040 * @param file path to delete.
041 * @throws IOException thrown if an IO error occurred.
042 */
043 public static void delete(File file) throws IOException {
044 if (file.getAbsolutePath().length() < 5) {
045 throw new IllegalArgumentException(
046 MessageFormat.format("Path [{0}] is too short, not deleting", file.getAbsolutePath()));
047 }
048 if (file.exists()) {
049 if (file.isDirectory()) {
050 File[] children = file.listFiles();
051 if (children != null) {
052 for (File child : children) {
053 delete(child);
054 }
055 }
056 }
057 if (!file.delete()) {
058 throw new RuntimeException(MessageFormat.format("Could not delete path [{0}]", file.getAbsolutePath()));
059 }
060 }
061 }
062
063 /**
064 * Copies an inputstream to an outputstream.
065 *
066 * @param is inputstream to copy.
067 * @param os target outputstream.
068 * @throws IOException thrown if an IO error occurred.
069 */
070 public static void copy(InputStream is, OutputStream os) throws IOException {
071 Check.notNull(is, "is");
072 Check.notNull(os, "os");
073 copy(is, os, 0, -1);
074 }
075
076 /**
077 * Copies a range of bytes from an inputstream to an outputstream.
078 *
079 * @param is inputstream to copy.
080 * @param os target outputstream.
081 * @param offset of the inputstream to start the copy from, the offset
082 * is relative to the current position.
083 * @param len length of the inputstream to copy, <code>-1</code> means
084 * until the end of the inputstream.
085 * @throws IOException thrown if an IO error occurred.
086 */
087 public static void copy(InputStream is, OutputStream os, long offset, long len) throws IOException {
088 Check.notNull(is, "is");
089 Check.notNull(os, "os");
090 byte[] buffer = new byte[1024];
091 long skipped = is.skip(offset);
092 if (skipped == offset) {
093 if (len == -1) {
094 int read = is.read(buffer);
095 while (read > -1) {
096 os.write(buffer, 0, read);
097 read = is.read(buffer);
098 }
099 is.close();
100 }
101 else {
102 long count = 0;
103 int read = is.read(buffer);
104 while (read > -1 && count < len) {
105 count += read;
106 if (count < len) {
107 os.write(buffer, 0, read);
108 read = is.read(buffer);
109 }
110 else if (count == len) {
111 os.write(buffer, 0, read);
112 }
113 else {
114 int leftToWrite = read - (int) (count - len);
115 os.write(buffer, 0, leftToWrite);
116 }
117 }
118 }
119 os.flush();
120 }
121 else {
122 throw new IOException(MessageFormat.format("InputStream ended before offset [{0}]", offset));
123 }
124 }
125
126 /**
127 * Copies a reader to a writer.
128 *
129 * @param reader reader to copy.
130 * @param writer target writer.
131 * @throws IOException thrown if an IO error occurred.
132 */
133 public static void copy(Reader reader, Writer writer) throws IOException {
134 Check.notNull(reader, "reader");
135 Check.notNull(writer, "writer");
136 copy(reader, writer, 0, -1);
137 }
138
139 /**
140 * Copies a range of chars from a reader to a writer.
141 *
142 * @param reader reader to copy.
143 * @param writer target writer.
144 * @param offset of the reader to start the copy from, the offset
145 * is relative to the current position.
146 * @param len length of the reader to copy, <code>-1</code> means
147 * until the end of the reader.
148 * @throws IOException thrown if an IO error occurred.
149 */
150 public static void copy(Reader reader, Writer writer, long offset, long len) throws IOException {
151 Check.notNull(reader, "reader");
152 Check.notNull(writer, "writer");
153 Check.ge0(offset, "offset");
154 char[] buffer = new char[1024];
155 long skipped = reader.skip(offset);
156 if (skipped == offset) {
157 if (len == -1) {
158 int read = reader.read(buffer);
159 while (read > -1) {
160 writer.write(buffer, 0, read);
161 read = reader.read(buffer);
162 }
163 reader.close();
164 }
165 else {
166 long count = 0;
167 int read = reader.read(buffer);
168 while (read > -1 && count < len) {
169 count += read;
170 if (count < len) {
171 writer.write(buffer, 0, read);
172 read = reader.read(buffer);
173 }
174 else if (count == len) {
175 writer.write(buffer, 0, read);
176 }
177 else {
178 int leftToWrite = read - (int) (count - len);
179 writer.write(buffer, 0, leftToWrite);
180 }
181 }
182 }
183 writer.flush();
184 }
185 else {
186 throw new IOException(MessageFormat.format("Reader ended before offset [{0}]", offset));
187 }
188 }
189
190 /**
191 * Reads a reader into a string.
192 * <p/>
193 *
194 * @param reader reader to read.
195 * @return string with the contents of the reader.
196 * @throws IOException thrown if an IO error occurred.
197 */
198 public static String toString(Reader reader) throws IOException {
199 Check.notNull(reader, "reader");
200 StringWriter writer = new StringWriter();
201 copy(reader, writer);
202 return writer.toString();
203 }
204
205
206 /**
207 * Zips the contents of a directory into a zip outputstream.
208 *
209 * @param dir directory contents to zip.
210 * @param relativePath relative path top prepend to all files in the zip.
211 * @param zos zip output stream
212 * @throws IOException thrown if an IO error occurred.
213 */
214 public static void zipDir(File dir, String relativePath, ZipOutputStream zos) throws IOException {
215 Check.notNull(dir, "dir");
216 Check.notNull(relativePath, "relativePath");
217 Check.notNull(zos, "zos");
218 zipDir(dir, relativePath, zos, true);
219 zos.close();
220 }
221
222 /**
223 * This recursive method is used by the {@link #zipDir(File, String, ZipOutputStream)} method.
224 * <p/>
225 * A special handling is required for the start of the zip (via the start parameter).
226 *
227 * @param dir directory contents to zip.
228 * @param relativePath relative path top prepend to all files in the zip.
229 * @param zos zip output stream
230 * @param start indicates if this invocation is the start of the zip.
231 * @throws IOException thrown if an IO error occurred.
232 */
233 private static void zipDir(File dir, String relativePath, ZipOutputStream zos, boolean start) throws IOException {
234 String[] dirList = dir.list();
235 for (String aDirList : dirList) {
236 File f = new File(dir, aDirList);
237 if (!f.isHidden()) {
238 if (f.isDirectory()) {
239 if (!start) {
240 ZipEntry dirEntry = new ZipEntry(relativePath + f.getName() + "/");
241 zos.putNextEntry(dirEntry);
242 zos.closeEntry();
243 }
244 String filePath = f.getPath();
245 File file = new File(filePath);
246 zipDir(file, relativePath + f.getName() + "/", zos, false);
247 }
248 else {
249 ZipEntry anEntry = new ZipEntry(relativePath + f.getName());
250 zos.putNextEntry(anEntry);
251 InputStream is = new FileInputStream(f);
252 byte[] arr = new byte[4096];
253 int read = is.read(arr);
254 while (read > -1) {
255 zos.write(arr, 0, read);
256 read = is.read(arr);
257 }
258 is.close();
259 zos.closeEntry();
260 }
261 }
262 }
263 }
264
265 }