/*
* Copyright 2016 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.optaplanner.core.config.domain;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Sets;
import org.reflections.Reflections;
import org.reflections.util.ClasspathHelper;
/**
* Workaround for bug in {@link ClasspathHelper}.
* This workaround expires once https://github.com/ronmamo/reflections/pull/118 is fixed.
*/
public abstract class ReflectionsWorkaroundClasspathHelper {
/**
* Gets the current thread context class loader.
* {@code Thread.currentThread().getContextClassLoader()}.
*
* @return the context class loader, may be null
*/
public static ClassLoader contextClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
/**
* Gets the class loader of this library.
* {@code Reflections.class.getClassLoader()}.
*
* @return the static library class loader, may be null
*/
public static ClassLoader staticClassLoader() {
return Reflections.class.getClassLoader();
}
/**
* Returns an array of class Loaders initialized from the specified array.
* <p>
* If the input is null or empty, it defaults to both {@link #contextClassLoader()} and {@link #staticClassLoader()}
*
* @return the array of class loaders, not null
*/
public static ClassLoader[] classLoaders(ClassLoader... classLoaders) {
if (classLoaders != null && classLoaders.length != 0) {
return classLoaders;
} else {
ClassLoader contextClassLoader = contextClassLoader(), staticClassLoader = staticClassLoader();
return contextClassLoader != null ?
staticClassLoader != null && contextClassLoader != staticClassLoader ?
new ClassLoader[]{contextClassLoader, staticClassLoader} :
new ClassLoader[]{contextClassLoader} :
new ClassLoader[] {};
}
}
/**
* Returns a distinct collection of URLs based on a package name.
* <p>
* This searches for the package name as a resource, using {@link ClassLoader#getResources(String)}.
* For example, {@code forPackage(org.reflections)} effectively returns URLs from the
* classpath containing packages starting with {@code org.reflections}.
* <p>
* If the optional {@link ClassLoader}s are not specified, then both {@link #contextClassLoader()}
* and {@link #staticClassLoader()} are used for {@link ClassLoader#getResources(String)}.
* <p>
* The returned URLs retains the order of the given {@code classLoaders}.
*
* @return the collection of URLs, not null
*/
public static Collection<URL> forPackage(String name, ClassLoader... classLoaders) {
return forResource(resourceName(name), classLoaders);
}
/**
* Returns a distinct collection of URLs based on a resource.
* <p>
* This searches for the resource name, using {@link ClassLoader#getResources(String)}.
* For example, {@code forResource(test.properties)} effectively returns URLs from the
* classpath containing files of that name.
* <p>
* If the optional {@link ClassLoader}s are not specified, then both {@link #contextClassLoader()}
* and {@link #staticClassLoader()} are used for {@link ClassLoader#getResources(String)}.
* <p>
* The returned URLs retains the order of the given {@code classLoaders}.
*
* @return the collection of URLs, not null
*/
public static Collection<URL> forResource(String resourceName, ClassLoader... classLoaders) {
final List<URL> result = new ArrayList<>();
final ClassLoader[] loaders = classLoaders(classLoaders);
for (ClassLoader classLoader : loaders) {
try {
final Enumeration<URL> urls = classLoader.getResources(resourceName);
while (urls.hasMoreElements()) {
final URL url = urls.nextElement();
int index = url.toExternalForm().lastIndexOf(resourceName);
if (index != -1) {
String newUrlString = url.toExternalForm().substring(0, index);
// Use old url as context to support exotic url handlers
result.add(new URL(url, newUrlString));
} else {
result.add(url);
}
}
} catch (IOException e) {
if (Reflections.log != null) {
Reflections.log.error("error getting resources for " + resourceName, e);
}
}
}
return distinctUrls(result);
}
/**
* Returns the URL that contains a {@code Class}.
* <p>
* This searches for the class using {@link ClassLoader#getResource(String)}.
* <p>
* If the optional {@link ClassLoader}s are not specified, then both {@link #contextClassLoader()}
* and {@link #staticClassLoader()} are used for {@link ClassLoader#getResources(String)}.
*
* @return the URL containing the class, null if not found
*/
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) {
if (Reflections.log != null) {
Reflections.log.warn("Could not get URL", e);
}
}
}
return null;
}
/**
* Returns a distinct collection of URLs based on URLs derived from class loaders.
* <p>
* This finds the URLs using {@link URLClassLoader#getURLs()} using both
* {@link #contextClassLoader()} and {@link #staticClassLoader()}.
* <p>
* The returned URLs retains the order of the given {@code classLoaders}.
*
* @return the collection of URLs, not null
*/
public static Collection<URL> forClassLoader() {
return forClassLoader(classLoaders());
}
/**
* Returns a distinct collection of URLs based on URLs derived from class loaders.
* <p>
* This finds the URLs using {@link URLClassLoader#getURLs()} using the specified
* class loader, searching up the parent hierarchy.
* <p>
* If the optional {@link ClassLoader}s are not specified, then both {@link #contextClassLoader()}
* and {@link #staticClassLoader()} are used for {@link ClassLoader#getResources(String)}.
* <p>
* The returned URLs retains the order of the given {@code classLoaders}.
*
* @return the collection of URLs, not null
*/
public static Collection<URL> forClassLoader(ClassLoader... classLoaders) {
final Collection<URL> result = new ArrayList<>();
final ClassLoader[] loaders = classLoaders(classLoaders);
for (ClassLoader classLoader : loaders) {
while (classLoader != null) {
if (classLoader instanceof URLClassLoader) {
URL[] urls = ((URLClassLoader) classLoader).getURLs();
if (urls != null) {
result.addAll(Sets.<URL>newHashSet(urls));
}
}
classLoader = classLoader.getParent();
}
}
return distinctUrls(result);
}
/**
* Returns a distinct collection of URLs based on the {@code java.class.path} system property.
* <p>
* This finds the URLs using the {@code java.class.path} system property.
* <p>
* The returned collection of URLs retains the classpath order.
*
* @return the collection of URLs, not null
*/
public static Collection<URL> forJavaClassPath() {
Collection<URL> urls = new ArrayList<>();
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) {
if (Reflections.log != null) {
Reflections.log.warn("Could not get URL", e);
}
}
}
}
return distinctUrls(urls);
}
// REMOVED SERVLET API's
/**
* Cleans the URL.
*
* @param url the URL to clean, not null
* @return the path, not null
*/
public static String cleanPath(final URL url) {
String path = url.getPath();
try {
path = URLDecoder.decode(path, "UTF-8");
} catch (UnsupportedEncodingException e) { /**/ }
if (path.startsWith("jar:")) {
path = path.substring("jar:".length());
}
if (path.startsWith("file:")) {
path = path.substring("file:".length());
}
if (path.endsWith("!/")) {
path = path.substring(0, path.lastIndexOf("!/")) + "/";
}
return path;
}
private static String resourceName(String name) {
if (name != null) {
String resourceName = name.replace(".", "/");
resourceName = resourceName.replace("\\", "/");
if (resourceName.startsWith("/")) {
resourceName = resourceName.substring(1);
}
return resourceName;
}
return null;
}
//http://michaelscharf.blogspot.co.il/2006/11/javaneturlequals-and-hashcode-make.html
private static Collection<URL> distinctUrls(Collection<URL> urls) {
Map<String, URL> distinct = new HashMap<>(urls.size());
for (URL url : urls) {
distinct.put(url.toExternalForm(), url);
}
return distinct.values();
}
}