Project: azkaban
/*
 * Copyright 2010 LinkedIn, Inc 
 *  
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 
 * use this file except in compliance with the License. You may obtain a copy of 
 * the License at 
 *  
 * http://www.apache.org/licenses/LICENSE-2.0 
 *  
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 
 * License for the specific language governing permissions and limitations under 
 * the License. 
 */
 
package azkaban.common.utils; 
 
import java.io.BufferedInputStream; 
import java.io.BufferedOutputStream; 
import java.io.BufferedReader; 
import java.io.File; 
import java.io.FileInputStream; 
import java.io.FileOutputStream; 
import java.io.FileReader; 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.OutputStream; 
import java.io.PrintWriter; 
import java.io.RandomAccessFile; 
import java.io.StringWriter; 
import java.lang.annotation.Annotation; 
import java.lang.reflect.Constructor; 
import java.lang.reflect.InvocationTargetException; 
import java.lang.reflect.Method; 
import java.net.URL; 
import java.net.URLClassLoader; 
import java.util.ArrayList; 
import java.util.Collections; 
import java.util.Comparator; 
import java.util.Enumeration; 
import java.util.LinkedList; 
import java.util.List; 
import java.util.Random; 
import java.util.StringTokenizer; 
import java.util.Vector; 
import java.util.regex.Pattern; 
import java.util.zip.ZipEntry; 
import java.util.zip.ZipFile; 
import java.util.zip.ZipOutputStream; 
import org.apache.commons.io.IOUtils; 
 
/**
 * Some helper functions 
 *  
 * @author jkreps 
 *  
 */
 
public class Utils { 
 
    public static final Random RANDOM = new Random(); 
    private static Pattern WHITESPACE = Pattern.compile("\\s+"); 
 
    public static String[] split(String str) { 
        return WHITESPACE.split(str, -1); 
    } 
 
    /**
     * Print the message and then exit with the given exit code 
     *  
     * @param message The message to print 
     * @param exitCode The exit code 
     */
 
    public static void croak(String message, int exitCode) { 
        System.err.println(message); 
        System.exit(exitCode); 
    } 
 
    /**
     * Load the class 
     *  
     * @param className The name of the class to load 
     * @return The loaded class 
     */
 
    public static Class<?> loadClass(String className) { 
        try { 
            return Class.forName(className); 
        } catch(ClassNotFoundException e) { 
            throw new IllegalStateException(e); 
        } 
    } 
 
    /**
     * Load the class 
     *  
     * @param className The name of the class to load 
     * @return The loaded class 
     */
 
    public static Class<?> loadClass(String className, ClassLoader loader) { 
        try { 
            return loader.loadClass(className); 
        } catch(ClassNotFoundException e) { 
            throw new IllegalStateException(e); 
        } 
    } 
 
    /**
     * Get an annotation from the class 
     *  
     * @param <T> The Annotation type 
     * @param theClass The class to read the annotation from 
     * @param annotation The annotation to read 
     * @return The annotation if it is there 
     * @throws IllegalArgumentException if the Annotation is not present 
     */
 
    public static <T extends Annotation> T getRequiredAnnotation(Class<?> theClass, 
                                                                 Class<T> annotation) { 
        T t = theClass.getAnnotation(annotation); 
        if(t == null
            throw new IllegalArgumentException("The expected annotation '" + annotation.getName() 
                                               + "' was not found on class '" + theClass.getName() 
                                               + "'."); 
        return t; 
    } 
 
    /**
     * Get the named method from the class 
     *  
     * @param c The class to get the method from 
     * @param name The method name 
     * @param argTypes The argument types 
     * @return The method 
     */
 
    public static Method getMethod(Class<?> c, String name, Class<?>... argTypes) { 
        try { 
            return c.getMethod(name, argTypes); 
        } catch(NoSuchMethodException e) { 
            throw new IllegalStateException(e); 
        } 
    } 
 
    /**
     * Return the object if it is non-null, otherwise throw an exception 
     *  
     * @param <T> The type of the object 
     * @param t The object 
     * @return The object if it is not null 
     * @throws IllegalArgumentException if the object is null 
     */
 
    public static <T> T nonNull(T t) { 
        if(t == null
            throw new IllegalArgumentException("Null value not allowed."); 
        else 
            return t; 
    } 
 
    /**
     * Get the Class of all the objects 
     *  
     * @param args The objects to get the Classes from 
     * @return The classes as an array 
     */
 
    public static Class<?>[] getTypes(Object... args) { 
        Class<?>[] argTypes = new Class<?>[args.length]; 
        for(int i = 0; i < argTypes.length; i++) 
            argTypes[i] = args[i].getClass(); 
        return argTypes; 
    } 
 
    /**
     * Call the named method 
     *  
     * @param obj The object to call the method on 
     * @param c The class of the object 
     * @param name The name of the method 
     * @param args The method arguments 
     * @return The result of the method 
     */
 
    public static Object callMethod(Object obj, Class<?> c, String name, Object... args) { 
        return callMethod(obj, c, name, getTypes(args), args); 
    } 
 
    /**
     * Call the named method 
     *  
     * @param obj The object to call the method on 
     * @param c The class of the object 
     * @param name The name of the method 
     * @param args The method arguments 
     * @return The result of the method 
     */
 
    public static Object callMethod(Object obj, 
                                    Class<?> c, 
                                    String name, 
                                    Class<?>[] classes, 
                                    Object[] args) { 
        try { 
            Method m = getMethod(c, name, classes); 
            return m.invoke(obj, args); 
        } catch(InvocationTargetException e) { 
            throw getCause(e); 
        } catch(IllegalAccessException e) { 
            throw new IllegalStateException(e); 
        } 
    } 
 
    /**
     * Call the class constructor with the given arguments 
     *  
     * @param className The name of the class 
     * @param args The arguments 
     * @return The constructed object 
     */
 
    public static Object callConstructor(String className, Object... args) { 
        return callConstructor(Utils.loadClass(className), getTypes(args), args); 
    } 
 
    /**
     * Call the class constructor with the given arguments 
     *  
     * @param c The class 
     * @param args The arguments 
     * @return The constructed object 
     */
 
    public static Object callConstructor(Class<?> c, Object... args) { 
        return callConstructor(c, getTypes(args), args); 
    } 
 
    /**
     * Call the class constructor with the given arguments 
     *  
     * @param c The class 
     * @param args The arguments 
     * @return The constructed object 
     */
 
    public static Object callConstructor(Class<?> c, Class<?>[] argTypes, Object[] args) { 
        try { 
            Constructor<?> cons = c.getConstructor(argTypes); 
            return cons.newInstance(args); 
        } catch(InvocationTargetException e) { 
            throw getCause(e); 
        } catch(IllegalAccessException e) { 
            throw new IllegalStateException(e); 
        } catch(NoSuchMethodException e) { 
            throw new IllegalStateException(e); 
        } catch(InstantiationException e) { 
            throw new IllegalStateException(e); 
        } 
    } 
 
    /**
     * Returns true if the constructor with the given args exists. 
     *  
     * @param c The class 
     * @param args The arguments 
     * @return 
     */
 
    public static boolean constructorExist(Class<?> c, Object... args) { 
        Class<?>[] argTypes = getTypes(args); 
        try { 
            @SuppressWarnings("unused"
            Constructor<?> cons = c.getConstructor(argTypes); 
        } catch(NoSuchMethodException e) { 
            return false
        } 
 
        return true
    } 
 
    /**
     * @return True if it is null or empty 
     */
 
    public static boolean isNullOrEmpty(String s) { 
        return s == null || "".equals(s); 
    } 
 
    /**
     * Get the root cause of the Exception 
     *  
     * @param e The Exception 
     * @return The root cause of the Exception 
     */
 
    private static RuntimeException getCause(InvocationTargetException e) { 
        Throwable cause = e.getCause(); 
        if(cause instanceof RuntimeException) 
            throw (RuntimeException) cause; 
        else 
            throw new IllegalStateException(e.getCause()); 
    } 
 
    /**
     * Create a directory at the path given (if there isn't already one there) 
     *  
     * @param path The path to create 
     */
 
    public static void makePaths(File path) { 
        if(!path.getParentFile().exists()) 
            makePaths(path.getParentFile()); 
 
        if(!path.exists()) 
            path.mkdir(); 
    } 
 
    public static <T> List<T> sorted(List<T> l, Comparator<T> comparator) { 
        List<T> copy = new ArrayList<T>(l); 
        Collections.sort(copy, comparator); 
        return copy; 
    } 
 
    public static <T extends Comparable<? super T>> List<T> sorted(List<T> l) { 
        List<T> copy = new ArrayList<T>(l); 
        Collections.sort(copy); 
        return copy; 
    } 
 
    public static File[] ls(String dir) { 
        return ls(new File(dir)); 
    } 
 
    public static File[] ls(File dir) { 
        if(!dir.exists() || !dir.isDirectory() || !dir.canRead()) 
            throw new IllegalArgumentException(dir 
                                               + " is not a readable directory or does not exist."); 
 
        File[] files = dir.listFiles(); 
        if(files == null
            return new File[0]; 
        else 
            return files; 
    } 
 
    public static List<String> getClassLoaderDescriptions(ClassLoader loader) { 
        List<String> values = new ArrayList<String>(); 
        while(loader != null) { 
            if(loader instanceof URLClassLoader) { 
                URLClassLoader urlLoader = (URLClassLoader) loader; 
                for(URL url: urlLoader.getURLs()) 
                    values.add(url.toString()); 
            } else { 
                values.add(loader.getClass().getName()); 
            } 
            loader = loader.getParent(); 
        } 
        return values; 
    } 
 
    public static String stackTrace(Throwable t) { 
        if(t == null) { 
            return "Utils.stackTrace(...) -- Throwable was null."
        } 
        StringWriter writer = new StringWriter(); 
        t.printStackTrace(new PrintWriter(writer)); 
        return writer.toString(); 
    } 
 
    public static void zip(File input, File output) throws IOException { 
        FileOutputStream out = new FileOutputStream(output); 
        ZipOutputStream zOut = new ZipOutputStream(out); 
        zipFile("", input, zOut); 
        zOut.close(); 
    } 
 
    private static void zipFile(String path, File input, ZipOutputStream zOut) throws IOException { 
        if(input.isDirectory()) { 
            File[] files = input.listFiles(); 
            if(files != null) { 
                for(File f: files) { 
                    String childPath = path + input.getName() + (f.isDirectory() ? "/" : ""); 
                    zipFile(childPath, f, zOut); 
                } 
            } 
        } else { 
            String childPath = path + (path.length() > 0 ? "/" : "") + input.getName(); 
            ZipEntry entry = new ZipEntry(childPath); 
            zOut.putNextEntry(entry); 
            InputStream fileInputStream = new BufferedInputStream(new FileInputStream(input)); 
            IOUtils.copy(fileInputStream, zOut); 
            fileInputStream.close(); 
        } 
    } 
 
    public static void unzip(ZipFile source, File dest) throws IOException { 
        Enumeration<?> entries = source.entries(); 
        while(entries.hasMoreElements()) { 
            ZipEntry entry = (ZipEntry) entries.nextElement(); 
            File newFile = new File(dest, entry.getName()); 
            if(entry.isDirectory()) { 
                newFile.mkdirs(); 
            } else { 
                newFile.getParentFile().mkdirs(); 
                InputStream src = source.getInputStream(entry); 
                OutputStream output = new BufferedOutputStream(new FileOutputStream(newFile)); 
                IOUtils.copy(src, output); 
                src.close(); 
                output.close(); 
            } 
        } 
    } 
 
    public static File createTempDir() { 
        return createTempDir(new File(System.getProperty("java.io.tmpdir"))); 
    } 
 
    public static File createTempDir(File parent) { 
        File temp = new File(parent, Integer.toString(Math.abs(RANDOM.nextInt()) % 100000000)); 
        temp.delete(); 
        temp.mkdir(); 
        temp.deleteOnExit(); 
        return temp; 
    } 
 
    /**
     * Read in content of a file and get the last *lineCount* lines. It is 
     * equivalent to *tail* command 
     *  
     * @param filename           input file name 
     * @param lineCount          desired number of tailing lines 
     * @return vector of the last *lineCount* lines 
     */
 
    public static Vector<String> tail(String filename, int lineCount) { 
        return tail(filename, lineCount, 2000); 
    } 
 
    /**
     * Read in content of a file and get the last *lineCount* lines. It is 
     * equivalent to *tail* command 
     *  
     * @param filename 
     * @param lineCount 
     * @param chunkSize 
     * @return 
     */
 
    public static Vector<String> tail(String filename, int lineCount, int chunkSize) { 
        try { 
            // read in content of the file 
            RandomAccessFile file = new RandomAccessFile(filename, "r"); 
            // destination vector 
            Vector<String> lastNLines = new Vector<String>(); 
            // current position 
            long currPos = file.length() - 1
            long startPos; 
            byte[] byteArray = new byte[chunkSize]; 
 
            // read in content of the file in reverse order 
            while(true) { 
                // read in from *fromPos* 
                startPos = currPos - chunkSize; 
 
                if(startPos <= 0) { // toward the beginning of the file 
                    file.seek(0); 
                    file.read(byteArray, 0, (int) currPos); // only read in 
                                                            // curPos bytes 
                    parseLinesFromLast(byteArray, 0, (int) currPos, lineCount, lastNLines); 
                    break
                } else { 
                    file.seek(startPos); 
                    if(byteArray == null
                        byteArray = new byte[chunkSize]; 
                    file.readFully(byteArray); 
                    if(parseLinesFromLast(byteArray, lineCount, lastNLines)) { 
                        break// we got the last *lineCount* lines 
                    } 
 
                    // move the current position 
                    currPos = startPos; // + lastLine.getBytes().length; 
                } 
            } 
 
            // there might be lineCount + 1 lines and the first line (now it is the last line) 
            // might not be complete 
            for (int index= lineCount; index < lastNLines.size(); index++) 
                lastNLines.removeElementAt(index); 
             
            // reverse the order of elements in lastNLines 
            Collections.reverse(lastNLines); 
            return lastNLines; 
        } catch(Exception e) { 
            return null
        } 
 
    } 
 
    /**
     * Parse lines in byteArray and store the last *lineCount* lines in 
     * *lastNLines* 
     *  
     * @param byteArray                 source byte array 
     * @param lineCount                   desired number of lines 
     * @param lastNLines                vector of last N lines 
     * @return true     indicates we get *lineCount* lines  
     *                false     otherwise 
     */
 
 
    protected static boolean parseLinesFromLast(byte[] byteArray, 
                                                int lineCount, 
                                                Vector<String> lastNLines) { 
        return parseLinesFromLast(byteArray, 0, byteArray.length, lineCount, lastNLines); 
    } 
 
    /**
     * Parse lines in byteArray and store the last *lineCount* lines in 
     * *lastNLines* 
     *  
     * @param byteArray         source byte array 
     * @param offset                offset of the byte array 
     * @param length                length of the byte array 
     * @param lineCount             desired number of lines 
     * @param lastNLines        vector of last N lines 
     * @return true         indicates we get *lineCount* lines  
     *                false         otherwise 
     */
 
    protected static boolean parseLinesFromLast(byte[] byteArray, 
                                                int offset, 
                                                int length, 
                                                int lineCount, 
                                                Vector<String> lastNLines) { 
 
        if(lastNLines.size() > lineCount) 
            return true
 
        // convert byte array to string 
        String lastNChars = new String(byteArray, offset, length); 
 
        // reverse the string 
        StringBuffer sb = new StringBuffer(lastNChars); 
        lastNChars = sb.reverse().toString(); 
 
        // tokenize the string using "\n" 
        String[] tokens = lastNChars.split("\n"); 
 
        // append lines to lastNLines 
        for (int index=0; index < tokens.length; index++) { 
            StringBuffer sbLine = new StringBuffer(tokens[index]); 
            String newline = sbLine.reverse().toString(); 
             
            if (index == 0 && !lastNLines.isEmpty()) { // first line might not be a complete line 
                int lineNum = lastNLines.size(); 
                String halfLine = lastNLines.get(lineNum - 1); 
                lastNLines.set(lineNum - 1, newline + halfLine); 
            } 
            else { 
                lastNLines.add(newline); 
            } 
             
            if(lastNLines.size() > lineCount) { 
                return true
            } 
        } 
 
        return false
    } 
}