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