/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You 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.apache.geode.management.internal.cli.util;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import org.apache.geode.internal.ClassPathLoader;
import org.apache.geode.management.internal.cli.CliUtil;
/**
* Utility class to scan class-path & load classes.
*
*
* @since GemFire 7.0
*/
public class ClasspathScanLoadHelper {
private static final String CLASSFILE_EXTENSION = ".class";
public static Set<Class<?>> loadAndGet(String commandPackageName,
Class<?> requiredInterfaceToLoad, boolean onlyInstantiable)
throws ClassNotFoundException, IOException {
Set<Class<?>> classSet = new HashSet<Class<?>>();
Class<?> classes[] = getClasses(commandPackageName);
for (int i = 0; i < classes.length; i++) {
if (implementsType(classes[i], requiredInterfaceToLoad)) {
if (onlyInstantiable) {
if (isInstantiable(classes[i])) {
classSet.add(classes[i]);
}
} else {
classSet.add(classes[i]);
}
}
}
return classSet;
}
public static boolean isInstantiable(Class<?> klass) {
int modifiers = klass.getModifiers();
boolean isInstantiable = !Modifier.isAbstract(modifiers) && !Modifier.isInterface(modifiers)
&& Modifier.isPublic(modifiers);
return isInstantiable;
}
private static boolean implementsType(Class<?> typeToCheck, Class<?> requiredInterface) {
if (requiredInterface.isAssignableFrom(typeToCheck)) {
return true;
} else {
return false;
}
}
public static Class<?>[] getClasses(String packageName)
throws ClassNotFoundException, IOException {
String packagePath = packageName.replace('.', '/');
List<File> dirs = new ArrayList<File>();
Enumeration<URL> resources = ClassPathLoader.getLatest().getResources(packagePath);
List<Class<?>> classesList = new ArrayList<Class<?>>();
while (resources.hasMoreElements()) {
URL packageUrl = resources.nextElement();
String actualPackagePath = packageUrl.getPath();
int jarIndex = actualPackagePath.indexOf(".jar!");
if (jarIndex != -1) { // resource appears to be in a jar
String jarPath = actualPackagePath.substring(0, jarIndex + ".jar".length());
if (jarPath.startsWith("file:")) {
if (File.separatorChar == '/') {// whether Unix or Windows system
// On Unix, to get actual path, we remove "file:" from the Path
jarPath = jarPath.substring("file:".length());
} else {
// On Windows jarPaths are like:
// Local Path:
// file:/G:/where/java/spring/spring-shell/1.0.0/spring-shell-1.0.0.RELEASE.jar
// Network Path:
// file://stinger.pune.gemstone.com/shared/where/java/spring/spring-shell/1.0.0/spring-shell-1.0.0.RELEASE.jar
// To get actual path, we remove "file:/" from the Path
jarPath = jarPath.substring("file:/".length());
// If the path still starts with a "/", then it's a network path.
// Hence, add one "/".
if (jarPath.startsWith("/") && !jarPath.startsWith("//")) {
jarPath = "/" + jarPath;
}
}
}
// decode the jarPath as it's derived from an URL
Class<?>[] classes = getClasses(CliUtil.decodeWithDefaultCharSet(jarPath), packageName);
classesList.addAll(Arrays.asList(classes));
} else {
dirs.add(new File(packageUrl.getFile()));
}
}
for (File directory : dirs) {
classesList.addAll(findClasses(directory, packageName));
}
return (Class[]) classesList.toArray(new Class[classesList.size()]);
}
public static List<Class<?>> findClasses(File directory, String packageName)
throws ClassNotFoundException {
List<Class<?>> classes = new ArrayList<Class<?>>();
if (!directory.exists()) {
return classes;
}
ClassPathLoader cpLoader = ClassPathLoader.getLatest();
// Load only .class files that are not from test code
TestClassFilter tcf = new TestClassFilter();
File[] files = directory.listFiles(tcf);
File file = null;
for (int i = 0; i < files.length; i++) {
file = files[i];
if (file.isDirectory()) {// sub-package
// assert !file.getName().contains(".");
classes.addAll(findClasses(file, packageName + "." + file.getName()));
} else {
// remove .class from the file name
String classSimpleName =
file.getName().substring(0, file.getName().length() - CLASSFILE_EXTENSION.length());
classes.add(cpLoader.forName(packageName + '.' + classSimpleName));
}
}
return classes;
}
/**
* Returns all classes that are in the specified jar and package name.
*
* @param jarPath The absolute or relative jar path.
* @param packageName The package name.
* @return Returns all classes that are in the specified jar and package name.
* @throws ClassNotFoundException Thrown if unable to load a class
* @throws IOException Thrown if error occurs while reading the jar file
*/
public static Class<?>[] getClasses(String jarPath, String packageName)
throws ClassNotFoundException, IOException {
ClassPathLoader cpLoader = ClassPathLoader.getLatest();
String[] classNames = getClassNames(jarPath, packageName);
Class<?> classes[] = new Class[classNames.length];
for (int i = 0; i < classNames.length; i++) {
String className = (String) classNames[i];
classes[i] = cpLoader.forName(className);
}
return classes;
}
/**
* Returns all names of classes that are defined in the specified jar and package name.
*
* @param jarPath The absolute or relative jar path.
* @param packageName The package name.
* @return Returns all names of classes that are defined in the specified jar and package name.
* @throws IOException Thrown if error occurs while reading the jar file
*/
public static String[] getClassNames(String jarPath, String packageName) throws IOException {
if (jarPath == null) {
return new String[0];
}
File file;
// Path is absolute on Unix if it starts with '/'
// or path contains colon on Windows
if (jarPath.startsWith("/") || (jarPath.indexOf(':') >= 0 && File.separatorChar == '\\')) {
// absolute path
file = new File(jarPath);
} else {
// relative path
String workingDir = System.getProperty("user.dir");
file = new File(workingDir + File.separator + jarPath);
}
List<String> classNames = new ArrayList<String>();
String packagePath = packageName.replaceAll("\\.", "/");
JarInputStream jarFile = new JarInputStream(new FileInputStream(file));
JarEntry jarEntry;
while (true) {
jarEntry = jarFile.getNextJarEntry();
if (jarEntry == null) {
break;
}
String name = jarEntry.getName();
if (name.startsWith(packagePath) && (name.endsWith(CLASSFILE_EXTENSION))) {
int endIndex = name.length() - 6;
name = name.replaceAll("/", "\\.");
name = name.substring(0, endIndex);
classNames.add(name);
}
}
jarFile.close();
return (String[]) classNames.toArray(new String[0]);
}
/**
* FileFilter to filter out GemFire Test Code.
*
* @since GemFire 7.0
*/
static class TestClassFilter implements FileFilter {
private static final String TESTS_CODE_INDICATOR = "Test";
@Override
public boolean accept(File pathname) {
String pathToCheck = pathname.getName();
return !pathToCheck.contains(TESTS_CODE_INDICATOR)
&& pathToCheck.endsWith(CLASSFILE_EXTENSION);
}
}
}