This question already has answers here:
How to read text file from classpath in Java?
(17 answers)
How to load/reference a file as a File instance from the classpath
(4 answers)
Get a list of resources from classpath directory
(15 answers)
How to locate all resources in classpath with a specified name?
(3 answers)
How to list the files inside a JAR file?
(17 answers)
Closed 4 years ago.
I make a Spring Boot web application, and I have to read static (.json) files on startup.
No problem, it works when I start the app in Idea. But if I run mvn package, and start I start the jar, the given path (BASE_DIR) does not exists anymore, so the files cannot be found.
How can I solve to read the files starting with Idea and the .jar as well?
package skyxplore.dataaccess.gamedata.base;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import org.apache.commons.io.FilenameUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import skyxplore.dataaccess.gamedata.entity.abstractentity.GeneralDescription;
#Slf4j
public abstract class AbstractGameDataService<V> extends HashMap<String, V> {
public static final String BASE_DIR = "src/main/resources/data/gamedata/";
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final JsonFileFilter jsonFilter = new JsonFileFilter();
private final String source;
public AbstractGameDataService(String source) {
this.source = BASE_DIR + source;
}
protected void loadFiles(Class<V> clazz) {
File root = new File(source);
if (!root.exists()) {
throw new IllegalStateException("Source directory does not exists. Path: " + root.getAbsolutePath());
}
if (!root.isDirectory()) {
throw new IllegalArgumentException("Source must be a directory. Path: " + root.getAbsolutePath());
}
File[] files = root.listFiles(jsonFilter);
for (File file : files) {
try {
String key = FilenameUtils.removeExtension(file.getName());
V content = objectMapper.readValue(file, clazz);
if (content instanceof GeneralDescription) {
GeneralDescription d = (GeneralDescription) content;
log.info("Loaded element. Key: {}, Value: {}", key, content);
put(d.getId(), content);
} else {
throw new RuntimeException(source + " cannot be loaded. Unknown data type.");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public abstract void init();
}
After you package your applications, resource files are no longer accessible from file system - thus you cannot list/open them using File. You need to read them as resources from JAR using class.readResourceAsStream for example. When you run it from IntelliJ files are not read from JAR but from project directory.
Here you have Oracles Java tutorial on how to use resources
Another solution (as you have some additional logic for reading files) you could put files aside JAR not as resource and read it from there then.
BASE_DIR = "src/main/resources/data/gamedata/"
Once you do a mvn package, src/main/resources folder becomes the root of the jar. You can use a class-loader to load this file. Something like:
AbstractGameDataService.class.getResourceAsStream("/data/gamedata/<your json file>")
Other possibility since you are using spring-boot, you can leverage spring scanning itself. Using spring constructs load file using : classpath:/data/gamedata/<your json file>
Related
I downloaded a text file by a click button functionality, using Selenium Java.
then the file is downloaded to a particular location in the system, for example,
C://myAppfiles.
But I can't access that downloaded folder because of some reason. But I have to read that file while downloading.
How to do it? is it possible to read that file from the browser(chrome) using selenium or any other method is available?
so I'd suggest to do the following:
wait until file download is done completely.
After that- try to list all the files in the given directory:
all files inside folder and sub-folder
public static void main(String[]args)
{
File curDir = new File(".");
getAllFiles(curDir);
}
private static void getAllFiles(File curDir) {
File[] filesList = curDir.listFiles();
for(File f : filesList){
if(f.isDirectory())
getAllFiles(f);
if(f.isFile()){
System.out.println(f.getName());
}
}
}
files/folder only
public static void main(String[]args)
{
File curDir = new File(".");
getAllFiles(curDir);
}
private static void getAllFiles(File curDir) {
File[] filesList = curDir.listFiles();
for(File f : filesList){
if(f.isDirectory())
System.out.println(f.getName());
if(f.isFile()){
System.out.println(f.getName());
}
}
}
That will help You to understand if there any files at all (in the given directory).
Dont forget to make paths platform independent (to the folder/ file), like:
//platform independent and safe to use across Unix and Windows
File fileSafe = new File("tmp"+File.separator+"myDownloadedFile.txt");
Also, You might want to check whether file actually exists via Path methods.
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
public class Main {
public static void main(String[] args) throws Exception {
Path filePath= Paths.get("C:\\myAppfiles\\downloaded.txt");
System.out.println("if exists: " + Files.exists(firstPath));
}
}
Additionally, path suggests You to check some other options on the file:
The following code snippet verifies that a particular file exists and that the program has the ability to execute the file.
Path file = ...;
boolean isRegularExecutableFile = Files.isRegularFile(file) &
Files.isReadable(file) & Files.isExecutable(file);
Once You face any exception- feel free to post it here.
Hope this helps You
I'm trying to migrate a large codebase from maven to bazel and I've found that some of the tests write to target/classes and target/test-classes and the production code reads it as resources on the classpath. This is because maven surefire/failsafe run by default from the module directory and add target/classes and target/test-classes to the classpath.
For me to migrate this large codebase the only reasonable solution is to create target, target/classes and target/test-classes folders and add the last two to the classpath of the tests.
Any ideas on how this can be achieved?
Thanks
Another line of approach. Instead of generating a test suite, create a custom javaagent and a custom class loader. Use jvm_flags to setup and configure it.
The javaagent has a premain method. This sounds like a natural place to do things that happen before the regular main method, even if they don't have anything to do with class instrumentation, debugging, coverage gathering, or any other usual uses of javaagents.
The custom javaagent reads system property extra.dirs and creates directories specified there. It then reads property extra.link.path and creates the symbolic links as specified there, so I can place resources where the tests expect them, without having to copy them.
Classloader is needed so that we can amend the classpath at runtime without hacks. Great advantage is that this solution works on Java 10.
The custom classloader reads system property extra.class.path and (in effect) prepends it before what is in java.class.path.
Doing things this way means that standard bazel rules can be used.
BUILD
runtime_classgen_dirs = ":".join([
"target/classes",
"target/test-classes",
])
java_test(
...,
jvm_flags = [
# agent
"-javaagent:$(location //tools:test-agent_deploy.jar)",
"-Dextra.dirs=" + runtime_classgen_dirs,
# classloader
"-Djava.system.class.loader=ResourceJavaAgent",
"-Dextra.class.path=" + runtime_classgen_dirs,
],
,,,,
deps = [
# not runtime_deps, cause https://github.com/bazelbuild/bazel/issues/1566
"//tools:test-agent_deploy.jartest-agent_deploy.jar"
],
...,
)
tools/BUILD
java_binary(
name = "test-agent",
testonly = True,
srcs = ["ResourceJavaAgent.java"],
deploy_manifest_lines = ["Premain-Class: ResourceJavaAgent"],
main_class = "ResourceJavaAgent",
visibility = ["//visibility:public"],
)
tools/ResourceJavaAgent.java
import java.io.File;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
// https://stackoverflow.com/questions/60764/how-should-i-load-jars-dynamically-at-runtime
public class ResourceJavaAgent extends URLClassLoader {
private final ClassLoader parent;
public ResourceJavaAgent(ClassLoader parent) throws MalformedURLException {
super(buildClassPath(), null);
this.parent = parent; // I need the parent as backup for SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
System.out.println("initializing url classloader");
}
private static URL[] buildClassPath() throws MalformedURLException {
final String JAVA_CLASS_PATH = "java.class.path";
final String EXTRA_CLASS_PATH = "extra.class.path";
List<String> paths = new LinkedList<>();
paths.addAll(Arrays.asList(System.getProperty(EXTRA_CLASS_PATH, "").split(File.pathSeparator)));
paths.addAll(Arrays.asList(System.getProperty(JAVA_CLASS_PATH, "").split(File.pathSeparator)));
URL[] urls = new URL[paths.size()];
for (int i = 0; i < paths.size(); i++) {
urls[i] = Paths.get(paths.get(i)).toUri().toURL(); // important only for resource url, really: this url must be absolute, to pass getClass().getResource("/users.properties").toURI()) with uri that isOpaque == false.
// System.out.println(urls[i]);
}
// this is for spawnVM functionality in tests
System.setProperty(JAVA_CLASS_PATH, System.getProperty(EXTRA_CLASS_PATH, "") + File.pathSeparator + System.getProperty(JAVA_CLASS_PATH));
return urls;
}
#Override
public Class<?> loadClass(String s) throws ClassNotFoundException {
try {
return super.loadClass(s);
} catch (ClassNotFoundException e) {
return parent.loadClass(s); // we search parent second, not first, as the default URLClassLoader would
}
}
private static void createRequestedDirs() {
for (String path : System.getProperty("extra.dirs", "").split(File.pathSeparator)) {
new File(path).mkdirs();
}
}
private static void createRequestedLinks() {
String linkPaths = System.getProperty("extra.link.path", null);
if (linkPaths == null) {
return;
}
for (String linkPath : linkPaths.split(",")) {
String[] fromTo = linkPath.split(":");
Path from = Paths.get(fromTo[0]);
Path to = Paths.get(fromTo[1]);
try {
Files.createSymbolicLink(from.toAbsolutePath(), to.toAbsolutePath());
} catch (IOException e) {
throw new IllegalArgumentException("Unable to create link " + linkPath, e);
}
}
}
public static void premain(String args, Instrumentation instrumentation) throws Exception {
createRequestedDirs();
createRequestedLinks();
}
}
If you could tell the tests where to write these files (in case target/classes and target/test-classes are hardcoded), and then turn the test run into a genrule, then you can specify the genrule's outputs as data for the production binary's *_binary rule.
I solved the first part, creating the directories. I still don't know how to add the latter two to classpath.
Starting from https://gerrit.googlesource.com/bazlets/+/master/tools/junit.bzl, I modified it to read
_OUTPUT = """import org.junit.runners.Suite;
import org.junit.runner.RunWith;
import org.junit.BeforeClass;
import java.io.File;
#RunWith(Suite.class)
#Suite.SuiteClasses({%s})
public class %s {
#BeforeClass
public static void setUp() throws Exception {
new File("./target").mkdir();
}
}
"""
_PREFIXES = ("org", "com", "edu")
# ...
I added the #BeforeClass setUp method.
I stored this as junit.bzl into third_party directory in my project.
Then in a BUILD file,
load("//third_party:junit.bzl", "junit_tests")
junit_tests(
name = "my_bundled_test",
srcs = glob(["src/test/java/**/*.java"]),
data = glob(["src/test/resources/**"]),
resources = glob(["src/test/resources/**"]),
tags = [
# ...
],
runtime_deps = [
# ...
],
],
deps = [
# ...
],
)
Now the test itself is wrapped with a setUp method which will create a directory for me. I am not deleting them afterwards, which is probably a sound idea to do.
The reason I need test resources in a directory (as opposed to in a jar file, which bazel gives by default) is that my test passes the URI to new FileInputStream(new File(uri)). If the file resides in a JAR, the URI will be file:/path/to/my.jar!/my.file and the rest of the test cannot work with such URI.
I've made a project in java, using Eclipse.
Here is the project structure:
When I'm running the project in Eclipse as a java application, it runs perfectly fine.
Now, I need to export it as a jar. So, I created the jar using the method described in 3rd answer on this link (answered by Fever):
Failing to run jar file from command line: “no main manifest attribute”
Here is the output of jar tf EventLogger.jar:
META-INF/MANIFEST.MF
com/
com/project/
com/project/eventLogger/
com/project/eventLogger/KafkaConsumerGroup.class
com/project/eventLogger/KafkaProducer.class
com/project/eventLogger/ConsumeConfig.class
com/project/eventLogger/ConsumerThread.class
com/project/eventLogger/Formatter.class
com/project/eventLogger/Execute.class
com/project/eventLogger/Config.class
com/project/eventLogger/package-info.class
com/project/eventLogger/ProdConfig.class
com/project/eventLogger/FormatConfig.class
resources/
resources/Config.properties
resources/ConsumerConfig.properties
resources/FormatterConfig.properties
resources/ProducerConfig.properties
resources/log4j.properties
Here is the manifest file:
Manifest-Version: 1.0
Built-By: vishrant
Class-Path: lib/json-simple-1.1.1.jar lib/junit-4.10.jar lib/hamcrest-
core-1.1.jar lib/kafka_2.9.2-0.8.2.2.jar lib/jopt-simple-3.2.jar lib/
kafka-clients-0.8.2.2.jar lib/log4j-1.2.16.jar lib/lz4-1.2.0.jar lib/
metrics-core-2.2.0.jar lib/slf4j-api-1.7.6.jar lib/snappy-java-1.1.1.
7.jar lib/slf4j-log4j12-1.6.1.jar lib/zkclient-0.3.jar lib/zookeeper-
3.4.6.jar lib/jline-0.9.94.jar lib/netty-3.7.0.Final.jar lib/scala-li
brary-2.9.2-RC3.jar
Build-Jdk: 1.8.0_74
Created-By: Maven Integration for Eclipse
Main-Class: com.project.eventLogger.Execute
and, here is the exception:
java.io.FileNotFoundException: ConsumerConfig.properties (No such file or directory)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at java.io.FileInputStream.<init>(FileInputStream.java:93)
at com.project.eventLogger.ConsumeConfig.loadPropertiesFile(ConsumeConfig.java:34)
at com.project.eventLogger.ConsumeConfig.<clinit>(ConsumeConfig.java:42)
at com.project.eventLogger.Execute.main(Execute.java:18)
Exception in thread "main" java.lang.ExceptionInInitializerError
at com.project.eventLogger.Execute.main(Execute.java:18)
Caused by: java.lang.NullPointerException
at com.project.eventLogger.ConsumeConfig.<clinit>(ConsumeConfig.java:47)
... 1 more
Seeing the exception, it is clear that it is not able to load ConsumerConfig.properties which is being done in ConsumeConfig.java.
Here is ConsumeConfig.java:
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Properties;
/**
* #author vishrant
*
*/
public class ConsumeConfig {
public static String zookeeper;
public static String balance;
public static String bootstrap_servers;
public static String zk_session_to;
public static String zk_sync;
public static String auto_commit;
public static String[] topics;
private static String kafka_bin;
private static final String PROPERTIES_FILE_PATH = "src/main/resources/ConsumerConfig.properties";
private static Properties loadPropertiesFile() throws IOException {
Properties properties = new Properties();
InputStream in = new FileInputStream(PROPERTIES_FILE_PATH);
properties.load(in);
return properties;
}
static {
Properties property = null;
try {
property = loadPropertiesFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
zookeeper = property.getProperty("ZOOKEEPER");
balance = property.getProperty("BALANCE");
bootstrap_servers = property.getProperty("BOOTSTRAP_SERVERS");
zk_session_to = property.getProperty("ZK_SESSION_TO");
zk_sync = property.getProperty("ZK_SYNC_TIME");
auto_commit = property.getProperty("AUTO_COMMIT_INTERVAL");
topics = property.getProperty("CONSUMER_TOPICS").split(",");
kafka_bin = property.getProperty("KAFKA_BIN_PATH");
}
}
Can someone tell me what is the problem and how to resolve this?
This runs perfectly well when run in Eclipse itself.
EDIT1:
Now, the exception is:
Exception in thread "main" java.lang.ExceptionInInitializerError
at com.project.eventLogger.Execute.main(Execute.java:18)
Caused by: java.lang.NullPointerException
at java.util.Properties$LineReader.readLine(Properties.java:434)
at java.util.Properties.load0(Properties.java:353)
at java.util.Properties.load(Properties.java:341)
at com.project.eventLogger.ConsumeConfig.loadPropertiesFile(ConsumeConfig.java:35)
at com.project.eventLogger.ConsumeConfig.<clinit> (ConsumeConfig.java:42)
... 1 more
line no 35:
props.load(resourceStream);
This is the code now:
private static final String PROPERTIES_FILE_PATH = "ConsumerConfig.properties";
private static Properties loadPropertiesFile() throws IOException {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Properties props = new Properties();
InputStream resourceStream = loader.getResourceAsStream(PROPERTIES_FILE_PATH);
props.load(resourceStream);
return props;
}
I see the following problems to be the cause:
The jar file is not created correctly for a maven project because the directory resources will normally be not copied to the target directory as is, i.e., instead of
resources/Config.properties
it should look like without the resources directory:
Config.properties
directly under the root directory of the jar file.
The second point is you are using the following in your code
private static final String PROPERTIES_FILE_PATH = "src/main/resources/ConsumerConfig.properties";
This path will not be seen outside of your IDE (in this case Eclipse) because src/main/resources should not exist in the jar file as you could see in the list of your jar file.
The last point is, you should use getResourceAsStream() of the class loader as Vikrant Kashyap already pointed.
try this
// Change Your File Path First.
private static final String PROPERTIES_FILE_PATH = "ConsumerConfig.properties";
private static Properties loadPropertiesFile() throws IOException {
Properties properties = new Properties();
// First way to load ResourceAsStream.
// ClassLoader loader = Thread.currentThread().getContextClassLoader();
// InputStream resourceStream = loader.getResourceAsStream(PROPERTIES_FILE_PATH);
// Second way to load ResourceAsStream.
InputStream resourceStream = ConsumeConfig.class.getResourceAsStream(PROPERTIES_FILE_PATH);
properties.load(resourceStream);
return properties;
}
i get the error "AWT-EventQueue-0 java.lang.IllegalArgumentException: URI is not hierarchical".
-I'm trying to use the java.awt.Desktop api to open a text file with the OS's default application.
-The application i'm running is launched from the autorunning jar.
I understand that getting a "file from a file" is not the correct way and that it's called resource. I still can't open it and can't figure out how to do this.
open(new File((this.getClass().getResource("prova.txt")).toURI()));
Is there a way to open the resource with the standard os application from my application?
Thx :)
You'd have to extract the file from the Jar to the temp folder and open that temporary file, much like you would do with files in a Zip-file (which a Jar basically is).
You do not have to extract file to /tmp folder. You can read it directly using `getClass().getResourceAsStream()'. But note that path depend on where your txt file is and what's your class' package. If your txt file is packaged in root of jar use '"/prova.txt"'. (pay attention on leading slash).
I don't think you can open it with external applications. As far as i know, all installers extract their compressed content to a temp location and delete them afterwards.
But you can do it inside your Java code with Class.getResource(String name)
http://download.oracle.com/javase/6/docs/api/java/lang/Class.html#getResource(java.lang.String)
Wrong
open(new File((this.getClass().getResource("prova.txt")).toURI()));
Right
/**
Do you accept the License Agreement of XYZ app.?
*/
import java.awt.Dimension;
import javax.swing.*;
import java.net.URL;
import java.io.File;
import java.io.IOException;
class ShowThyself {
public static void main(String[] args) throws Exception {
// get an URL to a document..
File file = new File("ShowThyself.java");
final URL url = file.toURI().toURL();
// ..then do this
SwingUtilities.invokeLater( new Runnable() {
public void run() {
JEditorPane license = new JEditorPane();
try {
license.setPage(url);
JScrollPane licenseScroll = new JScrollPane(license);
licenseScroll.setPreferredSize(new Dimension(305,90));
int result = JOptionPane.showConfirmDialog(
null,
licenseScroll,
"EULA",
JOptionPane.OK_CANCEL_OPTION);
if (result==JOptionPane.OK_OPTION) {
System.out.println("Install!");
} else {
System.out.println("Maybe later..");
}
} catch(IOException ioe) {
JOptionPane.showMessageDialog(
null,
"Could not read license!");
}
}
});
}
}
There is JarFile and JarEntry classes from JDK. This allows to load a file from JarFile.
JarFile jarFile = new JarFile("jar_file_Name");
JarEntry entry = jarFile.getJarEntry("resource_file_Name_inside_jar");
InputStream stream = jarFile.getInputStream(entry); // this input stream can be used for specific need
If what you're passing to can accept a java.net.URLthis will work:
this.getClass().getResource("prova.txt")).toURI().toURL()
Background:
One of the components of our project operates using spring. Some SQL code is dynamically generated, based on a given XML spring configuration.
At first it was fine to store all the XML configurations in the same package on the classpath, (and then load it as a resource when the service is called) but over time we ended up with a large number of configurations. It came time to separate the configurations into different namespaces.
The Goal
What I want is, given a starting package on the classpath, to recursively walk the directory structure and discover any spring XML files dynamically. (So that as new configurations / packages are added, the files will still be found by the service).
The Problem
I was able to accomplish my goal fine when running outside an EJB container by using Thread.getContextClassloader().getResource(myBasePackage), then getting a File object and using it to walk the tree on the filesystem. Clunky, I know, but it was still classpath relative and it worked.
However, you cannot do this inside an EJB container (you can't interact with the filesystem at all), so I had to use the rather annoying workaround in which I maintain a list of hardcoded packages to search.
The Question
Is there a way (running inside an EJB container) to dynamically walk the classpath (from a given starting location) searching for arbitrary resources?
Short answer: Not while staying in compliance with the EJB spec. Because the spec envisions containers running in all kinds of non-standard situations, it does not make this possible.
Longer answer: Since you are not creating these resources dynamically, I would write a routine that gives you a list of all of the resources at build time and puts them in a dynamically generated file that your EJB knows how to reference. So you basically create a directory listing of packages and files that you can load in the EJB that are referenced in one master file.
Spring answer: Spring supports finding resources on the classpath, although I have no idea how well this works in the EJB context (and I doubt its EJB compliant, but I haven't checked). Some details here.
DISCLAIMER: As already pointed out, creating resources in the classpath is not recommended and depending on the EJB container explicitly forbidden. This may cause you a lot of problems because containers may explode your resources into another folder or even replicate the resources throughout the cluster (if thats the case). In order to create resources dynamically you have to create a custom classloader. So, I would never do it. It is better to access the filesystem directly than the classpath. It is less ugly and eventually cluster-safe if you use a remote filesystem + file locks.
If even after all I explained you still want to play with the classpath, you can try to do something like: get the classloader via
ClassLoader cld = Thread.currentThread().getContextClassLoader();
Starting from a base package enumerate all occurrences
Enumeration<URL> basePackageUrls = cld.getResources(basePackagePath);
Each URL is generally either a file link (file:///home/scott/.../MyResource.properties) or a jar link (file:///lib.jar!/com/domain/MyResource.properties). You have to check the pattern in the URL. Using that, enumerate the contents of the folder using the normal java API and find the subpackages. Proceed until you have scanned all packages.
See the class below (will be released with an open-source project of mine soon). It implemens a classpath scanner that you can pass in a selector. It works like a visitor. It my work for you, if not, get ideas from it. See the sample annotation selector at the end.
public class ClasspathScanner
{
private static final Log log = LogFactory.getLog(ClasspathScanner.class);
private static final String JAR_FILE_PATTERN = ".jar!";
private ClassSelector selector;
private Set<Class<?>> classes;
// PUBLIC METHODS ------------------------------------------------------------------------------
public synchronized Set<Class<?>> scanPackage(String basePackage, ClassSelector selector)
throws Exception
{
if (selector == null)
{
throw new NullPointerException("Selector cannot be NULL");
}
this.selector = selector;
this.classes = new HashSet<Class<?>>();
Set<Class<?>> aux;
try
{
scanClasses0(basePackage);
aux = this.classes;
}
finally
{
this.selector = null;
this.classes = null;
}
return aux;
}
// HELPER CLASSES ------------------------------------------------------------------------------
private void scanClasses0(String basePackage)
throws IOException, ClassNotFoundException, FileNotFoundException
{
File packageDirectory = null;
ClassLoader cld = getLoader();
String basePackagePath = basePackage.replace('.', '/');
Enumeration<URL> basePackageUrls = cld.getResources(basePackagePath);
if (basePackageUrls == null || !basePackageUrls.hasMoreElements())
{
throw new ClassNotFoundException("Base package path not found: [" + basePackagePath
+ "]");
}
while (basePackageUrls.hasMoreElements())
{
String packagePath = basePackageUrls.nextElement().getFile();
if (packagePath.contains(JAR_FILE_PATTERN))
{
scanJarFile(basePackagePath, packagePath);
}
else
{
packageDirectory = new File(packagePath);
scanDirectory(basePackage, packageDirectory);
}
}
}
private void scanDirectory(String packageName, File packagePath)
throws ClassNotFoundException, FileNotFoundException
{
if (packagePath.exists())
{
File[] packageFiles = packagePath.listFiles();
for (File file : packageFiles)
{
if (file.isFile() && file.getName().endsWith(".class"))
{
String fullFileName = packageName + '.' + file.getName();
checkClass(fullFileName);
}
else if (file.isDirectory())
{
scanDirectory(packageName + "." + file.getName(), file);
}
}
}
else
{
throw new FileNotFoundException(packagePath.getPath());
}
}
private void scanJarFile(String basePackagePath, String jarFileUrl)
throws IOException, ClassNotFoundException
{
String jarFilePath = jarFileUrl.substring("file:".length(), jarFileUrl
.indexOf(JAR_FILE_PATTERN)
+ JAR_FILE_PATTERN.length() - 1);
log.debug("URL JAR file path: [" + jarFilePath + "]");
jarFilePath = URLDecoder.decode(jarFilePath, "UTF-8");
log.debug("Decoded JAR file path: [" + jarFilePath + "]");
JarFile jar = new JarFile(new File(jarFilePath));
for (Enumeration<JarEntry> jarFiles = jar.entries(); jarFiles.hasMoreElements();)
{
JarEntry file = jarFiles.nextElement();
String fileName = file.getName();
if (!file.isDirectory() && fileName.endsWith(".class")
&& fileName.startsWith(basePackagePath))
{
String className = fileName.replace('/', '.');
checkClass(className);
}
}
}
private void checkClass(String fullFilePath) throws ClassNotFoundException
{
String className = fullFilePath.substring(0, fullFilePath.length() - 6);
Class<?> c = getLoader().loadClass(className);
if (selector.select(c))
{
classes.add(c);
}
}
private ClassLoader getLoader()
{
ClassLoader loader = Thread.currentThread().getContextClassLoader();
if (loader == null)
{
loader = getClass().getClassLoader();
}
return loader;
}
// INNER CLASSES -------------------------------------------------------------------------------
public interface ClassSelector
{
boolean select(Class<?> clazz);
}
public static class AnnotatedClassSelector implements ClassSelector
{
private final Class<? extends Annotation>[] annotations;
public AnnotatedClassSelector(Class<? extends Annotation>... annotations)
{
this.annotations = annotations;
}
public boolean select(Class<?> clazz)
{
for (Class<? extends Annotation> ac : annotations)
{
if (clazz.isAnnotationPresent(ac))
{
return true;
}
}
return false;
}
}
}