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, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.oozie.util;
019
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.InputStreamReader;
023import java.io.OutputStream;
024import java.io.Reader;
025import java.io.Writer;
026import java.io.File;
027import java.io.FileInputStream;
028import java.io.FileOutputStream;
029import java.io.Closeable;
030import java.util.zip.ZipOutputStream;
031import java.util.zip.ZipEntry;
032import java.util.jar.JarOutputStream;
033import java.util.jar.Manifest;
034
035/**
036 * IO Utility methods.
037 */
038public abstract class IOUtils {
039
040    /**
041     * Delete recursively a local directory.
042     *
043     * @param file directory to delete.
044     * @throws IOException thrown if the directory could not be deleted.
045     */
046    public static void delete(File file) throws IOException {
047        ParamChecker.notNull(file, "file");
048        if (file.getAbsolutePath().length() < 5) {
049            throw new RuntimeException(XLog.format("Path[{0}] is too short, not deleting", file.getAbsolutePath()));
050        }
051        if (file.exists()) {
052            if (file.isDirectory()) {
053                File[] children = file.listFiles();
054                if (children != null) {
055                    for (File child : children) {
056                        delete(child);
057                    }
058                }
059            }
060            if (!file.delete()) {
061                throw new RuntimeException(XLog.format("Could not delete path[{0}]", file.getAbsolutePath()));
062            }
063        }
064    }
065
066    /**
067     * Return a reader as string. <p/>
068     *
069     * @param reader reader to read into a string.
070     * @param maxLen max content length allowed, if -1 there is no limit.
071     * @return the reader content.
072     * @throws IOException thrown if the resource could not be read.
073     */
074    public static String getReaderAsString(Reader reader, int maxLen) throws IOException {
075        ParamChecker.notNull(reader, "reader");
076        StringBuffer sb = new StringBuffer();
077        char[] buffer = new char[2048];
078        int read;
079        int count = 0;
080        while ((read = reader.read(buffer)) > -1) {
081            count += read;
082            if (maxLen > -1 && count > maxLen) {
083                throw new IllegalArgumentException(XLog.format("stream exceeds limit [{0}]", maxLen));
084            }
085            sb.append(buffer, 0, read);
086        }
087        reader.close();
088        return sb.toString();
089    }
090
091
092    /**
093     * Return a classpath resource as a stream. <p/>
094     *
095     * @param path classpath for the resource.
096     * @param maxLen max content length allowed.
097     * @return the stream for the resource.
098     * @throws IOException thrown if the resource could not be read.
099     */
100    public static InputStream getResourceAsStream(String path, int maxLen) throws IOException {
101        ParamChecker.notEmpty(path, "path");
102        InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
103        if (is == null) {
104            throw new IllegalArgumentException(XLog.format("resource [{0}] not found", path));
105        }
106        return is;
107    }
108
109    /**
110     * Return a classpath resource as a reader. <p/> It is assumed that the resource is a text resource.
111     *
112     * @param path classpath for the resource.
113     * @param maxLen max content length allowed.
114     * @return the reader for the resource.
115     * @throws IOException thrown if the resource could not be read.
116     */
117    public static Reader getResourceAsReader(String path, int maxLen) throws IOException {
118        return new InputStreamReader(getResourceAsStream(path, maxLen));
119    }
120
121    /**
122     * Return a classpath resource as string. <p/> It is assumed that the resource is a text resource.
123     *
124     * @param path classpath for the resource.
125     * @param maxLen max content length allowed.
126     * @return the resource content.
127     * @throws IOException thrown if the resource could not be read.
128     */
129    public static String getResourceAsString(String path, int maxLen) throws IOException {
130        ParamChecker.notEmpty(path, "path");
131        InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
132        if (is == null) {
133            throw new IllegalArgumentException(XLog.format("resource [{0}] not found", path));
134        }
135        Reader reader = new InputStreamReader(is);
136        return getReaderAsString(reader, maxLen);
137    }
138
139    /**
140     * Copies an inputstream into an output stream.
141     *
142     * @param is inputstream to copy from.
143     * @param os outputstream to  copy to.
144     * @throws IOException thrown if the copy failed.
145     */
146    public static void copyStream(InputStream is, OutputStream os) throws IOException {
147        ParamChecker.notNull(is, "is");
148        ParamChecker.notNull(os, "os");
149        byte[] buffer = new byte[4096];
150        int read;
151        while ((read = is.read(buffer)) > -1) {
152            os.write(buffer, 0, read);
153        }
154        os.close();
155        is.close();
156    }
157
158    /**
159     * Copies an char input stream into an char output stream.
160     *
161     * @param reader reader to copy from.
162     * @param writer writer to  copy to.
163     * @throws IOException thrown if the copy failed.
164     */
165    public static void copyCharStream(Reader reader, Writer writer) throws IOException {
166        ParamChecker.notNull(reader, "reader");
167        ParamChecker.notNull(writer, "writer");
168        char[] buffer = new char[4096];
169        int read;
170        while ((read = reader.read(buffer)) > -1) {
171            writer.write(buffer, 0, read);
172        }
173        writer.close();
174        reader.close();
175    }
176
177    /**
178     * Zips a local directory, recursively, into a ZIP stream.
179     *
180     * @param dir directory to ZIP.
181     * @param relativePath basePath in the ZIP for the files, normally "/".
182     * @param zos the ZIP output stream to ZIP the directory.
183     * @throws java.io.IOException thrown if the directory could not be zipped.
184     */
185    public static void zipDir(File dir, String relativePath, ZipOutputStream zos) throws IOException {
186        zipDir(dir, relativePath, zos, true);
187        zos.close();
188    }
189
190    private static void zipDir(File dir, String relativePath, ZipOutputStream zos, boolean start) throws IOException {
191        String[] dirList = dir.list();
192        for (String aDirList : dirList) {
193            File f = new File(dir, aDirList);
194            if (!f.isHidden()) {
195                if (f.isDirectory()) {
196                    if (!start) {
197                        ZipEntry dirEntry = new ZipEntry(relativePath + f.getName() + "/");
198                        zos.putNextEntry(dirEntry);
199                        zos.closeEntry();
200                    }
201                    String filePath = f.getPath();
202                    File file = new File(filePath);
203                    zipDir(file, relativePath + f.getName() + "/", zos, false);
204                }
205                else {
206                    ZipEntry anEntry = new ZipEntry(relativePath + f.getName());
207                    zos.putNextEntry(anEntry);
208                    InputStream is = new FileInputStream(f);
209                    byte[] arr = new byte[4096];
210                    int read = is.read(arr);
211                    while (read > -1) {
212                        zos.write(arr, 0, read);
213                        read = is.read(arr);
214                    }
215                    is.close();
216                    zos.closeEntry();
217                }
218            }
219        }
220    }
221
222    /**
223     * Creates a JAR file with the specified classes.
224     *
225     * @param baseDir local directory to create the JAR file, the staging 'classes' directory is created in there.
226     * @param jarName JAR file name, including extesion.
227     * @param classes classes to add to the JAR.
228     * @return an absolute File to the created JAR file.
229     * @throws java.io.IOException thrown if the JAR file could not be created.
230     */
231    public static File createJar(File baseDir, String jarName, Class... classes) throws IOException {
232        File classesDir = new File(baseDir, "classes");
233        for (Class clazz : classes) {
234            String classPath = clazz.getName().replace(".", "/") + ".class";
235            String classFileName = classPath;
236            if (classPath.lastIndexOf("/") > -1) {
237                classFileName = classPath.substring(classPath.lastIndexOf("/") + 1);
238            }
239            String packagePath = new File(classPath).getParent();
240            File dir = new File(classesDir, packagePath);
241            if (!dir.exists()) {
242                if (!dir.mkdirs()) {
243                    throw new IOException(XLog.format("could not create dir [{0}]", dir));
244                }
245            }
246            InputStream is = getResourceAsStream(classPath, -1);
247            OutputStream os = new FileOutputStream(new File(dir, classFileName));
248            copyStream(is, os);
249        }
250        File jar = new File(baseDir, jarName);
251        File jarDir = jar.getParentFile();
252        if (!jarDir.exists()) {
253            if (!jarDir.mkdirs()) {
254                throw new IOException(XLog.format("could not create dir [{0}]", jarDir));
255            }
256        }
257        JarOutputStream zos = new JarOutputStream(new FileOutputStream(jar), new Manifest());
258        zipDir(classesDir, "", zos);
259        return jar;
260    }
261
262    /**
263     * Close a list of resources. </p> Any thrown exceptions are suppressed.
264     * @param objects list of objects to close
265     */
266    public static void closeSafely(Closeable... objects) {
267        for (Closeable object : objects) {
268            try {
269                if (null != object) {
270                    object.close();
271                }
272            } catch (Throwable th) {
273                // ignore
274            }
275        }
276    }
277}