This question already has answers here:
Can you find all classes in a package using reflection?
(30 answers)
Closed 9 years ago.
So I have a package that has classes that extend JPanel and I want to add them as tabs dynamically. At the beginning I used a factory and I registered all the classes in it and it worked, but now I want load all the classes in the package without knowing their names. I've tried several things including Reflections library (which I found very confusing) and I couldn't get them to work. I appreciate any help.
Here's one of my trials:
public static void registerTab() {
String pkg = TabA.class.getPackage().getName();
String relPath = pkg.replace('.', '/');
URL resource = ClassLoader.getSystemClassLoader().getResource(relPath);
if (resource == null) {
throw new RuntimeException("Unexpected problem: No resource for "
+ relPath);
}
File f = new File(resource.getPath());
String[] files = f.list();
for (int i = 0; i < files.length; i++) {
String fileName = files[i];
String className = null;
String fileNm = null;
if (fileName.endsWith(".class")) {
fileNm = fileName.substring(0, fileName.length() - 6);
className = pkg + '.' + fileNm;
}
if (className != null) {
if (!tabClasses.containsKey(className))
tabClasses.put(fileNm, className);
}
}
}
Here is a custom solution I developed to find all the classes of a package:
public class ClassFinder {
private static final char PKG_SEPARATOR = '.';
private static final char DIR_SEPARATOR = '/';
private static final String CLASS_FILE_SUFFIX = ".class";
private static final String BAD_PACKAGE_ERROR = "Unable to get resources from path '%s'. Are you sure the package '%s' exists?";
public static List<Class<?>> find(String scannedPackage) {
String scannedPath = scannedPackage.replace(PKG_SEPARATOR, DIR_SEPARATOR);
URL scannedUrl = Thread.currentThread().getContextClassLoader().getResource(scannedPath);
if (scannedUrl == null) {
throw new IllegalArgumentException(String.format(BAD_PACKAGE_ERROR, scannedPath, scannedPackage));
}
File scannedDir = new File(scannedUrl.getFile());
List<Class<?>> classes = new ArrayList<Class<?>>();
for (File file : scannedDir.listFiles()) {
classes.addAll(find(file, scannedPackage));
}
return classes;
}
private static List<Class<?>> find(File file, String scannedPackage) {
List<Class<?>> classes = new ArrayList<Class<?>>();
String resource = scannedPackage + PKG_SEPARATOR + file.getName();
if (file.isDirectory()) {
for (File child : file.listFiles()) {
classes.addAll(find(child, resource));
}
} else if (resource.endsWith(CLASS_FILE_SUFFIX)) {
int endIndex = resource.length() - CLASS_FILE_SUFFIX.length();
String className = resource.substring(0, endIndex);
try {
classes.add(Class.forName(className));
} catch (ClassNotFoundException ignore) {
}
}
return classes;
}
}
Then, just use:
List<Class<?>> classes = ClassFinder.find("com.package");
Related
I have a small program that attempts to allow plugins via class files copied to a specific ext directory.
The program is derived from https://javaranch.com/journal/200607/Plugins.html and I have attempted to simplify it and add on a directory scanning ability to scan packages and directories that the original code lacks.
When running the original code, it works. When I add on my directory and package scanning capability and test it on a demo package, it fails. Below are the samples.
The directory layout of the system accepting dynamically loaded class files as plugins:
testpack-+
|
+---PluginDemo.java
|
+---PluginFunction.java
The test plugin's directory layout:
b-+
|
+---Fibonacci.java
testpack-+
|
+---PluginFunction.java
The PluginDemo code with custom class loader:
package testpack;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.*;
public class PluginDemo extends ClassLoader {
static String pluginsDir = "ext";
static File basePluginDir = null;
File directory;
static List plugins;
public static void main(String args[]) {
PluginDemo demo = new PluginDemo();
basePluginDir = new File(System.getProperty("user.dir") + File.separator + pluginsDir);
demo.getPlugins(pluginsDir, "");
}
PluginDemo() {
plugins = new ArrayList();
}
protected void getPlugins(String directory, String parent) {
File dir = new File(System.getProperty("user.dir") + File.separator + directory);
if (dir.exists() && dir.isDirectory()) {
String[] files = dir.list();
for (int i = 0; i < files.length; i++) {
try {
// Allows recursive targetting of nested directories
String newTargetFile = System.getProperty("user.dir") + File.separator + directory + File.separator
+ files[i];
System.out.println("Targetting: " + newTargetFile);
File currentTarget = new File(newTargetFile);
if (currentTarget.isDirectory()) {
String newDirectoryTarget = directory + File.separator + files[i];
getPlugins(newDirectoryTarget, files[i]);
}
if (!files[i].endsWith(".class"))
continue;
String childFile = parent + File.separator + files[i].substring(0, files[i].indexOf("."));
Class c = loadClass(childFile);
Class[] intf = c.getInterfaces();
for (int j = 0; j < intf.length; j++) {
if (intf[j].getName().equals("PluginFunction")) {
PluginFunction pf = (PluginFunction) c.newInstance();
plugins.add(pf);
continue;
}
}
} catch (Exception ex) {
System.err.println("File " + files[i] + " does not contain a valid PluginFunction class.");
ex.printStackTrace();
}
}
}
}
public Class loadClass(String name) throws ClassNotFoundException {
return loadClass(name, true);
}
public Class loadClass(String classname, boolean resolve) throws ClassNotFoundException {
try {
Class c = findLoadedClass(classname);
if (c == null) {
try {
c = findSystemClass(classname);
} catch (Exception ex) {
}
}
if (c == null) {
String filename = classname.replace('.', File.separatorChar) + ".class";
// Create a File object. Interpret the filename relative to the
// directory specified for this ClassLoader.
File baseDir = new File(System.getProperty("user.dir"));
File f = new File(baseDir, PluginDemo.pluginsDir + File.separator + filename);
int length = (int) f.length();
byte[] classbytes = new byte[length];
DataInputStream in = new DataInputStream(new FileInputStream(f));
in.readFully(classbytes);
in.close();
c = defineClass(classname, classbytes, 0, length);
}
if (resolve)
resolveClass(c);
return c;
} catch (Exception ex) {
throw new ClassNotFoundException(ex.toString());
}
}
}
The PluginFunction interface code:
package testpack;
public interface PluginFunction {
// let the application pass in a parameter
public void setParameter (int param);
// retrieve a result from the plugin
public int getResult();
// return the name of this plugin
public String getPluginName();
// can be called to determine whether the plugin
// aborted execution due to an error condition
public boolean hasError();
}
The Fibonacci.java code:
package b;
import testpack.PluginFunction;
public class Fibonacci implements PluginFunction {
int parameter = 0;
boolean hasError = false;
public boolean hasError() {
return hasError;
}
public void setParameter (int param) {
parameter = param;
}
public int getResult() {
hasError = false;
return fib(parameter);
}
protected int fib (int n) {
if (n < 0) {
hasError = true;
return 0;
}
if (n == 0)
return 0;
else if (n == 1)
return 1;
else
return fib(n-1) + fib(n-2);
}
public String getPluginName() {
return "Fibonacci";
}
}
The output with errors:
Targetting: C:\Users\Administrator\eclipse-workspace\TestPluginSystem\ext\b\Fibonacci.class
Exception in thread "main" java.lang.NoClassDefFoundError: b\Fibonacci (wrong name: b/Fibonacci)
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
at testpack.PluginDemo.loadClass(PluginDemo.java:89)
at testpack.PluginDemo.loadClass(PluginDemo.java:65)
at testpack.PluginDemo.getPlugins(PluginDemo.java:47)
at testpack.PluginDemo.getPlugins(PluginDemo.java:40)
at testpack.PluginDemo.main(PluginDemo.java:19)
I would need help to get this package and directory scanning capable classloader working. Thanks.
Looking at the error and method ClassLoader.defineClass, I think the name parameter must have . as package separators, not / or \.
In your code in getPlugins the childFile is constructed using File.separator
String childFile = parent + File.separator + files[i].substring(0, files[i].indexOf("."));
Class c = loadClass(childFile);
I am doing a memory game and in one of my method, I'm trying to create all cards by using File, but it could not run. It always goes to "Picture path is empty", but I want it to run.
/*
Create all Cards.
*/
public static List<Card> createAllCards(String dirPath){
//System.out.println("create all cards");
List<Card> cardList = new ArrayList<>();
File file = new File(dirPath);
File[] pictures = file.listFiles();
//System.out.println("file:" + Arrays.toString(pictures));
int index;
String type = "";
String cardPath;
if (pictures != null){
for (File picture : pictures) {
index = picture.getName().lastIndexOf(".");
if (picture.isFile()){
if (index > 0) {
type = picture.getName().substring(index + 1);
System.out.println("output:" + type);
if (type.equals("png")){
cardPath = picture.getPath();
//cardPath = cardPath.replaceAll("\\\\","\\\\\\\\");
cardList.add(new Card(picture.getName(),cardPath));
}
}
}
}
}else {
System.out.println("Picture path is empty");
}
return cardList;
}
}
The java.io.File class is obsolete. The java.nio.file package is its replacement.
The File class was part of Java 1.0 and did a poor job of reporting errors. Many of its methods return null or false, which tells you nothing about what actually went wrong. The classes in java.nio.file will actually throw exceptions telling you exactly what went wrong.
Specifically, you want to use the Path and Files classes:
try (DirectoryStream<Path> pictures =
Files.newDirectoryStream(Paths.get(dirPath), "*.png")) {
for (Path picture : pictures) {
if (Files.isRegularFile(picture)) {
cardList.add(
new Card(picture.getFileName().toString(), cardPath));
}
}
} catch (IOException e) {
e.printStackTrace();
}
This probably won’t make your program work, but it will give you the information you need to resolve the problem.
Okay, I used your code and modified this, as per your requirement.
Have a look:
NOTE: My photos folder is in the room directory
import java.io.File;
import java.util.ArrayList;
import java.util.List;
class Card{
String photoName,cardPath;
public Card(String name, String cardPath2) {
this.photoName = name;
this.cardPath = cardPath2;
}
}
public class readPhotos {
public static List<Card> createAllCards(String dirPath){
//System.out.println("create all cards");
List<Card> cardList = new ArrayList<>();
File file = new File(dirPath);
File[] pictures = file.listFiles();
//System.out.println("file:" + Arrays.toString(pictures));
int index;
String type = "";
String cardPath;
if (pictures != null){
for (File picture : pictures) {
index = picture.getName().lastIndexOf(".");
if (picture.isFile()){
if (index > 0) {
type = picture.getName().substring(index + 1);
System.out.println("output:" + type);
if (type.equals("png")){
cardPath = picture.getPath();
//cardPath = cardPath.replaceAll("\\\\","\\\\\\\\");
cardList.add(new Card(picture.getName(),cardPath));
}
}
}
}
}else {
System.out.println("Picture path is empty");
}
return cardList;
}
public static void main(String...args)
{
List<Card> cardList = readPhotos.createAllCards("./photos/");
System.out.println(cardList);
}
}
add your photos path accordingly... when calling the method readPhotos.createAllCards("./photos/");
I am writing Java application and I want to write some simple plugin system. I want to have base class Plugin. Other classes extends Plugin, these files are in some other directory out of class path.
public class Plugin {
public Plugin() {
//code
}
public void proc() {
//code
}
}
and class loader:
public class PluginLoader {
private static final FilenameFilter filter = new FilenameFilter() {
#Override
public boolean accept(File dir, String name) {
return Pattern.matches("^.*[a-zA-Z]*[.]class$", name);
}
};
public static final String removeExtension(String str) {
if (str == null)
return null;
int pos = str.lastIndexOf(".");
if (pos == -1)
return str;
return str.substring(0, pos);
}
#SuppressWarnings("unchecked")
public static LinkedList<Plugin> loadEffects(String path) {
LinkedList<Plugin> result = new LinkedList<Plugin>();
Plugin instance = null;
File[] classesList = null;
System.out.println("Searching in " + path);
try {
File classDir = new File(path);
URL[] url = { classDir.toURI().toURL() };
URLClassLoader urlLoader = new URLClassLoader(url);
String filename;
classesList = classDir.listFiles(filter);
System.out.println(classesList.length + " class files found:");
for (File file : classesList) {
System.out.println("- " + file.getName());
}
for (File file : classesList) {
filename = removeExtension(file.getName());
if (filename.equals(".") || filename.equals("..") || filename.startsWith("."))
continue;
if (filename.equals("Plugin")) {
System.err.println("File name is Plugin");
continue;
}
System.out.println("Reading " + filename);
instance = (Plugin) urlLoader.findClass(filename).getConstructor().newInstance();
System.out.println("Adding: " + url + ", " + filename);
result.push(instance);
}
urlLoader.close();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
This code causes java.lang.NoClassDefFoundError: D:\test\PluginImpl/class (wrong name: test/PluginImpl). Plugin class is in D:\test.
You are calling File#getAbsolutePath() which will include the entire path and the drive letter (D:\). The class name stored in the class does not match with the class name you provided so Java throws an error.
Try calling findClass with just the class name and it should work
I'm having this weird issue where a class from some transitive dependency keeps showing up at runtime, shadowing a newer version of the class from the (correct) first level dependency, even though I thought I made sure that I excluded the older version from all other dependencies I declare (this is in a Maven/IntelliJ setup)
More specifically, at runtime the app fails with a NoClassDefFoundError, since during class loading a wrong version of the owning class is loaded, which has a field of a type that does not exist in newer versions of the library that class is defined in. To illustrate:
// lib.jar:wrong-version
class Owner {
private SomeType f;
}
// lib.jar:new-version
class Owner {
private OtherType f;
}
At runtime, the class loader finds a reference to the symbol Owner and attempts to load the version that has SomeType, which in return does not exist anymore. This is even though I excluded wrong-version where ever I could spot it.
I also ran mvn dependency:tree to see if the old version is still being pulled in somewhere, but it's not!
In order to further debug this, I was wondering if there is a way to find out where a class loader was reading a specific class from, i.e. which file? Is that possible? Or even better, build a list of origins where a certain symbol is defined, in case it's defined more than once?
Sorry if this is vague, but the problem is rather nebulous.
The following code will search the whole classpath for a particular class. With no arguments it will dump every class it finds and then you can pipe to grep or redirect to a file. It looks inside jars...
Usage: WhichClass or WhichClass package.name (note no .class)
Apologies for the lack of comments ...
import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public final class WhichClass {
private WhichClass() {
}
static Vector<String> scratchVector;
public static void main(final String[] argv) {
Vector v;
if ((argv.length == 0) || "-all".equals(argv[0])) {
v = findClass(null);
} else {
v = findClass(argv[0]);
}
for (int i = 0; i < v.size(); i++) {
System.out.println(v.elementAt(i));
}
}
static String className(final String classFile) {
return classFile.replace('/', '.').substring(0, classFile.length() - ".class".length());
}
static Vector findClass(final String className) {
if (className != null) {
scratchVector = new Vector<String>(5);
} else {
scratchVector = new Vector<String>(5000);
}
findClassInPath(className, setupBootClassPath());
findClassInPath(className, setupClassPath());
return scratchVector;
}
static void findClassInPath(final String className, final StringTokenizer path) {
while (path.hasMoreTokens()) {
String pathElement = path.nextToken();
File pathFile = new File(pathElement);
if (pathFile.isDirectory()) {
try {
if (className != null) {
String pathName = className.replace('.', System.getProperty("file.separator").charAt(0)) + ".class";
findClassInPathElement(pathName, pathElement, pathFile);
} else {
findClassInPathElement(className, pathElement, pathFile);
}
} catch (IOException e) {
e.printStackTrace();
}
} else if (pathFile.exists()) {
try {
if (className != null) {
String pathName = className.replace('.', '/') + ".class";
ZipFile zipFile = new ZipFile(pathFile);
ZipEntry zipEntry = zipFile.getEntry(pathName);
if (zipEntry != null) {
scratchVector.addElement(pathFile + "(" + zipEntry + ")");
}
} else {
ZipFile zipFile = new ZipFile(pathFile);
Enumeration entries = zipFile.entries();
while (entries.hasMoreElements()) {
String entry = entries.nextElement().toString();
if (entry.endsWith(".class")) {
String name = className(entry);
scratchVector.addElement(pathFile + "(" + entry + ")");
}
}
}
} catch (IOException e) {
System.err.println(e + " while working on " + pathFile);
}
}
}
}
static void findClassInPathElement(final String pathName, final String pathElement, final File pathFile)
throws IOException {
String[] list = pathFile.list();
for (int i = 0; i < list.length; i++) {
File file = new File(pathFile, list[i]);
if (file.isDirectory()) {
findClassInPathElement(pathName, pathElement, file);
} else if (file.exists() && (file.length() != 0) && list[i].endsWith(".class")) {
String classFile = file.toString().substring(pathElement.length() + 1);
String name = className(classFile);
if (pathName != null) {
if (classFile.equals(pathName)) {
scratchVector.addElement(file.toString());
}
} else {
scratchVector.addElement(file.toString());
}
}
}
}
static StringTokenizer setupBootClassPath() {
String classPath = System.getProperty("sun.boot.class.path");
String separator = System.getProperty("path.separator");
return new StringTokenizer(classPath, separator);
}
static StringTokenizer setupClassPath() {
String classPath = System.getProperty("java.class.path");
String separator = System.getProperty("path.separator");
return new StringTokenizer(classPath, separator);
}
}
If you know the fully qualified name of the class, say somelib.Owner, you can try calling the following in your code:
public void foo() {
URL url = somelib.Owner.class.getClassLoader().getResource("somelib/Owner.class");
System.out.println(url);
}
I have used this code to get a list of class names from a package:
private List<String> getClasses()
{
List<String> classes = new ArrayList<String>();
String packageName = "algorithm/impl";
URL directoryUrl = Thread.currentThread().getContextClassLoader().
getResource(packageName);
File directory = new File(directoryUrl.getFile());
if(directory.exists())
{
String [] files = directory.list();
for(String filename : files)
{
classes.add(filename.substring(0, filename.lastIndexOf(".")));
}
}
return classes;
}
but this does not work when the app is packaged as an executable jar file. Why?
You can make use of this class JarFile.
JarFile file = new JarFile("YourFileName.jar");
for (Enumeration<JarEntry> enum = file.entries(); enum.hasMoreElements();) {
JarEntry entry = enum.next();
System.out.println(entry.getName());
}
Or if you want to search for particular class inside your jar you can use ZipFile class.
JarFile jar = new JarFile(YourJarFile);
ZipEntry e = jar.getEntry(CLASS_FILE_TO_FIND);
if (e == null) {
e = jar.getJarEntry(CLASS_FILE_TO_FIND);
if (e != null) {
foundIn.add(f.getPath());
}
} else {
foundIn.add(f.getPath());
}