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    }