PageRenderTime 30ms CodeModel.GetById 15ms app.highlight 10ms RepoModel.GetById 1ms app.codeStats 0ms

/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 */
 24package hudson.remoting;
 25
 26import java.io.ByteArrayOutputStream;
 27import java.io.File;
 28import java.io.IOException;
 29import java.io.UnsupportedEncodingException;
 30import java.io.InputStream;
 31import java.net.URL;
 32import java.net.MalformedURLException;
 33import java.net.URLConnection;
 34import java.net.JarURLConnection;
 35import java.lang.reflect.Field;
 36import java.lang.reflect.Method;
 37import java.util.zip.ZipFile;
 38import java.util.jar.JarFile;
 39import java.util.logging.Logger;
 40import java.util.logging.Level;
 41
 42/**
 43 * Locates where a given class is loaded from.
 44 *
 45 * @author Kohsuke Kawaguchi
 46 */
 47public class Which {
 48    /**
 49     * Locates the jar file that contains the given class.
 50     *
 51     * @throws IllegalArgumentException
 52     *      if failed to determine.
 53     */
 54    public static URL jarURL(Class clazz) throws IOException {
 55        ClassLoader cl = clazz.getClassLoader();
 56        if(cl==null)
 57            cl = ClassLoader.getSystemClassLoader();
 58        URL res = cl.getResource(clazz.getName().replace('.', '/') + ".class");
 59        if(res==null)
 60            throw new IllegalArgumentException("Unable to locate class file for "+clazz);
 61        return res;
 62    }
 63
 64    /**
 65     * Locates the jar file that contains the given class.
 66     *
 67     * <p>
 68     * Note that jar files are not always loaded from {@link File},
 69     * so for diagnostics purposes {@link #jarURL(Class)} is preferrable.
 70     *
 71     * @throws IllegalArgumentException
 72     *      if failed to determine.
 73     */
 74    public static File jarFile(Class clazz) throws IOException {
 75        URL res = jarURL(clazz);
 76        String resURL = res.toExternalForm();
 77        String originalURL = resURL;
 78        if(resURL.startsWith("jar:file:") || resURL.startsWith("wsjar:file:"))
 79            return fromJarUrlToFile(resURL);
 80
 81        if(resURL.startsWith("code-source:/")) {
 82            // OC4J apparently uses this. See http://www.nabble.com/Hudson-on-OC4J-tt16702113.html
 83            resURL = resURL.substring("code-source:/".length(), resURL.lastIndexOf('!')); // cut off jar: and the file name portion
 84            return new File(decode(new URL("file:/"+resURL).getPath()));
 85        }
 86        
 87        if(resURL.startsWith("zip:")){
 88            // weblogic uses this. See http://www.nabble.com/patch-to-get-Hudson-working-on-weblogic-td23997258.html
 89            // also see http://www.nabble.com/Re%3A-Hudson-on-Weblogic-10.3-td25038378.html#a25043415
 90            resURL = resURL.substring("zip:".length(), resURL.lastIndexOf('!')); // cut off zip: and the file name portion
 91            return new File(decode(new URL("file:"+resURL).getPath()));
 92        }
 93
 94        if(resURL.startsWith("file:")) {
 95            // unpackaged classes
 96            int n = clazz.getName().split("\\.").length; // how many slashes do wo need to cut?
 97            for( ; n>0; n-- ) {
 98                int idx = Math.max(resURL.lastIndexOf('/'), resURL.lastIndexOf('\\'));
 99                if(idx<0)   throw new IllegalArgumentException(originalURL + " - " + resURL);
100                resURL = resURL.substring(0,idx);
101            }
102
103            // won't work if res URL contains ' '
104            // return new File(new URI(null,new URL(res).toExternalForm(),null));
105            // won't work if res URL contains '%20'
106            // return new File(new URL(res).toURI());
107
108            return new File(decode(new URL(resURL).getPath()));
109        }
110
111        if(resURL.startsWith("vfszip:")) {
112            // JBoss5
113            InputStream is = res.openStream();
114            try {
115                Object delegate = is;
116                try {
117                    while (delegate.getClass().getEnclosingClass()!=ZipFile.class) {
118                        Field f = delegate.getClass().getDeclaredField("delegate");
119                        f.setAccessible(true);
120                        delegate = f.get(delegate);
121                    }
122                } catch (NoSuchFieldException e) {
123                    // extra step for JDK6u24; zip internals have changed
124                    Field f = delegate.getClass().getDeclaredField("is");
125                    f.setAccessible(true);
126                    delegate = f.get(delegate);
127                }
128                Field f = delegate.getClass().getDeclaredField("this$0");
129                f.setAccessible(true);
130                ZipFile zipFile = (ZipFile)f.get(delegate);
131                return new File(zipFile.getName());
132            } catch (Throwable e) {
133                // something must have changed in JBoss5. fall through
134                LOGGER.log(Level.FINE, "Failed to resolve vfszip into a jar location",e);
135            } finally {
136                is.close();
137            }
138        }
139
140        if(resURL.startsWith("vfs:") || resURL.startsWith("vfsfile:")) {
141            // JBoss6
142            try {
143                String resource = '/' + clazz.getName().replace('.', '/');
144                resURL = resURL.substring(0, resURL.lastIndexOf(resource));
145                Object content = new URL(res, resURL).getContent();
146                if (content instanceof File) {
147                    return (File)content;
148                }
149                Method m = content.getClass().getMethod( "getPhysicalFile" );
150                return (File)m.invoke(content);
151            } catch ( Throwable e ) {
152                // something must have changed in JBoss6. fall through
153                LOGGER.log(Level.FINE, "Failed to resolve vfs/vfsfile into a jar location",e);
154            }
155        }
156
157        if(resURL.startsWith("bundleresource:") || resURL.startsWith("bundle:")) {
158            // Equinox/Felix/etc.
159            try {
160                URLConnection con = res.openConnection();
161                Method m = con.getClass().getDeclaredMethod( "getLocalURL" );
162                m.setAccessible(true);
163                res = (URL)m.invoke(con);
164            } catch ( Throwable e ) {
165                // something must have changed in Equinox. fall through
166                LOGGER.log(Level.FINE, "Failed to resolve bundleresource into a jar location",e);
167            }
168        }
169
170        URLConnection con = res.openConnection();
171        if (con instanceof JarURLConnection) {
172            JarURLConnection jcon = (JarURLConnection) con;
173            JarFile jarFile = jcon.getJarFile();
174            if (jarFile!=null) {
175                String n = jarFile.getName();
176                if(n.length()>0) {// JDK6u10 needs this
177                    return new File(n);
178                } else {
179                    // JDK6u10 apparently starts hiding the real jar file name,
180                    // so this just keeps getting tricker and trickier...
181                    try {
182                        Field f = ZipFile.class.getDeclaredField("name");
183                        f.setAccessible(true);
184                        return new File((String) f.get(jarFile));
185                    } catch (NoSuchFieldException e) {
186                        LOGGER.log(Level.INFO, "Failed to obtain the local cache file name of "+clazz, e);
187                    } catch (IllegalAccessException e) {
188                        LOGGER.log(Level.INFO, "Failed to obtain the local cache file name of "+clazz, e);
189                    }
190                }
191            }
192        }
193
194        throw new IllegalArgumentException(originalURL + " - " + resURL);
195    }
196
197    public static File jarFile(URL resource) throws IOException {
198        return fromJarUrlToFile(resource.toExternalForm());
199    }
200
201    private static File fromJarUrlToFile(String resURL) throws MalformedURLException {
202        resURL = resURL.substring(resURL.indexOf(':')+1, resURL.lastIndexOf('!')); // cut off "scheme:" and the file name portion
203        return new File(decode(new URL(resURL).getPath()));
204    }
205
206    /**
207     * Decode '%HH'.
208     */
209    private static String decode(String s) {
210        ByteArrayOutputStream baos = new ByteArrayOutputStream();
211        for( int i=0; i<s.length();i++ ) {
212            char ch = s.charAt(i);
213            if(ch=='%') {
214                baos.write(hexToInt(s.charAt(i+1))*16 + hexToInt(s.charAt(i+2)));
215                i+=2;
216                continue;
217            }
218            baos.write(ch);
219        }
220        try {
221            return new String(baos.toByteArray(),"UTF-8");
222        } catch (UnsupportedEncodingException e) {
223            throw new Error(e); // impossible
224        }
225    }
226
227    private static int hexToInt(int ch) {
228        return Character.getNumericValue(ch);
229    }
230
231    private static final Logger LOGGER = Logger.getLogger(Which.class.getName());
232}