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.lang;
016    
017    import com.cloudera.lib.io.IOUtils;
018    import com.cloudera.lib.server.ServiceException;
019    import com.cloudera.lib.util.Check;
020    
021    import java.io.File;
022    import java.io.FileOutputStream;
023    import java.io.IOException;
024    import java.io.InputStream;
025    import java.io.OutputStream;
026    import java.lang.reflect.Field;
027    import java.lang.reflect.Method;
028    import java.lang.reflect.Modifier;
029    import java.net.URL;
030    import java.net.URLDecoder;
031    import java.text.MessageFormat;
032    import java.util.Enumeration;
033    import java.util.jar.JarOutputStream;
034    import java.util.jar.Manifest;
035    
036    /**
037     * Class related utilities.
038     */
039    public class ClassUtils {
040    
041      /**
042       * Finds the JAR file containing a class.
043       *
044       * @param klass class to find its JAR.
045       * @return the path to the JAR.
046       */
047      public static String getJar(Class klass) {
048        Check.notNull(klass, "klass");
049        ClassLoader loader = klass.getClassLoader();
050        if (loader != null) {
051          String class_file = klass.getName().replaceAll("\\.", "/") + ".class";
052          try {
053            for (Enumeration itr = loader.getResources(class_file); itr.hasMoreElements(); ) {
054              URL url = (URL) itr.nextElement();
055              if ("jar".equals(url.getProtocol())) {
056                String toReturn = url.getPath();
057                if (toReturn.startsWith("file:")) {
058                  toReturn = toReturn.substring("file:".length());
059                }
060                toReturn = URLDecoder.decode(toReturn, "UTF-8");
061                return toReturn.replaceAll("!.*$", "");
062              }
063            }
064          }
065          catch (IOException e) {
066            throw new RuntimeException(e);
067          }
068        }
069        return null;
070      }
071    
072      /**
073       * Convenience method that returns a resource as inputstream from the
074       * classpath.
075       * <p/>
076       * It first attempts to use the Thread's context classloader and if not
077       * set it uses the <code>ClassUtils</code> classloader.
078       *
079       * @param name resource to retrieve.
080       * @return inputstream with the resource, NULL if the resource does not
081       * exist.
082       */
083      public static InputStream getResource(String name) {
084        Check.notEmpty(name, "name");
085        ClassLoader cl = Thread.currentThread().getContextClassLoader();
086        if (cl == null) {
087          cl = ClassUtils.class.getClassLoader();
088        }
089        return cl.getResourceAsStream(name);
090      }
091    
092      /**
093       * Creates a JAR file with the specified classes.
094       *
095       * @param jarFile jar file path.
096       * @param classes classes to add to the JAR.
097       * @throws IOException thrown if an IO error occurred.
098       */
099      public static void createJar(File jarFile, Class... classes) throws IOException {
100        Check.notNull(jarFile, "jarFile");
101        File jarDir = jarFile.getParentFile();
102        if (!jarDir.exists()) {
103          if (!jarDir.mkdirs()) {
104            throw new IOException(MessageFormat.format("could not create dir [{0}]", jarDir));
105          }
106        }
107        createJar(new FileOutputStream(jarFile), classes);
108      }
109    
110      /**
111       * Writes the specified classes to an outputstream.
112       *
113       * @param os outputstream to write the classes to.
114       * @param classes classes to write to the outputstream.
115       * @throws IOException thrown if an IO error occurred.
116       */
117      public static void createJar(OutputStream os, Class... classes) throws IOException {
118        Check.notNull(os, "os");
119        File classesDir = File.createTempFile("createJar", "classes");
120        if (!classesDir.delete()) {
121          throw new IOException(MessageFormat.format("could not delete temp file [{0}]", classesDir));
122        }
123        for (Class clazz : classes) {
124          String classPath = clazz.getName().replace(".", "/") + ".class";
125          String classFileName = classPath;
126          if (classPath.lastIndexOf("/") > -1) {
127            classFileName = classPath.substring(classPath.lastIndexOf("/") + 1);
128          }
129          String packagePath = new File(classPath).getParent();
130          File dir = new File(classesDir, packagePath);
131          if (!dir.exists()) {
132            if (!dir.mkdirs()) {
133              throw new IOException(MessageFormat.format("could not create dir [{0}]", dir));
134            }
135          }
136          InputStream is = getResource(classPath);
137          OutputStream classOS = new FileOutputStream(new File(dir, classFileName));
138          IOUtils.copy(is, classOS);
139        }
140        JarOutputStream zos = new JarOutputStream(os, new Manifest());
141        IOUtils.zipDir(classesDir, "", zos);
142        IOUtils.delete(classesDir);
143      }
144    
145      /**
146       * Finds a public-static method by name in a class.
147       * <p/>
148       * In case of method overloading it will return the first method found.
149       *
150       * @param className name to look for the method.
151       * @param methodName method name to look in the class.
152       * @return the <code>Method</code> instance.
153       * @throws IllegalArgumentException thrown if the method does not exist,
154       * it is not public or it is not static.
155       */
156      public static Method findMethod(String className, String methodName) {
157        Check.notEmpty(className, "className");
158        Check.notEmpty(methodName, "methodName");
159        Method method = null;
160        try {
161          Class klass = Thread.currentThread().getContextClassLoader().loadClass(className);
162          for (Method m : klass.getMethods()) {
163            if (m.getName().equals(methodName)) {
164              method = m;
165              break;
166            }
167          }
168          if (method == null) {
169            throw new IllegalArgumentException(MessageFormat.format("class#method not found [{0}#{1}]", className,
170                                                                    methodName));
171          }
172          if ((method.getModifiers() & (Modifier.PUBLIC | Modifier.STATIC)) != (Modifier.PUBLIC | Modifier.STATIC)) {
173            throw new IllegalArgumentException(MessageFormat.format(
174              "class#method does not have PUBLIC or STATIC modifier [{0}#{1}]", className, methodName));
175          }
176        }
177        catch (ClassNotFoundException ex) {
178          throw new IllegalArgumentException(MessageFormat.format("class not found [{0}]", className));
179        }
180        return method;
181      }
182    
183      /**
184       * Finds a constant by name in a class.
185       * <p/>
186       *
187       * @param className name to look for the method.
188       * @param constantName constant name to look in the class.
189       * @return the constant instance.
190       * @throws IllegalArgumentException thrown if the constant does not exist,
191       * it is not public or it is not static.
192       */
193      public static Object findConstant(String className, String constantName) throws ServiceException {
194        Check.notEmpty(className, "className");
195        Check.notEmpty(constantName, "constantName");
196        try {
197          Class klass = Thread.currentThread().getContextClassLoader().loadClass(className);
198          Field field = klass.getField(constantName);
199          if ((field.getModifiers() & (Modifier.PUBLIC | Modifier.STATIC)) != (Modifier.PUBLIC | Modifier.STATIC)) {
200            throw new IllegalArgumentException(MessageFormat.format(
201              "class#constant does not have PUBLIC or STATIC modifier [{0}#{1}]", className, constantName));
202          }
203          return field.get(null);
204        }
205        catch (IllegalAccessException ex) {
206          throw new IllegalArgumentException(ex);
207        }
208        catch (NoSuchFieldException ex) {
209          throw new IllegalArgumentException(MessageFormat.format("class#constant not found [{0}#{1}]", className,
210                                                                  constantName));
211        }
212        catch (ClassNotFoundException ex) {
213          throw new IllegalArgumentException(MessageFormat.format("class not found [{0}]", className));
214        }
215      }
216    }