Hot loading .jar files: random FileNotFoundException - java

I have a PluginManager class that watches a ./plugins directory for file creation using a WatchServiceand then utilizes a static method loadPlugin(File) from a PluginLoader class to load newly added jars at runtime. All jars in the folder are also loaded and started at application start up, in that case everything goes well, even with a bunch of plugins.
But when I drop the plugins into the folder one by one I get a weird behavior:
the first jar is loaded fine in 98% of the time
the second only in about 5%
the third only in a very rare case, but it happens
What I get is this:
java.io.FileNotFoundException: .\plugins\test2.jar (Der Prozess kann nicht auf die Datei zugreifen, da sie von einem anderen Prozess verwendet wird)
at java.util.zip.ZipFile.open(Native Method)
at java.util.zip.ZipFile.<init>(ZipFile.java:220)
at java.util.zip.ZipFile.<init>(ZipFile.java:150)
at java.util.jar.JarFile.<init>(JarFile.java:166)
at java.util.jar.JarFile.<init>(JarFile.java:130)
at PluginLoader.loadPlugin(PluginLoader.java:34)
at PluginManager$WatchQueueReader.run(PluginManager.java:118)
at java.lang.Thread.run(Thread.java:745)
Exception in thread "FileWatcher" java.lang.NullPointerException
at PluginLoader.loadPlugin(PluginLoader.java:41)
at PluginManager$WatchQueueReader.run(PluginManager.java:118)
at java.lang.Thread.run(Thread.java:745)
It is saying that the file is used in another process.
PluginLoader:
public static BasePlugin loadPlugin(File pluginJar) {
Attributes attrib = null;
JarFile file = null;
try {
file = new JarFile(pluginJar);
Manifest manifest = file.getManifest();
attrib = manifest.getMainAttributes();
} catch (IOException e) {
e.printStackTrace();
}
String main = attrib.getValue(Attributes.Name.MAIN_CLASS);
String name = attrib.getValue("Plugin-Name");
if (main == null || name == null) {
System.out.println("Not a valid manifest: " + pluginJar.getName());
return null;
}
URLClassLoader loader = null;
try {
loader = URLClassLoader.newInstance(new URL[] { pluginJar.toURI().toURL() }, PluginLoader.class.getClassLoader());
} catch (MalformedURLException e2) {
e2.printStackTrace();
}
Class<?> cl = null;
try {
cl = Class.forName(main, true, loader);
} catch (ClassNotFoundException e1) {
e1.printStackTrace();
} finally {
try {
loader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
Class<? extends BasePlugin> c = cl.asSubclass(BasePlugin.class);
Constructor<? extends BasePlugin> ctr = c.getConstructor(String.class, SystemManager.class);
return ctr.newInstance(name, this.sm);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
WatchService inside PluginManager:
private static class WatchQueueReader implements Runnable {
private WatchService watcher;
private PluginManager pm;
public WatchQueueReader(PluginManager pm, WatchService watcher) {
this.pm = pm;
this.watcher = watcher;
}
#Override
public void run() {
try {
WatchKey key = watcher.take();
while (key != null) {
for (WatchEvent<?> event : key.pollEvents()) {
switch (event.kind().toString()) {
case "ENTRY_CREATE":
Path dir = (Path) key.watchable();
Path fullPath = dir.resolve(event.context().toString());
BasePlugin plugin = PluginLoader.loadPlugin(fullPath.toFile());
if (plugin != null) {
this.pm.startPlugin(plugin);
}
break;
case "ENTRY_MODIFY":
break;
case "ENTRY_DELETE":
this.pm.stopPlugin(event.context().toString()); // TODO wrong name (.jar)
break;
default:
break;
}
}
key.reset();
key = watcher.take();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
BasePlugin is an abstract class that plugins extend.

The file is still being copied, the WatchService simply finds it too soon.
In fact, it will report the file as existing as soon as it['s inode] is created, but not yet filled with information (the bytes, you know).
My first tip is that if you encounter this exception, wait a few seconds and try again. Just after like 10-30 seconds of futile attempts you can give up, as it have been deleted maybe. But this part needs fine tuning, as maybe the copy operation is slow.

Related

User URLClassLoader to load jar file "on the fly"

Ok, basically, I try to use the method described here JarFileLoader to load a jar containing a class that will be used the same as if it was on the classpath (the class name will be dynamic so that we can just add any jar with any class and the program will load it through parsing a text file, in the main line).
Problem is that when I debug and check the URLClassLoader object
protected Class<?> findClass(final String name)
Line :
Resource res = ucp.getResource(path, false);
the getResource() does not find the class name in parameter.
Does someone already try loading a jar file this way ?
Thanks.
Loader :
public class JarFileLoader extends URLClassLoader {
public JarFileLoader() {
super(new URL[] {});
}
public JarFileLoader withFile(String jarFile) {
return withFile(new File(jarFile));
}
public JarFileLoader withFile(File jarFile) {
try {
if (jarFile.exists())
addURL(new URL("file://" + jarFile.getAbsolutePath() + "!/"));
} catch (MalformedURLException e) {
throw new IllegalArgumentException(e);
}
return this;
}
public JarFileLoader withLibDir(String path) {
Stream.of(new File(path).listFiles(f -> f.getName().endsWith(".jar"))).forEach(this::withFile);
return this;
}
}
Main :
public static void main(String[] args) {
new Initializer();
JarFileLoader cl = new JarFileLoader();
cl = cl.withFile(new File("libs/dpr-common.jar"));
try {
cl.loadClass("com.*****.atm.dpr.common.util.DPRConfigurationLoader");
System.out.println("Success!");
} catch (ClassNotFoundException e) {
System.out.println("Failed.");
e.printStackTrace();
} finally {
try {
cl.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Here the test class I used. When I debug URLClassLoader I can see in the third loop the path of the jar file(loop on the classpath and the URL you add here), but still does not find ressource (and cannot debug the class URLClassPath so do not know what getRessource does exactly).
Ok I take the answer from this question : How to load all the jars from a directory dynamically?
And changing the URL part at the beginning with the way it is done in the long part it works.
So an example could be :
String path = "libs/dpr-common.jar";
if (new File(path).exists()) {
URL myJarFile = new File(path).toURI().toURL();
URL[] urls = { myJarFile };
URLClassLoader child = new URLClassLoader(urls);
Class DPRConfLoad = Class.forName("com.thales.atm.dpr.common.util.DPRConfigurationLoader", true, child);
Method method = DPRConfLoad.getDeclaredMethod("getInstance");
final Object dprConf = method.invoke(DPRConfLoad);
}
All my time wasted in search while it was the example which was wrong... Still does not understand why they use a stupid URL like "jar:file..." etc.
Thanks everyone.

Issue with loading class from jar file represented as byte array

I'm trying to create an instance of a class from jar file loaded on a byte array.
I'm receiving two args:
1. byte[] which represents jar file with required class
2. Qualified class name
When I'm testing it locally it works as expected, but when I upload exactly the same jar file with the same qualified class name remotely (using web application implemented with Spring MVC for back and AngularJS for front end deployed in Tomcat server) It can't find the required class:
java.lang.ClassNotFoundException
When I was debugging, it turned out, that classloader is properly invoked but no one class is loaded from jar.
I would be grateful if anyone can tell what can be the reason of that difference or how can I implement this functionality in other ways.
A method which loads class and returns an instance of it:
public static <T> T getInstanceOfLoadedClass(byte[] jarFileBytes, String qualifiedClassName) throws ClassFromJarInstantiationException {
LOGGER.info("Getting instance of class from loaded jar file. Class name: " + qualifiedClassName);
try {
return (T) Class.forName(qualifiedClassName, true, new ByteClassLoader(jarFileBytes)).newInstance();
} catch (InstantiationException | IllegalAccessException | IOException | ClassNotFoundException | NoSuchFieldException e) {
LOGGER.error("Exception was thrown while reading jar file for " + qualifiedClassName + "class.", e);
throw new ClassFromJarInstantiationException(e);
}
}
Custom ByteClassLoader:
public class ByteClassLoader extends ClassLoader {
private static final Logger LOGGER = Logger.getLogger(ByteClassLoader.class);
private final byte[] jarBytes;
private final Set<String> names;
public ByteClassLoader(byte[] jarBytes) throws IOException {
this.jarBytes = jarBytes;
this.names = loadNames(jarBytes);
}
private Set<String> loadNames(byte[] jarBytes) throws IOException {
Set<String> set = new HashSet<>();
try (ZipInputStream jis = new ZipInputStream(new ByteArrayInputStream(jarBytes))) {
ZipEntry entry;
while ((entry = jis.getNextEntry()) != null) {
set.add(entry.getName());
}
}
return Collections.unmodifiableSet(set);
}
#Override
public InputStream getResourceAsStream(String resourceName) {
if (!names.contains(resourceName)) {
return null;
}
boolean found = false;
ZipInputStream zipInputStream = null;
try {
zipInputStream = new ZipInputStream(new ByteArrayInputStream(jarBytes));
ZipEntry entry;
while ((entry = zipInputStream.getNextEntry()) != null) {
if (entry.getName().equals(resourceName)) {
found = true;
return zipInputStream;
}
}
} catch (IOException e) {
LOGGER.error("ByteClassLoader threw exception while reading jar byte stream for resource: "+resourceName, e);
e.printStackTrace();
} finally {
if (zipInputStream != null && !found) {
try {
zipInputStream.close();
} catch (IOException e) {
LOGGER.error("ByteClassLoader threw exception while closing jar byte stream for resource: "+resourceName, e);
e.printStackTrace();
}
}
}
return null;
} }
The problem was that the class required to be loaded was in a range of classloader while it was tested.
Hope it helps someone in solving this problem because it is really easy to miss.

Java WatchService watches not terminated

I'm working on an application that uses the Java watchservice (Java 8) under Linux Mint. One interesting problem I am encountering is running out of inotify watches.
I'm developing under Eclipse and the behavior is as follows:
When the app starts, it recurses a directory structure, putting a watch on each directory found. The current test case uses 13,660 paths. My maximum is set to 16384.
If I stop and restart the app several (20+ times), it seems to function normally. Eventually, however, I will get a cascade of system errors indicating the maximum number of watches has been reached. However, if I restart Eclipse, the issue goes away.
Obviously, the Watch Service isn't releasing all of it's resources, but of the 13,660 watches it acquires, only a few (I'm guessing less than a hundred) are retained. It appears they aren't released unless I shut down Eclipse's Java instance and restart it.
To address this, I've ensured the watch service's close method is called when the application shuts down and the watch service task is cancelled.
The only other thing that I'm doing differently is I'm running two separate watch services for two different purposes. I'm told that you shouldn't need to run more than one, and perhaps this is the problem, but I'd rather not run one watch service if I can help it.
That said, are there any thoughts or suggestions on how I might be able to determine the cause of this bug?
Apologies for the massive code posting. This is my implementation of the WatchService class.
A few notes:
The pathFinder runs in a separate thread and is just a file visitor - walking the directory tree and returning paths to all dirs / files found.
Register is called only when changes are posted to the pathsChanged property (from the pathFinder's onSucceeded callback).
The pathsChanged property is always updated by a setAll() call. It only posts the latest changes and is not meant to be cumulative. Beyond the watchservice, other classes listen to these properties and respond accordingly.
public final class LocalWatchService extends BaseTask {
private final static String TAG = "LocalWatchService";
//watch service task
private WatchService watcher;
//path finding task and associated executor
private LocalPathFinder finder;
//root path where the watch service begins
private final Path mRootPath;
private final ExecutorService pathFinderExecutor =
createExecutor ("pathFinder", false);
//class hash map which keys watched paths to generated watch keys
private final Map<WatchKey, Path> keys = new HashMap<WatchKey, Path>();
//reference to model property of watched paths.
private final SimpleListProperty <SyncPath> mChangedPaths =
new SimpleListProperty <SyncPath>
(FXCollections.<SyncPath> observableArrayList());
public LocalWatchService (String rootPath) {
super ();
mRootPath = Paths.get(rootPath);
//create the watch service
try {
this.watcher = FileSystems.getDefault().newWatchService();
} catch (IOException e) {
e.printStackTrace();
}
setOnCancelled(new EventHandler() {
#Override
public void handle(Event arg0) {
pathFinderExecutor.shutdown();
}
});
mChangedPaths.addListener(new ListChangeListener <SyncPath> (){
#Override
public void onChanged(
javafx.collections.ListChangeListener.Change<? extends SyncPath>
arg0) {
for (SyncPath path: arg0.getList()) {
//call register only when a directory is found
if (path.getFile() == null) {
try {
register (path.getPath());
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
});
};
public SimpleListProperty<SyncPath> changedPaths() { return mChangedPaths; }
public void initializeWatchPaths() {
ArrayList <Path> paths = new ArrayList <Path> ();
//create a DirectoryStream filter that finds only directories
//and symlinks
DirectoryStream.Filter<Path> filter =
new DirectoryStream.Filter<Path>() {
public boolean accept(Path file) throws IOException {
return (Files.isDirectory(file) ||
Files.isSymbolicLink(file));
}
};
//apply the filter to a directory stream opened on the root path
//and save everything returned.
paths.addAll(utils.getFiles(mRootPath, filter));
runPathFinder (paths);
}
private void runPathFinder (ArrayList <Path> paths) {
//need to add blocking code / mechanism in case a path finder is
//currently running (rare case)
finder = new LocalPathFinder();
finder.setPaths (paths);
//callbacks on successful completion of pathfinder
EventHandler <WorkerStateEvent> eh =
new EventHandler <WorkerStateEvent> () {
ArrayList <SyncPath> paths = new ArrayList <SyncPath>();
#Override
public void handle(WorkerStateEvent arg0) {
for (Path p: finder.getPaths()) {
paths.add(
new SyncPath(mRootPath, p, SyncType.SYNC_NONE));
}
addPaths(paths);
}
};
finder.setOnSucceeded(eh);
pathFinderExecutor.execute (finder);
}
private void addPath(Path path, SyncType syncType) {
mChangedPaths.setAll(new SyncPath(mRootPath, path, syncType));
}
private void addPaths(ArrayList<SyncPath> paths) {
mChangedPaths.setAll(paths);
}
/**
* Register the given directory with the WatchService
* #throws InterruptedException
*/
public final void register(Path dir)
throws IOException, InterruptedException {
//register the key with the watch service
WatchKey key =
dir.register (watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
if (!keys.isEmpty()) {
Path prev = keys.get(key);
if (prev == null) {
//This is a new key
}
else if (!dir.equals(prev)) {
//This is an update
}
}
keys.put(key, dir);
}
private void processWatchEvent (WatchKey key, Path dir) throws IOException, InterruptedException {
for (WatchEvent<?> event: key.pollEvents()) {
WatchEvent.Kind kind = event.kind();
// TBD - provide example of how OVERFLOW event is handled
if (kind == OVERFLOW) {
System.out.println ("Overflow encountered");
}
WatchEvent<Path> ev = (WatchEvent<Path>)event;
Path target = dir.resolve(ev.context());
if (kind == ENTRY_DELETE) {
ArrayList <Path> finderList = new ArrayList <Path> ();
if (Files.isDirectory(target)) {
//directory deletion is not implemented apart from
//file deletion
}
else
addPath (target, SyncType.SYNC_DELETE);
} else if (kind == ENTRY_CREATE) {
/*
* Added paths are passed to the pathfinder service for
* subdirectory discovery. Path and subpaths are then added
* to the AddedPaths property via an event listener on
* service's onSucceeded() event.
*
* Added files are added directly to the AddedPaths property
*/
ArrayList <Path> finderList = new ArrayList <Path> ();
if (Files.isDirectory(target)) {
finderList.add (target);
runPathFinder (finderList);
}
//add files directly to the addedPaths property
else {
//a newly created file may not be immediately readable
if (Files.isReadable(target)) {
addPath (target, SyncType.SYNC_CREATE);
}
else
System.err.println ("File " + target + " cannot be read");
}
} else if (kind == ENTRY_MODIFY) {
System.out.println ("File modified: " + target.toString());
}
boolean valid = key.reset();
if (!valid)
break;
}
}
#SuppressWarnings("unchecked")
<T> WatchEvent<T> cast(WatchEvent<?> event) {
return (WatchEvent<T>)event;
}
#Override
protected Void call () throws IOException, InterruptedException {
boolean interrupted = false;
register (mRootPath);
initializeWatchPaths();
try {
// enter watch cycle
while (!interrupted) {
//watch for a key change. Thread blocks until a change occurs
WatchKey key = null;
interrupted = isCancelled();
//thread blocks until a key change occurs
// (whether a new path is processed by finder or a watched item changes otherwise)
try {
key = watcher.take();
} catch (InterruptedException e) {
interrupted = true;
try {
watcher.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
// fall through and retry
}
Path dir = keys.get (key);
if (dir == null) {
System.out.println ("Null directory key encountered.");
continue;
}
//process key change once it occurs
processWatchEvent(key, dir);
// reset key and remove from set if directory no longer accessible
if (!key.reset()) {
keys.remove(key);
// all directories are inaccessible
if (keys.isEmpty())
break;
}
}
} finally {
if (interrupted)
Thread.currentThread().interrupt();
}
keys.clear();
watcher.close();
return null;
};
}

FileNotFoundException with FileOutputStream open

I'm having a FileNotFoundException when i try to create a FileOutputStream. The file does exists according to file.exists. i've tried everything like file.mkdir(s) ...
I'm on a mac and i'm using gauva.
The file input is ''
java.io.FileNotFoundException: /Users/big_Xplosion/mods/Blaze-Installer/installer/test
at java.io.FileOutputStream.open(Native Method)
at java.io.FileOutputStream.<init>(FileOutputStream.java:194)
at com.google.common.io.Files$FileByteSink.openStream(Files.java:223)
at com.google.common.io.Files$FileByteSink.openStream(Files.java:211)
at com.google.common.io.ByteSource.copyTo(ByteSource.java:203)
at com.google.common.io.Files.copy(Files.java:382)
at com.big_Xplosion.blazeInstaller.util.DownloadUtil.downloadFile(DownloadUtil.java:80)
at com.big_Xplosion.blazeInstaller.action.MCPInstall.downloadMCP(MCPInstall.java:78)
at com.big_Xplosion.blazeInstaller.action.MCPInstall.install(MCPInstall.java:30)
at com.big_Xplosion.blazeInstaller.util.InstallType.install(InstallType.java:37)
at com.big_Xplosion.blazeInstaller.BlazeInstaller.handleOptions(BlazeInstaller.java:51)
at com.big_Xplosion.blazeInstaller.BlazeInstaller.main(BlazeInstaller.java:26)
the code in the main class.
File file = mcpSpec.value(options); //the file input given is 'test'
try
{
InstallType.MCP.install(file.getAbsoluteFile());
}
catch (IOException e)
{
e.printStackTrace();
}
The execution code The mcpTarget file has to be a directory
public boolean install(File mcpTarget) throws IOException
{
mcpTarget.mkdirs();
if (isMCPInstalled(mcpTarget))
System.out.println(String.format("MCP is already installed in %s, skipped download and extraction.", mcpTarget));
else if (isMCPDownloaded(mcpTarget))
{
if (!unpackMCPZip(mcpTarget))
return false;
}
else
{
if (!downloadMCP(mcpTarget))
return false;
if (!unpackMCPZip(mcpTarget))
return false;
}
System.out.println("Successfully downloaded and unpacked MCP");
return false;
}
Download MCP method
public boolean downloadMCP(File targetFile)
{
String mcpURL = new UnresolvedString(LibURL.MCP_DOWNLOAD_URL, new VersionResolver()).call();
if (!DownloadUtil.downloadFile("MCP", targetFile, mcpURL))
{
System.out.println("Failed to download MCP, please try again and if it still doesn't work contact a dev.");
return false;
}
return true;
}
and the DownloadUtil.DownloadFile method
public static boolean downloadFile(String name, File path, String downloadUrl)
{
System.out.println(String.format("Attempt at downloading file: %s", name));
try
{
URL url = new URL(downloadUrl);
final URLConnection connection = url.openConnection();
connection.setConnectTimeout(6000);
connection.setReadTimeout(6000);
InputSupplier<InputStream> urlSupplier = new InputSupplier<InputStream>()
{
#Override
public InputStream getInput() throws IOException
{
return connection.getInputStream();
}
};
Files.copy(urlSupplier, path);
return true;
}
catch (Exception e)
{
e.printStackTrace();
return false;
}
}
mcpTarget.mkdirs();
mcpTarget.mkdir();
This is the problem. You are creating a folder at the specified file. Replace this with
mcpTarget.getParentFile().mkdirs();
(or, since you use Guava, use this: Files.createParentDirs(mcpTarget))
Also, the latter is a subset of the former, so you never need to call both of the mkdir methods.

A properties file I created in the 1st run gets blanked in the 2nd run

Okay, I'm trying to create a custom client for Minecraft (don't worry, my question has nothing to do with Minecraft in particular), and I added an abstract class to manage a configuration file using Java's built-in Properties system. I have a method that loads a properties file or creates it if it doesn't already exist. This method is called at the beginning of all my other methods (although it only does anything the first time its called).
The properties file gets created just fine when I run Minecraft the first time, but somehow when I run it the second time, the file gets blanked out. I'm not sure where or why or how I'm wiping the file clean, can someone please help me? Here's my code; the offending method is loadConfig():
package net.minecraft.src;
import java.util.*;
import java.util.regex.*;
import java.io.*;
/**
* Class for managing my custom client's properties
*
* #author oxguy3
*/
public abstract class OxProps
{
public static boolean configloaded = false;
private static Properties props = new Properties();
private static String[] usernames;
public static void loadConfig() {
System.out.println("loadConfig() called");
if (!configloaded) {
System.out.println("loading config for the first time");
File cfile = new File("oxconfig.properties");
boolean configisnew;
if (!cfile.exists()) {
System.out.println("cfile failed exists(), creating blank file");
try {
configisnew = cfile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
configisnew=true;
}
} else {
System.out.println("cfile passed exists(), proceding");
configisnew=false;
}
FileInputStream cin = null;
FileOutputStream cout = null;
try {
cin = new FileInputStream(cfile);
cout = new FileOutputStream(cfile);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
if (!configisnew) { //if the config already existed
System.out.println("config already existed");
try {
props.load(cin);
} catch (IOException e) {
e.printStackTrace();
}
} else { //if it doesn't exist, and therefore needs to be created
System.out.println("creating new config");
props.setProperty("names", "oxguy3, Player");
props.setProperty("cloak_url", "http://s3.amazonaws.com/MinecraftCloaks/akronman1.png");
try {
props.store(cout, "OXGUY3'S CUSTOM CLIENT\n\ncloak_url is the URL to get custom cloaks from\nnames are the usernames to give cloaks to\n");
cout.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
String names = props.getProperty("names");
System.out.println("names: "+names);
try {
usernames = Pattern.compile(", ").split(names);
} catch (NullPointerException npe) {
npe.printStackTrace();
}
System.out.println("usernames: "+Arrays.toString(usernames));
configloaded=true;
}
}
public static boolean checkUsername(String username) {
loadConfig();
System.out.println("Checking username...");
for (int i=0; i<usernames.length; i++) {
System.out.println("comparing "+username+" with config value "+usernames[i]);
if (username.startsWith(usernames[i])){
System.out.println("we got a match!");
return true;
}
}
System.out.println("no match found");
return false;
}
public static String getCloakUrl() {
loadConfig();
return props.getProperty("cloak_url", "http://s3.amazonaws.com/MinecraftCloaks/akronman1.png");
}
}
If it's too hard to read here, it's also on Pastebin: http://pastebin.com/9UscXWap
Thanks!
You are unconditionally creating new FileOutputStream(cfile). This will overwrite the existing file with an empty one. You should only invoke the FileOutputStream constructor when writing a new config file.
if (configloaded)
return;
File cfile = new File("oxconfig.properties");
try {
if (cfile.createNewFile()) {
try {
FileOutputStream cout = new FileOutputStream(cfile);
props.setProperty("names", "oxguy3, Player");
props.setProperty("cloak_url", "http://...");
...
cout.flush();
} finally {
cout.close();
}
} else {
FileInputStream cin = new FileInputStream(cfile);
try {
props.load(cin);
} finally {
cin.close();
}
}
configloaded=true;
} catch(IOException ex) {
e.printStackTrace();
}

Categories