/*
* Copyright (C) 2015 Red Hat, Inc. and/or its affiliates.
*
* 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 org.jboss.errai.reflections.util;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import javax.servlet.ServletContext;
import org.jboss.errai.reflections.Reflections;
import org.jboss.errai.reflections.vfs.Vfs;
import com.google.common.collect.Sets;
/**
* Some classpath convenient methods
*/
public abstract class ClasspathHelper {
public static ClassLoader[] defaultClassLoaders = new ClassLoader[]{getContextClassLoader(), getStaticClassLoader()};
/** returns {@code Thread.currentThread().getContextClassLoader()} */
public static ClassLoader getContextClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
/** returns {@code Reflections.class.getClassLoader()} */
public static ClassLoader getStaticClassLoader() {
return Reflections.class.getClassLoader();
}
/** returns given classLoaders, if not null,
* otherwise defaults to both {@link #getContextClassLoader()} and {@link #getStaticClassLoader()} */
public static ClassLoader[] classLoaders(ClassLoader... classLoaders) {
return classLoaders != null && classLoaders.length != 0 ? classLoaders : defaultClassLoaders;
}
/** returns urls with resources of package starting with given name, using {@link ClassLoader#getResources(String)}
* <p>that is, forPackage("org.reflections") effectively returns urls from classpath with packages starting with {@code org.reflections}
* <p>if optional {@link ClassLoader}s are not specified, then both {@link #getContextClassLoader()} and {@link #getStaticClassLoader()} are used for {@link ClassLoader#getResources(String)}
*/
public static Set<URL> forPackage(String name, ClassLoader... classLoaders) {
final Set<URL> result = Sets.newHashSet();
final ClassLoader[] loaders = classLoaders(classLoaders);
final String resourceName = name.replace(".", "/");
for (ClassLoader classLoader : loaders) {
try {
final Enumeration<URL> urls = classLoader.getResources(resourceName);
while (urls.hasMoreElements()) {
final URL url = urls.nextElement();
final URL normalizedUrl = new URL(url.toExternalForm().substring(0, url.toExternalForm().lastIndexOf(resourceName)));
result.add(normalizedUrl);
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
/** returns the url that contains the given class, using {@link ClassLoader#getResource(String)}
* <p>if optional {@link ClassLoader}s are not specified, then either {@link #getContextClassLoader()} or {@link #getStaticClassLoader()} are used for {@link ClassLoader#getResources(String)}
* */
public static URL forClass(Class<?> aClass, ClassLoader... classLoaders) {
final ClassLoader[] loaders = classLoaders(classLoaders);
final String resourceName = aClass.getName().replace(".", "/") + ".class";
for (ClassLoader classLoader : loaders) {
try {
final URL url = classLoader.getResource(resourceName);
if (url != null) {
final String normalizedUrl = url.toExternalForm().substring(0, url.toExternalForm().lastIndexOf(aClass.getPackage().getName().replace(".", "/")));
return new URL(normalizedUrl);
}
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
return null;
}
/** returns urls using {@link java.net.URLClassLoader#getURLs()} up the classloader parent hierarchy
* <p>if optional {@link ClassLoader}s are not specified, then both {@link #getContextClassLoader()} and {@link #getStaticClassLoader()} are used for {@link ClassLoader#getResources(String)}
* */
public static Set<URL> forClassLoader(ClassLoader... classLoaders) {
final Set<URL> result = Sets.newHashSet();
final ClassLoader[] loaders = classLoaders(classLoaders);
for (ClassLoader classLoader : loaders) {
while (classLoader != null) {
if (classLoader instanceof URLClassLoader) {
URL[] urls = ((URLClassLoader) classLoader).getURLs();
if (urls != null) {
for (URL url : urls) {
try {
String urlString = url.toExternalForm();
String decodedUrlString =
URLDecoder.decode(urlString.replaceAll("\\+", "%2b"), "UTF-8");
result.add(new URL(decodedUrlString));
}
catch (IOException ioe) {
throw new RuntimeException("Failed to scan configuration Url's", ioe);
}
}
}
}
classLoader = classLoader.getParent();
}
}
return result;
}
/** returns urls using {@code java.class.path} system property */
public static Set<URL> forJavaClassPath() {
Set<URL> urls = Sets.newHashSet();
String javaClassPath = System.getProperty("java.class.path");
if (javaClassPath != null) {
for (String path : javaClassPath.split(File.pathSeparator)) {
try { urls.add(new File(path).toURI().toURL()); }
catch (Exception e) { e.printStackTrace(); }
}
}
return urls;
}
/** returns urls using {@link ServletContext} in resource path WEB-INF/lib */
public static Set<URL> forWebInfLib(final ServletContext servletContext) {
final Set<URL> urls = Sets.newHashSet();
for (Object urlString : servletContext.getResourcePaths("/WEB-INF/lib")) {
try { urls.add(servletContext.getResource((String) urlString)); }
catch (MalformedURLException e) { /*fuck off*/ }
}
return urls;
}
/** returns url using {@link ServletContext} in resource path WEB-INF/classes */
public static URL forWebInfClasses(final ServletContext servletContext) {
try {
final String path = servletContext.getRealPath("/WEB-INF/classes");
final File file = new File(path);
if (file.exists()) return file.toURL();
}
catch (MalformedURLException e) { /*fuck off*/ }
return null;
}
/** return urls that are in the current class path.
* attempts to load the jar manifest, if any, and adds to the result any dependencies it finds. */
public static Set<URL> forManifest() {
return forManifest(forClassLoader());
}
/** get the urls that are specified in the manifest of the given url for a jar file.
* attempts to load the jar manifest, if any, and adds to the result any dependencies it finds. */
public static Set<URL> forManifest(final URL url) {
final Set<URL> result = Sets.newHashSet();
result.add(url);
try {
final String part = Vfs.normalizePath(url);
File jarFile = new File(part);
JarFile myJar = new JarFile(part);
URL validUrl = tryToGetValidUrl(jarFile.getPath(), new File(part).getParent(), part);
if (validUrl != null) { result.add(validUrl); }
final Manifest manifest = myJar.getManifest();
if (manifest != null) {
final String classPath = manifest.getMainAttributes().getValue(new Attributes.Name("Class-Path"));
if (classPath != null) {
for (String jar : classPath.split(" ")) {
validUrl = tryToGetValidUrl(jarFile.getPath(), new File(part).getParent(), jar);
if (validUrl != null) { result.add(validUrl); }
}
}
}
} catch (IOException e) {
// don't do anything, we're going on the assumption it is a jar, which could be wrong
}
return result;
}
/** get the urls that are specified in the manifest of the given urls.
* attempts to load the jar manifest, if any, and adds to the result any dependencies it finds. */
public static Set<URL> forManifest(final Iterable<URL> urls) {
Set<URL> result = Sets.newHashSet();
// determine if any of the URLs are JARs, and get any dependencies
for (URL url : urls) {
result.addAll(forManifest(url));
}
return result;
}
//a little bit cryptic...
private static URL tryToGetValidUrl(String workingDir, String path, String filename) {
try {
if (new File(filename).exists())
return new File(filename).toURI().toURL();
if (new File(path + File.separator + filename).exists())
return new File(path + File.separator + filename).toURI().toURL();
if (new File(workingDir + File.separator + filename).exists())
return new File(workingDir + File.separator + filename).toURI().toURL();
} catch (MalformedURLException e) {
// don't do anything, we're going on the assumption it is a jar, which could be wrong
}
return null;
}
}