All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dorkbox.util.LocationResolver Maven / Gradle / Ivy

There is a newer version: 2.18
Show newest version
/*
 * Copyright 2010 dorkbox, llc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package dorkbox.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Vector;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * Convenience methods for working with resource/file/class locations
 */
public
class LocationResolver {
    private static final Pattern SLASH_PATTERN = Pattern.compile("\\\\");

    private static
    void log(String message) {
        System.err.println(prefix() + message);
    }

    /**
     * Normalizes the path. fixes %20 as spaces (in winxp at least). Converts \ -> /  (windows slash -> unix slash)
     *
     * @return a string pointing to the cleaned path
     * @throws IOException
     */
    private static
    String normalizePath(String path) throws IOException {
        // make sure the slashes are in unix format.
        path = SLASH_PATTERN.matcher(path)
                            .replaceAll("/");

        // Can have %20 as spaces (in winxp at least). need to convert to proper path from URL
        return URLDecoder.decode(path, "UTF-8");
    }

    /**
     * Retrieve the location of the currently loaded jar, or possibly null if it was compiled on the fly
     */
    public static
    File get() {
        return get(LocationResolver.class);
    }

    /**
     * Retrieve the location that this classfile was loaded from, or possibly null if the class was compiled on the fly
     */
    public static
    File get(Class clazz) {
        // Get the location of this class
        ProtectionDomain pDomain = clazz.getProtectionDomain();
        CodeSource cSource = pDomain.getCodeSource();

        // file:/X:/workspace/XYZ/classes/  when it's in ide/flat
        // jar:/X:/workspace/XYZ/jarname.jar  when it's jar
        URL loc = cSource.getLocation();

        // we don't always have a protection domain (for example, when we compile classes on the fly, from memory)
        if (loc == null) {
            return null;
        }

        // Can have %20 as spaces (in winxp at least). need to convert to proper path from URL
        try {
            File file = new File(normalizePath(loc.getFile())).getAbsoluteFile()
                                                              .getCanonicalFile();
            return file;

        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Unable to decode file path!", e);
        } catch (IOException e) {
            throw new RuntimeException("Unable to get canonical file path!", e);
        }
    }

    /**
     * Retrieves a URL of a given resourceName. If the resourceName is a directory, the returned URL will be the URL for the directory.
     * 

* This method searches the disk first (via new {@link File#File(String)}, then by {@link ClassLoader#getResource(String)}, then by * {@link ClassLoader#getSystemResource(String)}. * * @param resourceName the resource name to search for * * @return the URL for that given resource name */ public static URL getResource(String resourceName) { try { resourceName = normalizePath(resourceName); } catch (IOException e) { e.printStackTrace(); } URL resource = null; // 1) maybe it's on disk? priority is disk File file = new File(resourceName); if (file.canRead()) { try { resource = file.toURI() .toURL(); } catch (MalformedURLException e) { e.printStackTrace(); } } // 2) is it in the context classloader if (resource == null) { resource = Thread.currentThread() .getContextClassLoader() .getResource(resourceName); } // 3) is it in the system classloader if (resource == null) { // maybe it's in the system classloader? resource = ClassLoader.getSystemResource(resourceName); } // 4) look for it, and log the output (so we can find or debug it) if (resource == null) { try { searchResource(resourceName); } catch (IOException e) { e.printStackTrace(); } } return resource; } /** * Retrieves an enumeration of URLs of a given resourceName. If the resourceName is a directory, the returned list will be the URLs * of the contents of that directory. The first URL will always be the directory URL, as returned by {@link #getResource(String)}. *

* This method searches the disk first (via new {@link File#File(String)}, then by {@link ClassLoader#getResources(String)}, then by * {@link ClassLoader#getSystemResources(String)}. * * @param resourceName the resource name to search for * * @return the enumeration of URLs for that given resource name */ public static Enumeration getResources(String resourceName) { try { resourceName = normalizePath(resourceName); } catch (IOException e) { e.printStackTrace(); } Enumeration resources = null; try { // 1) maybe it's on disk? priority is disk File file = new File(resourceName); if (file.canRead()) { ArrayDeque urlList = new ArrayDeque(4); // add self always urlList.add(file.toURI() .toURL()); if (file.isDirectory()) { // add urls of all children File[] files = file.listFiles(); if (files != null) { for (int i = 0, n = files.length; i < n; i++) { urlList.add(files[i].toURI() .toURL()); } } } resources = new Vector(urlList).elements(); } // 2) is it in the context classloader if (resources == null) { resources = Thread.currentThread() .getContextClassLoader() .getResources(resourceName); } // 3) is it in the system classloader if (resources == null) { // maybe it's in the system classloader? resources = ClassLoader.getSystemResources(resourceName); } // 4) look for it, and log the output (so we can find or debug it) if (resources == null) { searchResource(resourceName); } } catch (IOException e) { e.printStackTrace(); } return resources; } /** * Retrieves the resource as a stream. *

* 1) checks the disk in the relative location to the executing app
* 2) Checks the current thread context classloader
* 3) Checks the Classloader system resource * * @param resourceName the name, including path information (Only valid '\' as the path separator) * * @return the resource stream, if it could be found, otherwise null. */ public static InputStream getResourceAsStream(String resourceName) { try { resourceName = normalizePath(resourceName); } catch (IOException e) { e.printStackTrace(); } InputStream resourceAsStream = null; // 1) maybe it's on disk? priority is disk if (new File(resourceName).canRead()) { try { resourceAsStream = new FileInputStream(resourceName); } catch (FileNotFoundException e) { // shouldn't happen, but if there is something wonky... e.printStackTrace(); } } // 2) is it in the context classloader if (resourceAsStream == null) { resourceAsStream = Thread.currentThread() .getContextClassLoader() .getResourceAsStream(resourceName); } // 3) is it in the system classloader if (resourceAsStream == null) { // maybe it's in the system classloader? resourceAsStream = ClassLoader.getSystemResourceAsStream(resourceName); } // 4) look for it, and log the output (so we can find or debug it) if (resourceAsStream == null) { try { searchResource(resourceName); } catch (IOException e) { e.printStackTrace(); } } return resourceAsStream; } // via RIVEN at JGO. CC0 as far as I can tell. public static void searchResource(String path) throws IOException { try { path = normalizePath(path); } catch (IOException e) { e.printStackTrace(); } List roots = new ArrayList(); ClassLoader contextClassLoader = Thread.currentThread() .getContextClassLoader(); if (contextClassLoader instanceof URLClassLoader) { URL[] urLs = ((URLClassLoader) contextClassLoader).getURLs(); for (URL url : urLs) { roots.add(new Root(url)); } System.err.println(); log("SEARCHING: \"" + path + "\""); for (int attempt = 1; attempt <= 6; attempt++) { for (Root root : roots) { if (root.search(path, attempt)) { return; } } } log("FAILED: failed to find anything like"); log(" \"" + path + "\""); log(" in all classpath entries:"); for (Root root : roots) { final File entry = root.entry; if (entry != null) { log(" \"" + entry.getAbsolutePath() + "\""); } } } else { throw new IOException("Unable to search for '" + path + "' in the context classloader of type '" + contextClassLoader.getClass() + "'. Please report this issue with as many specific details as possible (OS, Java version, application version"); } } @SuppressWarnings("Duplicates") private static class Root { final File entry; final List resources = new ArrayList(); public Root(URL entry) throws IOException { this.entry = visitRoot(entry, resources); } public boolean search(String path, int attempt) { try { path = normalizePath(path); } catch (IOException e) { e.printStackTrace(); } switch (attempt) { case 1: { for (String resource : resources) { if (path.equals(resource)) { log("SUCCESS: found resource \"" + path + "\" in root: " + entry); return true; } } break; } case 2: { for (String resource : resources) { if (path.toLowerCase() .equals(resource.toLowerCase())) { log("FOUND: similarly named resource:"); log(" \"" + resource + "\""); log(" in classpath entry:"); log(" \"" + entry + "\""); log(" for access use:"); log(" getResourceAsStream(\"/" + resource + "\");"); return true; } } break; } case 3: { for (String resource : resources) { String r1 = path; String r2 = resource; if (r1.contains("/")) { r1 = r1.substring(r1.lastIndexOf('/') + 1); } if (r2.contains("/")) { r2 = r2.substring(r2.lastIndexOf('/') + 1); } if (r1.equals(r2)) { log("FOUND: mislocated resource:"); log(" \"" + resource + "\""); log(" in classpath entry:"); log(" \"" + entry + "\""); log(" for access use:"); log(" getResourceAsStream(\"/" + resource + "\");"); return true; } } break; } case 4: { for (String resource : resources) { String r1 = path.toLowerCase(); String r2 = resource.toLowerCase(); if (r1.contains("/")) { r1 = r1.substring(r1.lastIndexOf('/') + 1); } if (r2.contains("/")) { r2 = r2.substring(r2.lastIndexOf('/') + 1); } if (r1.equals(r2)) { log("FOUND: mislocated, similarly named resource:"); log(" \"" + resource + "\""); log(" in classpath entry:"); log(" \"" + entry + "\""); log(" for access use:"); log(" getResourceAsStream(\"/" + resource + "\");"); return true; } } break; } case 5: { for (String resource : resources) { String r1 = path; String r2 = resource; if (r1.contains("/")) { r1 = r1.substring(r1.lastIndexOf('/') + 1); } if (r2.contains("/")) { r2 = r2.substring(r2.lastIndexOf('/') + 1); } if (r1.contains(".")) { r1 = r1.substring(0, r1.lastIndexOf('.')); } if (r2.contains(".")) { r2 = r2.substring(0, r2.lastIndexOf('.')); } if (r1.equals(r2)) { log("FOUND: resource with different extension:"); log(" \"" + resource + "\""); log(" in classpath entry:"); log(" \"" + entry + "\""); log(" for access use:"); log(" getResourceAsStream(\"/" + resource + "\");"); return true; } } break; } case 6: { for (String resource : resources) { String r1 = path.toLowerCase(); String r2 = resource.toLowerCase(); if (r1.contains("/")) { r1 = r1.substring(r1.lastIndexOf('/') + 1); } if (r2.contains("/")) { r2 = r2.substring(r2.lastIndexOf('/') + 1); } if (r1.contains(".")) { r1 = r1.substring(0, r1.lastIndexOf('.')); } if (r2.contains(".")) { r2 = r2.substring(0, r2.lastIndexOf('.')); } if (r1.equals(r2)) { log("FOUND: similarly named resource with different extension:"); log(" \"" + resource + "\""); log(" in classpath entry:"); log(" \"" + entry + "\""); log(" for access use:"); log(" getResourceAsStream(\"/" + resource + "\");"); return true; } } break; } default: return false; } return false; } } private static File visitRoot(URL url, List resources) throws IOException { if (!url.getProtocol() .equals("file")) { throw new IllegalStateException(); } String path = url.getPath(); if (OS.isWindows()) { if (path.startsWith("/")) { path = path.substring(1); } } File root = new File(path); if (!root.exists()) { log("failed to find classpath entry in filesystem: " + path); return null; } if (root.isDirectory()) { visitDir(normalizePath(root.getAbsolutePath()), root, resources); } else { final String s = root.getName() .toLowerCase(); if (s.endsWith(".zip")) { visitZip(root, resources); } else if (s.endsWith(".jar")) { visitZip(root, resources); } else { log("unknown classpath entry type: " + path); return null; } } return root; } private static void visitDir(String root, File dir, Collection out) { final File[] files = dir.listFiles(); if (files != null) { for (File file : files) { if (file.isDirectory()) { visitDir(root, file, out); } out.add(file.getAbsolutePath() .replace('\\', '/') .substring(root.length() + 1)); } } } private static void visitZip(File jar, Collection out) throws IOException { ZipInputStream zis = new ZipInputStream(new FileInputStream(jar)); while (true) { ZipEntry entry = zis.getNextEntry(); if (entry == null) { break; } out.add(entry.getName() .replace('\\', '/')); } zis.close(); } private static String prefix() { return "[" + LocationResolver.class.getSimpleName() + "] "; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy