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 }