/hudson-remoting/src/main/java/hudson/remoting/Which.java

http://github.com/hudson/hudson · Java · 232 lines · 150 code · 18 blank · 64 comment · 22 complexity · 72b94c545146a2d02218dd45a0d478af MD5 · raw file

  1. /*
  2. * The MIT License
  3. *
  4. * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Ullrich Hafner
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy
  7. * of this software and associated documentation files (the "Software"), to deal
  8. * in the Software without restriction, including without limitation the rights
  9. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. * copies of the Software, and to permit persons to whom the Software is
  11. * furnished to do so, subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in
  14. * all copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. * THE SOFTWARE.
  23. */
  24. package hudson.remoting;
  25. import java.io.ByteArrayOutputStream;
  26. import java.io.File;
  27. import java.io.IOException;
  28. import java.io.UnsupportedEncodingException;
  29. import java.io.InputStream;
  30. import java.net.URL;
  31. import java.net.MalformedURLException;
  32. import java.net.URLConnection;
  33. import java.net.JarURLConnection;
  34. import java.lang.reflect.Field;
  35. import java.lang.reflect.Method;
  36. import java.util.zip.ZipFile;
  37. import java.util.jar.JarFile;
  38. import java.util.logging.Logger;
  39. import java.util.logging.Level;
  40. /**
  41. * Locates where a given class is loaded from.
  42. *
  43. * @author Kohsuke Kawaguchi
  44. */
  45. public class Which {
  46. /**
  47. * Locates the jar file that contains the given class.
  48. *
  49. * @throws IllegalArgumentException
  50. * if failed to determine.
  51. */
  52. public static URL jarURL(Class clazz) throws IOException {
  53. ClassLoader cl = clazz.getClassLoader();
  54. if(cl==null)
  55. cl = ClassLoader.getSystemClassLoader();
  56. URL res = cl.getResource(clazz.getName().replace('.', '/') + ".class");
  57. if(res==null)
  58. throw new IllegalArgumentException("Unable to locate class file for "+clazz);
  59. return res;
  60. }
  61. /**
  62. * Locates the jar file that contains the given class.
  63. *
  64. * <p>
  65. * Note that jar files are not always loaded from {@link File},
  66. * so for diagnostics purposes {@link #jarURL(Class)} is preferrable.
  67. *
  68. * @throws IllegalArgumentException
  69. * if failed to determine.
  70. */
  71. public static File jarFile(Class clazz) throws IOException {
  72. URL res = jarURL(clazz);
  73. String resURL = res.toExternalForm();
  74. String originalURL = resURL;
  75. if(resURL.startsWith("jar:file:") || resURL.startsWith("wsjar:file:"))
  76. return fromJarUrlToFile(resURL);
  77. if(resURL.startsWith("code-source:/")) {
  78. // OC4J apparently uses this. See http://www.nabble.com/Hudson-on-OC4J-tt16702113.html
  79. resURL = resURL.substring("code-source:/".length(), resURL.lastIndexOf('!')); // cut off jar: and the file name portion
  80. return new File(decode(new URL("file:/"+resURL).getPath()));
  81. }
  82. if(resURL.startsWith("zip:")){
  83. // weblogic uses this. See http://www.nabble.com/patch-to-get-Hudson-working-on-weblogic-td23997258.html
  84. // also see http://www.nabble.com/Re%3A-Hudson-on-Weblogic-10.3-td25038378.html#a25043415
  85. resURL = resURL.substring("zip:".length(), resURL.lastIndexOf('!')); // cut off zip: and the file name portion
  86. return new File(decode(new URL("file:"+resURL).getPath()));
  87. }
  88. if(resURL.startsWith("file:")) {
  89. // unpackaged classes
  90. int n = clazz.getName().split("\\.").length; // how many slashes do wo need to cut?
  91. for( ; n>0; n-- ) {
  92. int idx = Math.max(resURL.lastIndexOf('/'), resURL.lastIndexOf('\\'));
  93. if(idx<0) throw new IllegalArgumentException(originalURL + " - " + resURL);
  94. resURL = resURL.substring(0,idx);
  95. }
  96. // won't work if res URL contains ' '
  97. // return new File(new URI(null,new URL(res).toExternalForm(),null));
  98. // won't work if res URL contains '%20'
  99. // return new File(new URL(res).toURI());
  100. return new File(decode(new URL(resURL).getPath()));
  101. }
  102. if(resURL.startsWith("vfszip:")) {
  103. // JBoss5
  104. InputStream is = res.openStream();
  105. try {
  106. Object delegate = is;
  107. try {
  108. while (delegate.getClass().getEnclosingClass()!=ZipFile.class) {
  109. Field f = delegate.getClass().getDeclaredField("delegate");
  110. f.setAccessible(true);
  111. delegate = f.get(delegate);
  112. }
  113. } catch (NoSuchFieldException e) {
  114. // extra step for JDK6u24; zip internals have changed
  115. Field f = delegate.getClass().getDeclaredField("is");
  116. f.setAccessible(true);
  117. delegate = f.get(delegate);
  118. }
  119. Field f = delegate.getClass().getDeclaredField("this$0");
  120. f.setAccessible(true);
  121. ZipFile zipFile = (ZipFile)f.get(delegate);
  122. return new File(zipFile.getName());
  123. } catch (Throwable e) {
  124. // something must have changed in JBoss5. fall through
  125. LOGGER.log(Level.FINE, "Failed to resolve vfszip into a jar location",e);
  126. } finally {
  127. is.close();
  128. }
  129. }
  130. if(resURL.startsWith("vfs:") || resURL.startsWith("vfsfile:")) {
  131. // JBoss6
  132. try {
  133. String resource = '/' + clazz.getName().replace('.', '/');
  134. resURL = resURL.substring(0, resURL.lastIndexOf(resource));
  135. Object content = new URL(res, resURL).getContent();
  136. if (content instanceof File) {
  137. return (File)content;
  138. }
  139. Method m = content.getClass().getMethod( "getPhysicalFile" );
  140. return (File)m.invoke(content);
  141. } catch ( Throwable e ) {
  142. // something must have changed in JBoss6. fall through
  143. LOGGER.log(Level.FINE, "Failed to resolve vfs/vfsfile into a jar location",e);
  144. }
  145. }
  146. if(resURL.startsWith("bundleresource:") || resURL.startsWith("bundle:")) {
  147. // Equinox/Felix/etc.
  148. try {
  149. URLConnection con = res.openConnection();
  150. Method m = con.getClass().getDeclaredMethod( "getLocalURL" );
  151. m.setAccessible(true);
  152. res = (URL)m.invoke(con);
  153. } catch ( Throwable e ) {
  154. // something must have changed in Equinox. fall through
  155. LOGGER.log(Level.FINE, "Failed to resolve bundleresource into a jar location",e);
  156. }
  157. }
  158. URLConnection con = res.openConnection();
  159. if (con instanceof JarURLConnection) {
  160. JarURLConnection jcon = (JarURLConnection) con;
  161. JarFile jarFile = jcon.getJarFile();
  162. if (jarFile!=null) {
  163. String n = jarFile.getName();
  164. if(n.length()>0) {// JDK6u10 needs this
  165. return new File(n);
  166. } else {
  167. // JDK6u10 apparently starts hiding the real jar file name,
  168. // so this just keeps getting tricker and trickier...
  169. try {
  170. Field f = ZipFile.class.getDeclaredField("name");
  171. f.setAccessible(true);
  172. return new File((String) f.get(jarFile));
  173. } catch (NoSuchFieldException e) {
  174. LOGGER.log(Level.INFO, "Failed to obtain the local cache file name of "+clazz, e);
  175. } catch (IllegalAccessException e) {
  176. LOGGER.log(Level.INFO, "Failed to obtain the local cache file name of "+clazz, e);
  177. }
  178. }
  179. }
  180. }
  181. throw new IllegalArgumentException(originalURL + " - " + resURL);
  182. }
  183. public static File jarFile(URL resource) throws IOException {
  184. return fromJarUrlToFile(resource.toExternalForm());
  185. }
  186. private static File fromJarUrlToFile(String resURL) throws MalformedURLException {
  187. resURL = resURL.substring(resURL.indexOf(':')+1, resURL.lastIndexOf('!')); // cut off "scheme:" and the file name portion
  188. return new File(decode(new URL(resURL).getPath()));
  189. }
  190. /**
  191. * Decode '%HH'.
  192. */
  193. private static String decode(String s) {
  194. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  195. for( int i=0; i<s.length();i++ ) {
  196. char ch = s.charAt(i);
  197. if(ch=='%') {
  198. baos.write(hexToInt(s.charAt(i+1))*16 + hexToInt(s.charAt(i+2)));
  199. i+=2;
  200. continue;
  201. }
  202. baos.write(ch);
  203. }
  204. try {
  205. return new String(baos.toByteArray(),"UTF-8");
  206. } catch (UnsupportedEncodingException e) {
  207. throw new Error(e); // impossible
  208. }
  209. }
  210. private static int hexToInt(int ch) {
  211. return Character.getNumericValue(ch);
  212. }
  213. private static final Logger LOGGER = Logger.getLogger(Which.class.getName());
  214. }