Java tutorial
/** * Copyright (C) 2010-2012 the original author or authors. * * 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 com.googlecode.flyway.core.util.scanner; import com.googlecode.flyway.core.util.ClassPathResource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.io.IOException; import java.net.URL; import java.net.URLDecoder; import java.util.*; /** * ClassPath scanner. */ public class ClassPathScanner { private static final Log LOG = LogFactory.getLog(ClassPathScanner.class); /** * Scans the classpath for resources under the specified location, starting with the specified prefix and ending with * the specified suffix. * * @param location The location (directory) in the classpath to start searching. Subdirectories are also searched. * @param prefix The prefix of the resource names to match. * @param suffix The suffix of the resource names to match. * @return The resources that were found. * @throws IOException when the location could not be scanned. */ public ClassPathResource[] scanForResources(String location, String prefix, String suffix) throws IOException { Set<ClassPathResource> classPathResources = new TreeSet<ClassPathResource>(); Set<String> resourceNames = findResourceNames(location, prefix, suffix); for (String resourceName : resourceNames) { classPathResources.add(new ClassPathResource(resourceName)); LOG.debug("Found resource: " + resourceName); } return classPathResources.toArray(new ClassPathResource[classPathResources.size()]); } /** * Scans the classpath for classes under the specified package implementing any of these interfaces. * * @param location The location (package) in the classpath to start scanning. * Subpackages are also scanned. * @param implementedInterfaces The interfaces the matching classes should implement.. * @return The classes that were found. * @throws Exception when the location could not be scanned. */ public Class<?>[] scanForClasses(String location, Class<?>... implementedInterfaces) throws Exception { List<Class<?>> classes = new ArrayList<Class<?>>(); Set<String> resourceNames = findResourceNames(location, "", ".class"); for (String resourceName : resourceNames) { String className = toClassName(resourceName); Class<?> clazz = getClassLoader().loadClass(className); if (implementedInterfaces.length == 0) { classes.add(clazz); LOG.debug("Found class: " + className); } else { for (Class<?> implementedInterface : implementedInterfaces) { if (implementedInterface.isAssignableFrom(clazz)) { classes.add(clazz); LOG.debug("Found class: " + className); break; } } } } return classes.toArray(new Class<?>[classes.size()]); } /** * Converts this resource name to a fully qualified class name. * * @param resourceName The resource name. * @return The class name. */ private String toClassName(String resourceName) { String nameWithDots = resourceName.replace("/", "."); return nameWithDots.substring(0, (nameWithDots.length() - ".class".length())); } /** * Finds the resources names present at this location and below on the classpath starting with this prefix and * ending with this suffix. * * @param location The location on the classpath to scan. * @param prefix The filename prefix to match. * @param suffix The filename suffix to match. * @return The resource names. * @throws IOException when scanning this location failed. */ private Set<String> findResourceNames(String location, String prefix, String suffix) throws IOException { Set<String> resourceNames = new TreeSet<String>(); String normalizedLocation = normalizeLocation(location); Enumeration<URL> locationsUrls = getClassLoader().getResources(normalizedLocation); if (!locationsUrls.hasMoreElements()) { LOG.debug("Unable to determine URL for classpath location: " + normalizedLocation + " (ClassLoader: " + getClassLoader() + ")"); } while (locationsUrls.hasMoreElements()) { URL locationUrl = locationsUrls.nextElement(); LOG.debug("Scanning URL: " + locationUrl.toExternalForm()); String scanRoot = URLDecoder.decode(locationUrl.getFile(), "UTF-8"); if (scanRoot.endsWith("/")) { scanRoot = scanRoot.substring(0, scanRoot.length() - 1); } String protocol = locationUrl.getProtocol(); LocationScanner locationScanner = createLocationScanner(protocol); if (locationScanner == null) { LOG.warn("Unable to scan location: " + scanRoot + " (unsupported protocol: " + protocol + ")"); } else { resourceNames.addAll(locationScanner.findResourceNames(normalizedLocation, scanRoot)); } } return filterResourceNames(resourceNames, prefix, suffix); } /** * Creates an appropriate location scanner for this url protocol. * * @param protocol The protocol of the location url to scan. * @return The location scanner or {@code null} if it could not be created. */ private LocationScanner createLocationScanner(String protocol) { if ("file".equals(protocol)) { return new FileSystemLocationScanner(); } if ("jar".equals(protocol) || "zip".equals(protocol)) { return new JarFileLocationScanner(protocol); } return null; } /** * Normalizes this classpath location by * <ul> * <li>eliminating all leading and trailing slashes</li> * <li>turning all separators into slashes</li> * </ul> * * @param location The location to normalize. * @return The normalized location. */ private String normalizeLocation(String location) { String directory = location.replace(".", "/").replace("\\", "/"); if (directory.startsWith("/")) { directory = directory.substring(1); } if (directory.endsWith("/")) { directory = directory.substring(0, directory.length() - 1); } return directory; } /** * @return The classloader to use to scan the classpath. */ private ClassLoader getClassLoader() { return Thread.currentThread().getContextClassLoader(); } /** * Filters this list of resource names to only include the ones whose filename matches this prefix and this suffix. * * @param resourceNames The names to filter. * @param prefix The prefix to match. * @param suffix The suffix to match. * @return The filtered names set. */ private Set<String> filterResourceNames(Set<String> resourceNames, String prefix, String suffix) { Set<String> filteredResourceNames = new TreeSet<String>(); for (String resourceName : resourceNames) { String fileName = resourceName.substring(resourceName.lastIndexOf("/") + 1); if (fileName.startsWith(prefix) && fileName.endsWith(suffix) && (fileName.length() > (prefix + suffix).length())) { filteredResourceNames.add(resourceName); } else { LOG.debug("Filtering out resource: " + resourceName + " (filename: " + fileName + ")"); } } return filteredResourceNames; } }