/* * Copyright (c) 2001-2016, Inversoft, All Rights Reserved * * 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.primeframework.mvc.util; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; import org.primeframework.mvc.PrimeException; import static java.util.Collections.list; /** * This class models a ClassPath that contains a list of files that are directories or JAR/ZIP files in the path. * * @author Brian Pontarelli */ public class Classpath { private List<String> names = new ArrayList<String>(); /** * Makes a new classpath with the given names. * * @param names The name for the classpath. */ public Classpath(List<String> names) { this.names.addAll(names); } /** * Constructs a new classpath builder using the given classloader. * * @param loader The class loader to use. * @return The ClasspathBuilder. */ public static ClasspathBuilder build(ClassLoader loader) { return new ClasspathBuilder(loader); } /** * Constructs a new classpath builder using the current Thread's context classloader. * * @return The ClasspathBuilder. */ public static ClasspathBuilder build() { return new ClasspathBuilder(Thread.currentThread().getContextClassLoader()); } /** * @return The current classpath. * @throws IOException If there was any problems retrieving the classpath from the current thread's context * classloader. */ public static Classpath getCurrentClassPath() throws IOException { ClasspathBuilder builder = new ClasspathBuilder(Thread.currentThread().getContextClassLoader()); return builder.build(); } /** * Adds all the files to the classpath. * * @param files The files. */ public void addAllFiles(File[] files) { for (int i = 0; i < files.length; i++) { File file = files[i]; addFile(file); } } /** * Adds the entry to the classpath. * * @param entry The entry to add to the classpath. */ public void addEntry(String entry) { names.add(entry); } /** * Adds the file to the classpath by getting the file's absolute path and appending this string to the classpath. * * @param file The file to add to the classpath. */ public void addFile(File file) { names.add(file.getAbsolutePath()); } /** * @return The list of names. */ public List<String> getNames() { return Collections.unmodifiableList(names); } /** * Removes the entry from the classpath. * * @param entry The entry to remove. */ public void removeEntry(String entry) { names.remove(entry); } /** * Removes the existing file entry from the classpath. * * @param file The file whose absolute path to remove from the classpath. */ public void removeFile(File file) { names.remove(file.getAbsolutePath()); } /** * Converts the classpath to a platform compatible classpath String using the path separator character from the File * class. * * @return The classpath as a String or an empty String if the classpath is empty. */ public String toString() { StringBuffer buf = new StringBuffer(); for (int i = 0; i < names.size(); i++) { String entry = names.get(i); if (i != 0) { buf.append(File.pathSeparator); } buf.append(entry); } return buf.toString(); } /** * Builds a URLClassLoader from the classpath. Each entry is first made into a URL. If this is successful, that URL * is * added to the URLClassLoader's URL list. If not, a File is created and if that File exists, it is converted to a * URL * and then added to the URLClassLoader. * * @param parent The parent classloader of the URLClassLoader being created. * @return The URLClassLoader and never null. * @throws IllegalStateException If the creation of the URLClassLoader failed because a URL could not be created for * each entry in the classpath. */ public URLClassLoader toURLClassLoader(ClassLoader parent) throws IllegalStateException { List<URL> urls = new ArrayList<URL>(); for (int i = 0; i < names.size(); i++) { String s = names.get(i); URL url; try { url = new URL(s); } catch (MalformedURLException e) { File f = new File(s); if (f.exists()) { try { url = f.toURI().toURL(); } catch (MalformedURLException e1) { throw new PrimeException("Cannot create URLClassLoader because classpath entry [" + s + "] could not be " + "converted to a URL from a File."); } } else { throw new PrimeException("Cannot create URLClassLoader because classpath entry [" + s + "] is not a URL or a File."); } } urls.add(url); } URLClassLoader cl; if (parent == null) { cl = new URLClassLoader(urls.toArray(new URL[urls.size()])); } else { cl = new URLClassLoader(urls.toArray(new URL[urls.size()]), parent); } return cl; } /** * Simple class to assist in build ClassPath objects using the classpath of a ClassLoader. */ public static class ClasspathBuilder { private final ClassLoader classLoader; private final Set<Pattern> excludePatterns = new HashSet<Pattern>(); private final Set<String> excludes = new HashSet<String>(); public ClasspathBuilder(ClassLoader classLoader) { this.classLoader = classLoader; } /** * Adds a fully qualified URL to be excluded. This must be the fully qualified URL to a JAR or a classpath entry * such as: * <p/> * <pre> * file:///tmp/foo.jar * </pre> * * @param exclude The URL to exclude. */ public void addExclude(String exclude) { excludes.add(exclude); } /** * Adds a pattern that will exclude all URLs that match. * * @param pattern The pattern. */ public void addExcludePattern(Pattern pattern) { excludePatterns.add(pattern); } /** * Builds the ClassPath. * * @return The ClassPath. * @throws IOException If the classloader throws an exception. */ public Classpath build() throws IOException { Set<String> list = new HashSet<String>(); List<URL> urls = list(classLoader.getResources("META-INF")); for (URL url : urls) { String path = clean(url); if (path != null && !exclude(path)) { list.add(path); } } urls = list(classLoader.getResources("")); for (URL url : urls) { String path = clean(url); if (path != null && !exclude(path)) { list.add(path); } } String[] parts = System.getProperty("java.class.path").split(File.pathSeparator); for (String part : parts) { if (part != null && !exclude(part)) { list.add(part); } } return new Classpath(new ArrayList<>(list)); } /** * Return a {@link String} representation of the path. <p> The {@link URL} object provided is assumed to have been * returned from {@link ClassLoader#getResources(String)}. Calling {@link URL#toURI()} is never expected to throw a * {@link URISyntaxException}, if it does we are catching it and wrapping in an {@link IOException}. </p> * * @param url The url to clean. * @return a String representation of the path. * @throws IOException */ private String clean(URL url) throws IOException { try { // Decode scheme specific part of the URI. Do NOT use URLDecoder, it assumes a content type of x-www-form-urlencoded. String externalForm = url.toURI().getSchemeSpecificPart(); if (externalForm.endsWith("META-INF")) { externalForm = externalForm.substring(0, externalForm.length() - 8); } else if (externalForm.endsWith("META-INF/")) { /* JBoss work-around */ externalForm = externalForm.substring(0, externalForm.length() - 9); } if (externalForm.endsWith("!/")) { externalForm = externalForm.substring(0, externalForm.length() - 2); } if (externalForm.startsWith("file:")) { externalForm = externalForm.substring(5); } if (externalForm.startsWith("jar:file:")) { externalForm = externalForm.substring(9); } // On Windows the externalForm will be /C:/foo/bar which is invalid. Calling new File().getPath() will strip the leading slash return new File(externalForm).getPath(); } catch (URISyntaxException e) { throw new IOException(e); } } private boolean exclude(String externalForm) { for (String exclude : excludes) { if (externalForm.equals(exclude)) { return true; } } for (Pattern pattern : excludePatterns) { if (pattern.matcher(externalForm).matches()) { return true; } } return false; } } }