Java tutorial
/* * Copyright (c) 2009-2019, b3log.org & hacpai.com * * 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.b3log.latke.ioc; import org.apache.commons.io.FileUtils; import org.apache.commons.io.filefilter.IOFileFilter; import org.apache.commons.io.filefilter.TrueFileFilter; import org.apache.commons.lang.StringUtils; import org.b3log.latke.logging.Level; import org.b3log.latke.logging.Logger; import org.b3log.latke.util.AntPathMatcher; import java.io.File; import java.io.IOException; import java.net.*; import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * Resolver for scanning the classpath. * * @author <a href="mailto:wmainlove@gmail.com">Love Yao</a> * @author <a href="http://88250.b3log.org">Liang Ding</a> * @version 1.0.1.1, Jan 20, 2013 */ public final class ClassPathResolver { /** * Logger. */ private static final Logger LOGGER = Logger.getLogger(ClassPathResolver.class); /** * Separator between JAR URL and file path within the JAR. */ private static final String JAR_URL_SEPARATOR = "!/"; /** * URL prefix for loading from the file system: "file:". */ private static final String FILE_URL_PREFIX = "file:"; /** * URL protocol for a JBoss VFS resource: "vfs". */ public static final String URL_PROTOCOL_VFS = "vfs"; /** * Private constructor. */ private ClassPathResolver() { } /** * Gets all URLs (resources) under the pattern. * * @param locationPattern the pattern of classPath (a ant-style string) * @return all URLS */ public static Set<URL> getResources(final String locationPattern) { final Set<URL> result = new HashSet<URL>(); final String scanRootPath = getRootPath(locationPattern); final String subPattern = locationPattern.substring(scanRootPath.length()); final Set<URL> rootDirResources = getResourcesFromRoot(scanRootPath); for (final URL rootDirResource : rootDirResources) { LOGGER.log(Level.INFO, "RootDirResource [protocol={0}, path={1}]", new Object[] { rootDirResource.getProtocol(), rootDirResource.getPath() }); if (isJarURL(rootDirResource)) { result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern)); } else if (rootDirResource.getProtocol().startsWith(URL_PROTOCOL_VFS)) { result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern)); } else { result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern)); } } return result; } /** * get rootPath from locationPattern. * <p> * if "/context/** / *.xml" should get the result "/context/" * </p> * * @param locationPattern locationPattern * @return the RootPath string. */ private static String getRootPath(final String locationPattern) { int rootDirEnd = locationPattern.length(); while (AntPathMatcher.isPattern(locationPattern.substring(0, rootDirEnd))) { rootDirEnd = locationPattern.lastIndexOf('/', rootDirEnd - 2) + 1; } return locationPattern.substring(0, rootDirEnd); } /** * the URLS under Root path ,each of which we should scan from. * * @param rootPath rootPath * @return the URLS under the Root path */ private static Set<URL> getResourcesFromRoot(final String rootPath) { final Set<URL> rets = new LinkedHashSet<URL>(); String path = rootPath; if (path.startsWith("/")) { path = path.substring(1); } try { final Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources(path); URL url = null; while (resources.hasMoreElements()) { url = (URL) resources.nextElement(); rets.add(url); } } catch (final IOException e) { LOGGER.log(Level.ERROR, "get the ROOT Rescources error", e); } return rets; } /** * check if the URL of the Rousource is a JAR resource. * * @param rootDirResource rootDirResource * @return isJAR */ private static boolean isJarURL(final URL rootDirResource) { final String protocol = rootDirResource.getProtocol(); /** * Determine whether the given URL points to a resource in a jar file, that is, has protocol "jar", "zip", * "wsjar" or "code-source". * <p> * "zip" and "wsjar" are used by BEA WebLogic Server and IBM WebSphere, respectively, but can be treated like * jar files. The same applies to "code-source" URLs on Oracle OC4J, provided that the path contains a jar * separator. * * */ return "jar".equals(protocol) || "zip".equals(protocol) || "wsjar".equals(protocol) || ("code-source".equals(protocol) && rootDirResource.getPath().contains(JAR_URL_SEPARATOR)); } /** * scan the jar to get the URLS of the Classes. * * @param rootDirResource which is "Jar" * @param subPattern subPattern * @return the URLs of all the matched classes */ private static Collection<? extends URL> doFindPathMatchingJarResources(final URL rootDirResource, final String subPattern) { final Set<URL> result = new LinkedHashSet<URL>(); JarFile jarFile = null; String jarFileUrl; String rootEntryPath = null; URLConnection con; boolean newJarFile = false; try { con = rootDirResource.openConnection(); if (con instanceof JarURLConnection) { final JarURLConnection jarCon = (JarURLConnection) con; jarCon.setUseCaches(false); jarFile = jarCon.getJarFile(); jarFileUrl = jarCon.getJarFileURL().toExternalForm(); final JarEntry jarEntry = jarCon.getJarEntry(); rootEntryPath = jarEntry != null ? jarEntry.getName() : ""; } else { // No JarURLConnection -> need to resort to URL file parsing. // We'll assume URLs of the format "jar:path!/entry", with the // protocol // being arbitrary as long as following the entry format. // We'll also handle paths with and without leading "file:" // prefix. final String urlFile = rootDirResource.getFile(); final int separatorIndex = urlFile.indexOf(JAR_URL_SEPARATOR); if (separatorIndex != -1) { jarFileUrl = urlFile.substring(0, separatorIndex); rootEntryPath = urlFile.substring(separatorIndex + JAR_URL_SEPARATOR.length()); jarFile = getJarFile(jarFileUrl); } else { jarFile = new JarFile(urlFile); jarFileUrl = urlFile; rootEntryPath = ""; } newJarFile = true; } } catch (final IOException e) { LOGGER.log(Level.ERROR, "reslove jar File error", e); return result; } try { if (!"".equals(rootEntryPath) && !rootEntryPath.endsWith("/")) { // Root entry path must end with slash to allow for proper // matching. // The Sun JRE does not return a slash here, but BEA JRockit // does. rootEntryPath = rootEntryPath + "/"; } for (final Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) { final JarEntry entry = (JarEntry) entries.nextElement(); final String entryPath = entry.getName(); String relativePath = null; if (entryPath.startsWith(rootEntryPath)) { relativePath = entryPath.substring(rootEntryPath.length()); if (AntPathMatcher.match(subPattern, relativePath)) { if (relativePath.startsWith("/")) { relativePath = relativePath.substring(1); } result.add(new URL(rootDirResource, relativePath)); } } } return result; } catch (final IOException e) { LOGGER.log(Level.ERROR, "parse the JarFile error", e); } finally { // Close jar file, but only if freshly obtained - // not from JarURLConnection, which might cache the file reference. if (newJarFile) { try { jarFile.close(); } catch (final IOException e) { LOGGER.log(Level.WARN, " occur error when closing jarFile", e); } } } return result; } /** * Resolve the given jar file URL into a JarFile object. * * @param jarFileUrl jarFileUrl * @return the JarFile * @throws IOException IOException */ private static JarFile getJarFile(final String jarFileUrl) throws IOException { if (jarFileUrl.startsWith(FILE_URL_PREFIX)) { try { return new JarFile(toURI(jarFileUrl).getSchemeSpecificPart()); } catch (final URISyntaxException ex) { // Fallback for URLs that are not valid URIs (should hardly ever // happen). return new JarFile(jarFileUrl.substring(FILE_URL_PREFIX.length())); } } else { return new JarFile(jarFileUrl); } } /** * scan the system file to get the URLS of the Classes. * * @param rootDirResource rootDirResource which is in File System * @param subPattern subPattern * @return the URLs of all the matched classes */ private static Collection<? extends URL> doFindPathMatchingFileResources(final URL rootDirResource, final String subPattern) { File rootFile = null; final Set<URL> rets = new LinkedHashSet<URL>(); try { rootFile = new File(rootDirResource.toURI()); } catch (final URISyntaxException e) { LOGGER.log(Level.ERROR, "cat not resolve the rootFile", e); throw new RuntimeException("cat not resolve the rootFile", e); } String fullPattern = StringUtils.replace(rootFile.getAbsolutePath(), File.separator, "/"); if (!subPattern.startsWith("/")) { fullPattern += "/"; } final String filePattern = fullPattern + StringUtils.replace(subPattern, File.separator, "/"); @SuppressWarnings("unchecked") final Collection<File> files = FileUtils.listFiles(rootFile, new IOFileFilter() { @Override public boolean accept(final File dir, final String name) { return true; } @Override public boolean accept(final File file) { if (file.isDirectory()) { return false; } if (AntPathMatcher.match(filePattern, StringUtils.replace(file.getAbsolutePath(), File.separator, "/"))) { return true; } return false; } }, TrueFileFilter.INSTANCE); try { for (File file : files) { rets.add(file.toURI().toURL()); } } catch (final Exception e) { LOGGER.log(Level.ERROR, "convert file to URL error", e); throw new RuntimeException("convert file to URL error", e); } return rets; } /** * Create a URI instance for the given location String, replacing spaces with "%20" quotes first. * * @param location the location String to convert into a URI instance * @return the URI instance * @throws URISyntaxException if the location wasn't a valid URI */ private static URI toURI(final String location) throws URISyntaxException { return new URI(StringUtils.replace(location, " ", "%20")); } /** * Inner delegate class, avoiding a hard JBoss VFS API dependency at runtime. */ private static final class VfsResourceMatchingDelegate { /** * the default private constructor. */ private VfsResourceMatchingDelegate() { } /** * scan Resources in Jboss Victual File System. * * @param rootUrl rootUrl * @param locationPattern subPattern * @return the matched URLd */ public static Set<URL> findMatchingResources(final URL rootUrl, final String locationPattern) { // VirtualFile root; // // try { // root = VFS.getChild(rootUrl.toURI()); // final PatternVirtualFileVisitor visitor = new PatternVirtualFileVisitor(root.getPathName(), locationPattern); // // root.visit(visitor); // return visitor.getResources(); // } catch (final Exception e) { // LOGGER.log(Level.ERROR, "findMatchingResources in Jboss VPF wrong", e); // return new HashSet<URL>(); // } throw new UnsupportedOperationException("JBoss VFS not supported yet!"); } } /** * VFS visitor for path matching purposes. */ private static class PatternVirtualFileVisitor { // implements VirtualFileVisitor { /** * the subPattern of the Pattern URL(not full). */ private final String subPattern; /** * the ROOT Path which to be scanned. */ private final String rootPath; /** * the all matched URLS. */ private final Set<URL> resources = new LinkedHashSet<URL>(); /** * the simplest constructor. * * @param rootPath rootPath * @param subPattern subPattern */ PatternVirtualFileVisitor(final String rootPath, final String subPattern) { this.subPattern = subPattern; this.rootPath = rootPath.length() == 0 || rootPath.endsWith("/") ? rootPath : rootPath + "/"; } // @Override // public VisitorAttributes getAttributes() { // return VisitorAttributes.RECURSE; // } // // @Override // public void visit(final VirtualFile vf) { // if (AntPathMatcher.match(this.subPattern, vf.getPathName().substring(this.rootPath.length()))) { // try { // this.resources.add(vf.toURL()); // } catch (final Exception e) { // LOGGER.log(Level.ERROR, "getting URL from JBOSS VirtualFile occurs error ", e); // throw new RuntimeException("getting URL from JBOSS VirtualFile occurs error ", e); // } // } // } /** * return the matched URLs. * * @return URLs */ public Set<URL> getResources() { return this.resources; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("sub-pattern: ").append(this.subPattern); sb.append(", resources: ").append(this.resources); return sb.toString(); } } }