1   /**
2    * Distribution License:
3    * JSword is free software; you can redistribute it and/or modify it under
4    * the terms of the GNU Lesser General Public License, version 2.1 or later
5    * as published by the Free Software Foundation. This program is distributed
6    * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
7    * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8    * See the GNU Lesser General Public License for more details.
9    *
10   * The License is available on the internet at:
11   *      http://www.gnu.org/copyleft/lgpl.html
12   * or by writing to:
13   *      Free Software Foundation, Inc.
14   *      59 Temple Place - Suite 330
15   *      Boston, MA 02111-1307, USA
16   *
17   * © CrossWire Bible Society, 2005 - 2016
18   *
19   */
20  package org.crosswire.common.util;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.util.zip.ZipEntry;
25  import java.util.zip.ZipFile;
26  
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  
30  /**
31   * Various Java Class Utilities.
32   * 
33   * @see gnu.lgpl.License The GNU Lesser General Public License for details.
34   * @author Joe Walker
35   */
36  public final class ClassUtil {
37      /**
38       * Prevent instantiation
39       */
40      private ClassUtil() {
41      }
42  
43      /**
44       * Gets the Class for the className in a way that works well for extensions.
45       * See: http://www.javageeks.com/Papers/ClassForName/ClassForName.pdf
46       * 
47       * @param className
48       *            the class to get
49       * @return the found Class
50       * @throws ClassNotFoundException if the class is not found
51       */
52      public static Class<?> forName(String className) throws ClassNotFoundException {
53          return Thread.currentThread().getContextClassLoader().loadClass(className);
54      }
55  
56      /**
57       * This function finds the first matching filename for a Java class file
58       * from the classpath, if none is found it returns null.
59       * 
60       * @param className
61       *            the class to get
62       * @param classPath the lookup class path
63       * @return the filename for the class
64       */
65      public static String findClasspathEntry(String className, String classPath) {
66          String full = null;
67  
68          String[] paths = StringUtil.split(classPath, File.pathSeparator);
69          for (int i = 0; i < paths.length; i++) {
70              // Search the jar
71              if (paths[i].endsWith(EXTENSION_ZIP) || paths[i].endsWith(EXTENSION_JAR)) {
72                  ZipFile zip = null;
73                  try {
74                      String fileName = className.replace(',', '/') + EXTENSION_CLASS;
75                      zip = new ZipFile(paths[i]);
76                      ZipEntry entry = zip.getEntry(fileName);
77  
78                      if (entry != null && !entry.isDirectory()) {
79                          if (full != null && !full.equals(fileName)) {
80                              LOGGER.warn("Warning duplicate {} found: {} and {}", className, full, paths[i]);
81                          } else {
82                              full = paths[i];
83                          }
84                      }
85                  } catch (IOException ex) {
86                      // If that zip file failed, then ignore it and move on.
87                      LOGGER.warn("Missing zip file for {} and {}", className, paths[i]);
88                  } finally {
89                      if (null != zip) {
90                          try {
91                              zip.close();
92                          } catch (IOException ex) {
93                              LOGGER.error("close", ex);
94                          }
95                      }
96                  }
97              } else {
98                  StringBuilder path = new StringBuilder(256);
99  
100                 // Search for the file
101                 String extra = className.replace('.', File.separatorChar);
102 
103                 path.append(paths[i]);
104                 if (paths[i].charAt(paths[i].length() - 1) != File.separatorChar) {
105                     path.append(File.separatorChar);
106                 }
107 
108                 path.append(extra);
109                 path.append(EXTENSION_CLASS);
110                 String fileName = path.toString();
111 
112                 if (new File(fileName).isFile()) {
113                     if (full != null && !full.equals(fileName)) {
114                         LOGGER.warn("Warning duplicate {} found: {} and {}", className, full, paths[i]);
115                     } else {
116                         full = paths[i];
117                     }
118                 }
119             }
120         }
121 
122         return full;
123     }
124 
125     /**
126      * This function find the first matching filename for a Java class file from
127      * the classpath, if none is found it returns null.
128      * 
129      * @param className
130      *            the class to get
131      * @return the filename for the class
132      */
133     public static String findClasspathEntry(String className) {
134         String classpath = System.getProperty("java.class.path", "");
135         return findClasspathEntry(className, classpath);
136     }
137 
138     /**
139      * Gets the class name minus the package name for an <code>Object</code>.
140      * 
141      * @param object
142      *            the class to get the short name for, may be null
143      * @param valueIfNull
144      *            the value to return if null
145      * @return the class name of the object without the package name, or the
146      *         null value
147      */
148     public static String getShortClassName(Object object, String valueIfNull) {
149         if (object == null) {
150             return valueIfNull;
151         }
152         return getShortClassName(object.getClass().getName());
153     }
154 
155     /**
156      * Gets the class name minus the package name from a <code>Class</code>.
157      * 
158      * @param cls
159      *            the class to get the short name for, must not be
160      *            <code>null</code>
161      * @return the class name without the package name
162      * @throws IllegalArgumentException
163      *             if the class is <code>null</code>
164      */
165     public static String getShortClassName(Class<?> cls) {
166         if (cls == null) {
167             throw new IllegalArgumentException("The class must not be null");
168         }
169         return getShortClassName(cls.getName());
170     }
171 
172     /**
173      * Gets the class name minus the package name from a String.
174      * 
175      * <p>
176      * The string passed in is assumed to be a class name - it is not checked.
177      * </p>
178      * 
179      * @param className
180      *            the className to get the short name for, must not be empty or
181      *            <code>null</code>
182      * @return the class name of the class without the package name
183      * @throws IllegalArgumentException
184      *             if the className is empty
185      */
186     public static String getShortClassName(String className) {
187         if (className == null || className.length() == 0) {
188             throw new IllegalArgumentException("The class name must not be empty");
189         }
190         char[] chars = className.toCharArray();
191         int lastDot = 0;
192         for (int i = 0; i < chars.length; i++) {
193             if (chars[i] == PACKAGE_SEPARATOR_CHAR) {
194                 lastDot = i + 1;
195             } else if (chars[i] == INNER_CLASS_SEPARATOR_CHAR) {
196                 chars[i] = PACKAGE_SEPARATOR_CHAR;
197             }
198         }
199         return new String(chars, lastDot, chars.length - lastDot);
200     }
201 
202     /**
203      * The package separator character: <code>&#x2e;</code>.
204      */
205     private static final char PACKAGE_SEPARATOR_CHAR = '.';
206 
207     /**
208      * The inner class separator character: <code>$</code>.
209      */
210     private static final char INNER_CLASS_SEPARATOR_CHAR = '$';
211 
212     private static final String EXTENSION_CLASS = ".class";
213     private static final String EXTENSION_JAR = ".jar";
214     private static final String EXTENSION_ZIP = ".zip";
215 
216     /**
217      * The log stream
218      */
219     private static final Logger LOGGER = LoggerFactory.getLogger(ClassUtil.class);
220 }
221