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 }